From c6a3a37f0e133b604881f6e33c02fa14866e421d Mon Sep 17 00:00:00 2001 From: dougal83 Date: Thu, 13 Jul 2017 10:42:11 +0100 Subject: [PATCH 1/2] secured api /w express-jwt [issue 58] --- client/app/app.component.html | 12 +-- client/app/app.module.ts | 8 +- client/app/auth.module.ts | 30 ++++++ client/app/login/login.component.ts | 2 +- .../app/services/auth-guard-login.service.ts | 6 +- client/app/services/auth.service.ts | 11 ++- client/app/services/cat.service.ts | 16 +-- client/app/services/user.service.ts | 16 +-- package.json | 1 + server/controllers/base.ts | 98 +++++++++++++------ server/controllers/user.ts | 14 +++ server/routes.ts | 30 +++--- 12 files changed, 168 insertions(+), 76 deletions(-) create mode 100644 client/app/auth.module.ts diff --git a/client/app/app.component.html b/client/app/app.component.html index 049a7757..eecc43d7 100644 --- a/client/app/app.component.html +++ b/client/app/app.component.html @@ -5,22 +5,22 @@ Home - + Cats - + Login - + Register - + Account ({{auth.currentUser.username}}) - + Admin - + Logout diff --git a/client/app/app.module.ts b/client/app/app.module.ts index a0ccfb9f..9384aa69 100644 --- a/client/app/app.module.ts +++ b/client/app/app.module.ts @@ -1,12 +1,10 @@ import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { RoutingModule } from './routing.module'; +import { AuthModule } from './auth.module'; import { SharedModule } from './shared/shared.module'; import { CatService } from './services/cat.service'; import { UserService } from './services/user.service'; -import { AuthService } from './services/auth.service'; -import { AuthGuardLogin } from './services/auth-guard-login.service'; -import { AuthGuardAdmin } from './services/auth-guard-admin.service'; import { AppComponent } from './app.component'; import { CatsComponent } from './cats/cats.component'; import { AboutComponent } from './about/about.component'; @@ -30,13 +28,11 @@ import { NotFoundComponent } from './not-found/not-found.component'; NotFoundComponent ], imports: [ + AuthModule, RoutingModule, SharedModule ], providers: [ - AuthService, - AuthGuardLogin, - AuthGuardAdmin, CatService, UserService ], diff --git a/client/app/auth.module.ts b/client/app/auth.module.ts new file mode 100644 index 00000000..c4cccfd2 --- /dev/null +++ b/client/app/auth.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { Http, RequestOptions } from '@angular/http'; +import { AuthHttp, AuthConfig } from 'angular2-jwt'; + +import { AuthService } from './services/auth.service'; +import { AuthGuardLogin } from './services/auth-guard-login.service'; +import { AuthGuardAdmin } from './services/auth-guard-admin.service'; + +export function authHttpServiceFactory(http: Http, options: RequestOptions) { + return new AuthHttp(new AuthConfig({ + tokenName: 'token', + tokenGetter: (() => localStorage.getItem('token')), + globalHeaders: [{'Content-Type': 'application/json'}], + }), http, options); +} + +@NgModule({ + providers: [ + AuthService, + AuthGuardLogin, + AuthGuardAdmin, + { + provide: AuthHttp, + useFactory: authHttpServiceFactory, + deps: [Http, RequestOptions] + } + ] +}) + +export class AuthModule { } diff --git a/client/app/login/login.component.ts b/client/app/login/login.component.ts index d93b348f..3ccadc3e 100644 --- a/client/app/login/login.component.ts +++ b/client/app/login/login.component.ts @@ -25,7 +25,7 @@ export class LoginComponent implements OnInit { public toast: ToastComponent) { } ngOnInit() { - if (this.auth.loggedIn) { + if (this.auth.loggedIn()) { this.router.navigate(['/']); } this.loginForm = this.formBuilder.group({ diff --git a/client/app/services/auth-guard-login.service.ts b/client/app/services/auth-guard-login.service.ts index e42e78e5..0cf05c4e 100644 --- a/client/app/services/auth-guard-login.service.ts +++ b/client/app/services/auth-guard-login.service.ts @@ -8,7 +8,11 @@ export class AuthGuardLogin implements CanActivate { constructor(public auth: AuthService, private router: Router) {} canActivate() { - return this.auth.loggedIn; + if (this.auth.loggedIn()) { + return true; + } else { + return false; + } } } diff --git a/client/app/services/auth.service.ts b/client/app/services/auth.service.ts index c2a33a2f..28626ed6 100644 --- a/client/app/services/auth.service.ts +++ b/client/app/services/auth.service.ts @@ -1,12 +1,11 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { JwtHelper } from 'angular2-jwt'; +import { JwtHelper, tokenNotExpired } from 'angular2-jwt'; import { UserService } from '../services/user.service'; @Injectable() export class AuthService { - loggedIn = false; isAdmin = false; jwtHelper: JwtHelper = new JwtHelper(); @@ -28,14 +27,13 @@ export class AuthService { localStorage.setItem('token', res.token); const decodedUser = this.decodeUserFromToken(res.token); this.setCurrentUser(decodedUser); - return this.loggedIn; + return this.loggedIn(); } ); } logout() { localStorage.removeItem('token'); - this.loggedIn = false; this.isAdmin = false; this.currentUser = { _id: '', username: '', role: '' }; this.router.navigate(['/']); @@ -46,7 +44,6 @@ export class AuthService { } setCurrentUser(decodedUser) { - this.loggedIn = true; this.currentUser._id = decodedUser._id; this.currentUser.username = decodedUser.username; this.currentUser.role = decodedUser.role; @@ -54,4 +51,8 @@ export class AuthService { delete decodedUser.role; } + loggedIn() { + return tokenNotExpired(); + } + } diff --git a/client/app/services/cat.service.ts b/client/app/services/cat.service.ts index 0179611e..0367eed5 100644 --- a/client/app/services/cat.service.ts +++ b/client/app/services/cat.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { Http, Headers, RequestOptions } from '@angular/http'; +import { AuthHttp } from 'angular2-jwt'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; @@ -10,30 +11,31 @@ export class CatService { private headers = new Headers({ 'Content-Type': 'application/json', 'charset': 'UTF-8' }); private options = new RequestOptions({ headers: this.headers }); - constructor(private http: Http) { } + constructor(private http: Http, + public authHttp: AuthHttp) { } getCats(): Observable { - return this.http.get('/api/cats').map(res => res.json()); + return this.authHttp.get('/api/cats').map(res => res.json()); } countCats(): Observable { - return this.http.get('/api/cats/count').map(res => res.json()); + return this.authHttp.get('/api/cats/count').map(res => res.json()); } addCat(cat): Observable { - return this.http.post('/api/cat', JSON.stringify(cat), this.options); + return this.authHttp.post('/api/cat', JSON.stringify(cat), this.options); } getCat(cat): Observable { - return this.http.get(`/api/cat/${cat._id}`).map(res => res.json()); + return this.authHttp.get(`/api/cat/${cat._id}`).map(res => res.json()); } editCat(cat): Observable { - return this.http.put(`/api/cat/${cat._id}`, JSON.stringify(cat), this.options); + return this.authHttp.put(`/api/cat/${cat._id}`, JSON.stringify(cat), this.options); } deleteCat(cat): Observable { - return this.http.delete(`/api/cat/${cat._id}`, this.options); + return this.authHttp.delete(`/api/cat/${cat._id}`, this.options); } } diff --git a/client/app/services/user.service.ts b/client/app/services/user.service.ts index e179c0b8..3bb40eb5 100644 --- a/client/app/services/user.service.ts +++ b/client/app/services/user.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { Http, Headers, RequestOptions } from '@angular/http'; +import { AuthHttp } from 'angular2-jwt'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; @@ -10,7 +11,8 @@ export class UserService { private headers = new Headers({ 'Content-Type': 'application/json', 'charset': 'UTF-8' }); private options = new RequestOptions({ headers: this.headers }); - constructor(private http: Http) { } + constructor(private http: Http, + public authHttp: AuthHttp) { } register(user): Observable { return this.http.post('/api/user', JSON.stringify(user), this.options); @@ -21,27 +23,27 @@ export class UserService { } getUsers(): Observable { - return this.http.get('/api/users').map(res => res.json()); + return this.authHttp.get('/api/users').map(res => res.json()); } countUsers(): Observable { - return this.http.get('/api/users/count').map(res => res.json()); + return this.authHttp.get('/api/users/count').map(res => res.json()); } addUser(user): Observable { - return this.http.post('/api/user', JSON.stringify(user), this.options); + return this.authHttp.post('/api/user', JSON.stringify(user), this.options); } getUser(user): Observable { - return this.http.get(`/api/user/${user._id}`).map(res => res.json()); + return this.authHttp.get(`/api/user/${user._id}`).map(res => res.json()); } editUser(user): Observable { - return this.http.put(`/api/user/${user._id}`, JSON.stringify(user), this.options); + return this.authHttp.put(`/api/user/${user._id}`, JSON.stringify(user), this.options); } deleteUser(user): Observable { - return this.http.delete(`/api/user/${user._id}`, this.options); + return this.authHttp.delete(`/api/user/${user._id}`, this.options); } } diff --git a/package.json b/package.json index 6bbdcf98..31dab7d8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "core-js": "^2.4.1", "dotenv": "^4.0.0", "express": "^4.15.3", + "express-jwt": "^5.3.0", "font-awesome": "^4.7.0", "jquery": "^3.2.1", "jsonwebtoken": "^7.4.1", diff --git a/server/controllers/base.ts b/server/controllers/base.ts index 24a5f31c..ca31d485 100644 --- a/server/controllers/base.ts +++ b/server/controllers/base.ts @@ -4,57 +4,93 @@ abstract class BaseCtrl { // Get all getAll = (req, res) => { - this.model.find({}, (err, docs) => { - if (err) { return console.error(err); } - res.json(docs); - }); + if (!req.payload.user._id) { + res.status(401).json({ + 'message' : 'UnauthorizedError: private' + }); + } else { + this.model.find({}, (err, docs) => { + if (err) { return console.error(err); } + res.json(docs); + }); + } }; // Count all count = (req, res) => { - this.model.count((err, count) => { - if (err) { return console.error(err); } - res.json(count); - }); + if (!req.payload.user._id) { + res.status(401).json({ + 'message' : 'UnauthorizedError: private' + }); + } else { + this.model.count((err, count) => { + if (err) { return console.error(err); } + res.json(count); + }); + } }; // Insert insert = (req, res) => { - const obj = new this.model(req.body); - obj.save((err, item) => { - // 11000 is the code for duplicate key error - if (err && err.code === 11000) { - res.sendStatus(400); - } - if (err) { - return console.error(err); - } - res.status(200).json(item); - }); + if (!req.payload.user._id) { + res.status(401).json({ + 'message' : 'UnauthorizedError: private' + }); + } else { + const obj = new this.model(req.body); + obj.save((err, item) => { + // 11000 is the code for duplicate key error + if (err && err.code === 11000) { + res.sendStatus(400); + } + if (err) { + return console.error(err); + } + res.status(200).json(item); + }); + } }; // Get by id get = (req, res) => { - this.model.findOne({ _id: req.params.id }, (err, obj) => { - if (err) { return console.error(err); } - res.json(obj); - }); + if (!req.payload.user._id) { + res.status(401).json({ + 'message' : 'UnauthorizedError: private' + }); + } else { + this.model.findOne({ _id: req.params.id }, (err, obj) => { + if (err) { return console.error(err); } + res.json(obj); + }); + } }; // Update by id update = (req, res) => { - this.model.findOneAndUpdate({ _id: req.params.id }, req.body, (err) => { - if (err) { return console.error(err); } - res.sendStatus(200); - }); + if (!req.payload.user._id) { + res.status(401).json({ + 'message' : 'UnauthorizedError: private' + }); + } else { + this.model.findOneAndUpdate({ _id: req.params.id }, req.body, (err) => { + if (err) { return console.error(err); } + res.sendStatus(200); + }); + } }; // Delete by id delete = (req, res) => { - this.model.findOneAndRemove({ _id: req.params.id }, (err) => { - if (err) { return console.error(err); } - res.sendStatus(200); - }); + if (!req.payload.user._id) { + res.status(401).json({ + 'message' : 'UnauthorizedError: private' + }); + } else { + this.model.findOneAndRemove({ _id: req.params.id }, (err) => { + if (err) { return console.error(err); } + res.sendStatus(200); + }); + } }; } diff --git a/server/controllers/user.ts b/server/controllers/user.ts index ac4cd22f..08bc04b0 100644 --- a/server/controllers/user.ts +++ b/server/controllers/user.ts @@ -7,6 +7,20 @@ import BaseCtrl from './base'; export default class UserCtrl extends BaseCtrl { model = User; + register = (req, res) => { + const obj = new this.model(req.body); + obj.save((err, item) => { + // 11000 is the code for duplicate key error + if (err && err.code === 11000) { + res.sendStatus(400); + } + if (err) { + return console.error(err); + } + res.status(200).json(item); + }); + }; + login = (req, res) => { this.model.findOne({ email: req.body.email }, (err, user) => { if (!user) { return res.sendStatus(403); } diff --git a/server/routes.ts b/server/routes.ts index 1ec8093e..4f0acc78 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1,4 +1,6 @@ import * as express from 'express'; +import * as jwt from 'express-jwt'; +import * as dotenv from 'dotenv'; import CatCtrl from './controllers/cat'; import UserCtrl from './controllers/user'; @@ -8,26 +10,30 @@ import User from './models/user'; export default function setRoutes(app) { const router = express.Router(); + const auth = jwt({ + secret: process.env.SECRET_TOKEN, + userProperty: 'payload' + }); const catCtrl = new CatCtrl(); const userCtrl = new UserCtrl(); // Cats - router.route('/cats').get(catCtrl.getAll); - router.route('/cats/count').get(catCtrl.count); - router.route('/cat').post(catCtrl.insert); - router.route('/cat/:id').get(catCtrl.get); - router.route('/cat/:id').put(catCtrl.update); - router.route('/cat/:id').delete(catCtrl.delete); + router.route('/cats').get(auth, catCtrl.getAll); + router.route('/cats/count').get(auth, catCtrl.count); + router.route('/cat').post(auth, catCtrl.insert); + router.route('/cat/:id').get(auth, catCtrl.get); + router.route('/cat/:id').put(auth, catCtrl.update); + router.route('/cat/:id').delete(auth, catCtrl.delete); // Users router.route('/login').post(userCtrl.login); - router.route('/users').get(userCtrl.getAll); - router.route('/users/count').get(userCtrl.count); - router.route('/user').post(userCtrl.insert); - router.route('/user/:id').get(userCtrl.get); - router.route('/user/:id').put(userCtrl.update); - router.route('/user/:id').delete(userCtrl.delete); + router.route('/users').get(auth, userCtrl.getAll); + router.route('/users/count').get(auth, userCtrl.count); + router.route('/user').post(userCtrl.register); + router.route('/user/:id').get(auth, userCtrl.get); + router.route('/user/:id').put(auth, userCtrl.update); + router.route('/user/:id').delete(auth, userCtrl.delete); // Apply the routes to our application with the prefix /api app.use('/api', router); From 6a08d2cea15185d2785ca3b031eb7f3e6e744538 Mon Sep 17 00:00:00 2001 From: dougal83 Date: Thu, 13 Jul 2017 11:27:39 +0100 Subject: [PATCH 2/2] removed redundant headers from AuthHttp calls --- client/app/services/cat.service.ts | 12 ++++-------- client/app/services/user.service.ts | 6 +++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/client/app/services/cat.service.ts b/client/app/services/cat.service.ts index 0367eed5..8263709a 100644 --- a/client/app/services/cat.service.ts +++ b/client/app/services/cat.service.ts @@ -8,11 +8,7 @@ import 'rxjs/add/operator/map'; @Injectable() export class CatService { - private headers = new Headers({ 'Content-Type': 'application/json', 'charset': 'UTF-8' }); - private options = new RequestOptions({ headers: this.headers }); - - constructor(private http: Http, - public authHttp: AuthHttp) { } + constructor(public authHttp: AuthHttp) { } getCats(): Observable { return this.authHttp.get('/api/cats').map(res => res.json()); @@ -23,7 +19,7 @@ export class CatService { } addCat(cat): Observable { - return this.authHttp.post('/api/cat', JSON.stringify(cat), this.options); + return this.authHttp.post('/api/cat', JSON.stringify(cat)); } getCat(cat): Observable { @@ -31,11 +27,11 @@ export class CatService { } editCat(cat): Observable { - return this.authHttp.put(`/api/cat/${cat._id}`, JSON.stringify(cat), this.options); + return this.authHttp.put(`/api/cat/${cat._id}`, JSON.stringify(cat)); } deleteCat(cat): Observable { - return this.authHttp.delete(`/api/cat/${cat._id}`, this.options); + return this.authHttp.delete(`/api/cat/${cat._id}`); } } diff --git a/client/app/services/user.service.ts b/client/app/services/user.service.ts index 3bb40eb5..8de03239 100644 --- a/client/app/services/user.service.ts +++ b/client/app/services/user.service.ts @@ -31,7 +31,7 @@ export class UserService { } addUser(user): Observable { - return this.authHttp.post('/api/user', JSON.stringify(user), this.options); + return this.authHttp.post('/api/user', JSON.stringify(user)); } getUser(user): Observable { @@ -39,11 +39,11 @@ export class UserService { } editUser(user): Observable { - return this.authHttp.put(`/api/user/${user._id}`, JSON.stringify(user), this.options); + return this.authHttp.put(`/api/user/${user._id}`, JSON.stringify(user)); } deleteUser(user): Observable { - return this.authHttp.delete(`/api/user/${user._id}`, this.options); + return this.authHttp.delete(`/api/user/${user._id}`); } }