Skip to content

Commit

Permalink
security(jwt): add security layer for API calls
Browse files Browse the repository at this point in the history
  • Loading branch information
jkuri committed Aug 20, 2017
1 parent 160248e commit e6126e9
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 61 deletions.
26 changes: 26 additions & 0 deletions src/api/security.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as jwt from 'jsonwebtoken';
import * as crypto from 'crypto';
import * as express from 'express';
import { getUser } from './db/user';

export function generatePassword(plain: string): Promise<string> {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -27,3 +29,27 @@ export function generateJwt(data: any): Promise<string> {
export function calculateMd5(str: string): string {
return crypto.createHash('md5').update(str).digest('hex');
}

export function checkApiRequestAuth(req: express.Request): Promise<void> {
return new Promise((resolve, reject) => {
const token = req.get('abstruse-ci-token');
if (!token) {
reject('Authentication failed.');
} else {
jwt.verify(token, 'abstruseSecret4321!!', (err, decoded: any) => {
if (err) {
reject('Authentication failed.');
} else {
getUser(decoded.id)
.then(user => {
if (!user) {
reject('Authentication failed');
} else {
resolve();
}
});
}
});
}
});
}
103 changes: 67 additions & 36 deletions src/api/server-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { getBuilds, getBuild } from './db/build';
import { getJob } from './db/job';
import { insertAccessToken, getAccessTokens } from './db/access-token';
import { imageExists } from './docker';
import { checkApiRequestAuth } from './security';

export function webRoutes(): express.Router {
const router = express.Router();
Expand Down Expand Up @@ -79,11 +80,14 @@ export function userRoutes(): express.Router {
const router = express.Router();

router.get('/', (req: express.Request, res: express.Response) => {
getUsers().then(users => {
return res.status(200).json({ data: users });
}).catch(err => {
return res.status(200).json({ err: err });
});
checkApiRequestAuth(req)
.then(() => {
getUsers().then(users => {
return res.status(200).json({ data: users });
}).catch(err => {
return res.status(200).json({ err: err });
});
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.post('/login', (req: express.Request, res: express.Response) => {
Expand All @@ -101,31 +105,43 @@ export function userRoutes(): express.Router {
});

router.post('/save', (req: express.Request, res: express.Response) => {
updateUser(req.body).then(() => {
return res.status(200).json({ data: true });
}).catch(err => {
return res.status(200).json({ data: false });
});
checkApiRequestAuth(req)
.then(() => {
updateUser(req.body).then(() => {
return res.status(200).json({ data: true });
}).catch(err => {
return res.status(200).json({ data: false });
});
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.post('/update-password', (req: express.Request, res: express.Response) => {
updateUserPassword(req.body).then(() => {
return res.status(200).json({ data: true });
}).catch(err => {
return res.status(200).json({ data: false });
});
checkApiRequestAuth(req)
.then(() => {
updateUserPassword(req.body).then(() => {
return res.status(200).json({ data: true });
}).catch(err => {
return res.status(200).json({ data: false });
});
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.get('/:id', (req: express.Request, res: express.Response) => {
getUser(req.params.id)
.then(user => res.status(200).json({ data: user }))
.catch(err => res.status(400).json({ err: err }));
checkApiRequestAuth(req)
.then(() => {
getUser(req.params.id)
.then(user => res.status(200).json({ data: user }))
.catch(err => res.status(400).json({ err: err }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.post('/add-token', (req: express.Request, res: express.Response) => {
insertAccessToken(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
checkApiRequestAuth(req)
.then(() => {
insertAccessToken(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

return router;
Expand All @@ -135,9 +151,12 @@ export function tokenRoutes(): express.Router {
const router = express.Router();

router.get('/', (req: express.Request, res: express.Response) => {
getAccessTokens()
.then(tokens => res.status(200).json({ data: tokens }))
.catch(() => res.status(200).json({ data: false }));
checkApiRequestAuth(req)
.then(() => {
getAccessTokens()
.then(tokens => res.status(200).json({ data: tokens }))
.catch(() => res.status(200).json({ data: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

return router;
Expand All @@ -147,27 +166,39 @@ export function repositoryRoutes(): express.Router {
const router = express.Router();

router.get('/', (req: express.Request, res: express.Response) => {
getRepositories(req.query.userId, req.query.keyword).then(repos => {
return res.status(200).json({ data: repos });
}).catch(err => res.status(200).json({ status: false }));
checkApiRequestAuth(req)
.then(() => {
getRepositories(req.query.userId, req.query.keyword).then(repos => {
return res.status(200).json({ data: repos });
}).catch(err => res.status(200).json({ status: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.get('/:id', (req: express.Request, res: express.Response) => {
getRepository(req.params.id).then(repo => {
return res.status(200).json({ data: repo });
}).catch(err => res.status(200).json({ status: false }));
checkApiRequestAuth(req)
.then(() => {
getRepository(req.params.id).then(repo => {
return res.status(200).json({ data: repo });
}).catch(err => res.status(200).json({ status: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.post('/add', (req: express.Request, res: express.Response) => {
addRepository(req.body).then(result => {
return res.status(200).json({ status: true });
}).catch(err => res.status(200).json({ status: false }));
checkApiRequestAuth(req)
.then(() => {
addRepository(req.body).then(result => {
return res.status(200).json({ status: true });
}).catch(err => res.status(200).json({ status: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.post('/save', (req: express.Request, res: express.Response) => {
saveRepositorySettings(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
checkApiRequestAuth(req)
.then(() => {
saveRepositorySettings(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

return router;
Expand Down
11 changes: 8 additions & 3 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<router-outlet></router-outlet>

<div class="disconnected" *ngIf="!connected">
<p *ngIf="state === 2">Connection closed. Please check your internet connection.</p>
<p *ngIf="state === 3 || state === 0">Retrying to connect ...</p>
<div class="disconnected" *ngIf="!connected || routing">
<div *ngIf="!routing">
<p *ngIf="state === 2">Connection closed. Please check your internet connection.</p>
<p *ngIf="state === 3 || state === 0">Retrying to connect ...</p>
</div>
<div *ngIf="routing">
<p>Loading, please wait ...</p>
</div>
</div>
12 changes: 11 additions & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { SocketService } from './services/socket.service';
import { ConnectionStates } from './classes/rx-web-socket.class';

Expand All @@ -9,8 +10,9 @@ import { ConnectionStates } from './classes/rx-web-socket.class';
export class AppComponent {
connected: boolean;
state: ConnectionStates;
routing: boolean;

constructor(private socketService: SocketService) { }
constructor(private socketService: SocketService, private router: Router) { }

ngOnInit() {
this.socketService.connectionState
Expand All @@ -23,5 +25,13 @@ export class AppComponent {
this.connected = false;
}
});

this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.routing = true;
} else if (event instanceof NavigationEnd) {
this.routing = false;
}
});
}
}
2 changes: 1 addition & 1 deletion src/app/components/app-builds/app-builds.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h1>Dashboard</h1>
<div class="nav-right">
<div class="group-buttons">
<button class="group-button is-active">All Builds</button>
<button class="group-button">Branches</button>
<button class="group-button">Commits</button>
<button class="group-button">Pull Requests</button>
</div>
</div>
Expand Down
54 changes: 34 additions & 20 deletions src/app/services/api.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable, Provider } from '@angular/core';
import { Http, Response, URLSearchParams, RequestOptions, Headers } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { IAccessToken } from '../components/app-settings';

Expand All @@ -9,7 +10,7 @@ export class ApiService {
loc: Location;
port: string;

constructor(private http: Http) {
constructor(private http: Http, private router: Router) {
this.loc = window.location;
this.port = this.loc.port === '8000' ? ':6500' : `:${this.loc.port}`; // dev mode
this.url = `${this.loc.protocol}//${this.loc.hostname}${this.port}/api`;
Expand All @@ -20,34 +21,34 @@ export class ApiService {
}

getBuilds(limit: number, offset: number): Observable<any> {
return this.get(`${this.url}/builds/limit/${limit}/offset/${offset}`);
return this.get(`${this.url}/builds/limit/${limit}/offset/${offset}`, null, true);
}

getBuild(id: string): Observable<any> {
return this.get(`${this.url}/builds/${id}`);
return this.get(`${this.url}/builds/${id}`, null, true);
}

getJob(id: number): Observable<any> {
return this.get(`${this.url}/jobs/${id}`);
return this.get(`${this.url}/jobs/${id}`, null, true);
}

getRepositories(userId: string, keyword: string): Observable<any> {
const params = new URLSearchParams();
params.append('userId', userId);
params.append('keyword', keyword);
return this.get(`${this.url}/repositories`, params);
return this.get(`${this.url}/repositories`, params, true);
}

getRepository(id: string): Observable<any> {
return this.get(`${this.url}/repositories/${id}`);
return this.get(`${this.url}/repositories/${id}`, null, true);
}

addRepository(data: any): Observable<any> {
return this.post(`${this.url}/repositories/add`, data);
return this.post(`${this.url}/repositories/add`, data, true);
}

saveRepositorySettings(data: any): Observable<any> {
return this.post(`${this.url}/repositories/save`, data);
return this.post(`${this.url}/repositories/save`, data, true);
}

isAppReady(): Observable<any> {
Expand All @@ -71,45 +72,53 @@ export class ApiService {
}

getAllTokens(): Observable<any> {
return this.get(`${this.url}/tokens`);
return this.get(`${this.url}/tokens`, null, true);
}

addToken(data: IAccessToken): Observable<any> {
return this.post(`${this.url}/user/add-token`, data);
return this.post(`${this.url}/user/add-token`, data, true);
}

getUsers(): Observable<any> {
return this.get(`${this.url}/user`);
return this.get(`${this.url}/user`, null, true);
}

getUser(id: number): Observable<any> {
return this.get(`${this.url}/user/${id}`);
return this.get(`${this.url}/user/${id}`, null, true);
}

updateUser(data: any): Observable<any> {
return this.post(`${this.url}/user/save`, data);
return this.post(`${this.url}/user/save`, data, true);
}

updatePassword(data: any): Observable<any> {
return this.post(`${this.url}/user/update-password`, data);
return this.post(`${this.url}/user/update-password`, data, true);
}

createUser(data: any): Observable<any> {
return this.post(`${this.url}/user/create`, data);
return this.post(`${this.url}/user/create`, data, true);
}

login(data: any): Observable<any> {
return this.post(`${this.url}/user/login`, data);
}

private get(url: string, searchParams: URLSearchParams = null): Observable<any> {
return this.http.get(url, { search: searchParams })
private get(url: string, searchParams: URLSearchParams = null, auth = false): Observable<any> {
let headers = new Headers();
if (auth) {
headers.append('abstruse-ci-token', localStorage.getItem('abs-token'));
}

return this.http.get(url, { search: searchParams, headers: headers })
.map(this.extractData)
.catch(this.handleError);
}

private post(url: string, data: any): Observable<any> {
private post(url: string, data: any, auth = false): Observable<any> {
let headers = new Headers({ 'Content-Type': 'application/json' });
if (auth) {
headers.append('abstruse-ci-token', localStorage.getItem('abs-token'));
}
let options = new RequestOptions({ headers: headers });

return this.http.post(url, data, options)
Expand All @@ -118,8 +127,13 @@ export class ApiService {
}

private extractData(res: Response) {
let body = res.json();
return body && typeof body.data !== 'undefined' ? body.data : {};
if (res.status !== 200) {
localStorage.removeItem('abs-token');
this.router.navigate(['/login']);
} else {
let body = res.json();
return body && typeof body.data !== 'undefined' ? body.data : {};
}
}

private handleError (error: Response | any) {
Expand Down

0 comments on commit e6126e9

Please sign in to comment.