Skip to content

Commit d4252ce

Browse files
author
hirsch88
committed
Merge branch 'release/1.4.0'
2 parents c1852d9 + a9645f4 commit d4252ce

33 files changed

+853
-840
lines changed

.env.example

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ APP_NAME=express-typescript-boilerplate
55
APP_ENV=local
66
APP_HOST=http://localhost
77
APP_PORT=3000
8+
APP_URL_PREFIX=/api
89

910
#
1011
# LOGGING
@@ -34,10 +35,4 @@ DB_SEEDS_DIR=./src/database/seeds
3435
#
3536
# Auth0
3637
#
37-
AUTH0_HOST=https://w3tecch.auth0.com
38-
AUTH0_OAUTH=/oauth
39-
AUTH0_API=/api/v2
40-
AUTH0_CLIENT_ID=auth0_client_id
41-
AUTH0_CLIENT_SECRET=auth0_client_secret
42-
AUTH0_AUDIENCE=https://my.auth0.com/api/v2/
43-
AUTH0_GRANT_TYPE=client_credentials
38+
AUTH0_HOST=http://localhost:3333

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
{
22
"name": "express-typescript-boilerplate",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"description": "A delightful way to building a RESTful API with NodeJs & TypeScript",
55
"main": "src/index.ts",
66
"scripts": {
77
"lint": "./node_modules/.bin/gulp lint",
8-
"test": "NODE_ENV=test ./node_modules/.bin/jest ./test/unit --verbose --coverage",
9-
"test:black-box": "NODE_ENV=test ./node_modules/.bin/jest ./test/black-box -i --verbose",
8+
"test": "NODE_ENV=test ./node_modules/.bin/jest ./test/unit",
9+
"test:pretty": "NODE_ENV=test ./node_modules/.bin/jest ./test/unit --verbose",
10+
"test:coverage": "NODE_ENV=test ./node_modules/.bin/jest ./test/unit --coverage",
11+
"test:black-box": "NODE_ENV=test ./node_modules/.bin/jest ./test/black-box -i",
12+
"test:black-box:pretty": "NODE_ENV=test ./node_modules/.bin/jest ./test/black-box -i --verbose",
1013
"build": "./node_modules/.bin/gulp build",
1114
"clean": "./node_modules/.bin/gulp clean",
1215
"db:migrate": "./node_modules/.bin/knex migrate:latest",
@@ -56,6 +59,7 @@
5659
"@types/faker": "^4.1.0",
5760
"@types/helmet": "0.0.35",
5861
"@types/jest": "^19.2.3",
62+
"@types/jsonwebtoken": "^7.2.0",
5963
"@types/knex": "0.0.50",
6064
"@types/lodash": "^4.14.64",
6165
"@types/morgan": "^1.7.32",
@@ -87,6 +91,7 @@
8791
"inversify": "^4.1.0",
8892
"inversify-express-utils": "^3.5.1",
8993
"jest": "^20.0.3",
94+
"jsonwebtoken": "^7.4.1",
9095
"knex": "^0.12.0",
9196
"lodash": "^4.17.4",
9297
"morgan": "^1.7.0",

src/api/controllers/UserController.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { injectable, inject } from 'inversify';
2-
import { Controller, Get, Post, Put, Delete, RequestParam, RequestBody, Response } from 'inversify-express-utils';
2+
import { Controller, Get, Post, Put, Delete, RequestParam, RequestBody, Response, Request } from 'inversify-express-utils';
33
import { my } from 'my-express';
44
import { Log } from '../../core/log';
55
import { UserService } from '../services/UsersService';
66
import { Types } from '../../constants/Types';
7-
import { authenticate } from '../middlewares/authenticate';
7+
import { authenticate, populateUser } from '../middlewares';
88

99
const log = new Log('api:ctrl.UserController');
1010

@@ -16,18 +16,24 @@ const log = new Log('api:ctrl.UserController');
1616
* @class UserController
1717
*/
1818
@injectable()
19-
@Controller('/v1/user')
19+
@Controller('/v1/user', authenticate)
2020
export class UserController {
2121

2222
constructor( @inject(Types.UserService) private userService: UserService) { }
2323

24-
@Get('/', authenticate)
24+
@Get('/')
2525
public async findAll( @Response() res: my.Response): Promise<any> {
2626
log.debug('findAll');
2727
const users = await this.userService.findAll();
2828
return res.found(users.toJSON());
2929
}
3030

31+
@Get('/me', populateUser)
32+
public async findMe( @Request() req: my.Request, @Response() res: my.Response): Promise<any> {
33+
log.debug('findMe');
34+
return res.found(req.user);
35+
}
36+
3137
@Get('/:id')
3238
public async findOne( @Response() res: my.Response, @RequestParam('id') id: string): Promise<any> {
3339
log.debug('findOne ', id);
Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,69 @@
1+
import { RequestAPI, RequiredUriUrl, Options, Request, RequestResponse } from 'request';
12
import { my } from 'my-express';
23
import { Log } from '../../core/log';
34

4-
const log = new Log('api:middleware.authenticate');
5+
// const log = new Log('api:middleware.authenticate');
56

67
/**
78
* authenticate middleware
89
* -----------------------
9-
* This middleware can be used to check if all credentials are given and
10-
* verify them.
10+
* This middleware secures your resources with the auth0 authentication.
1111
*
1212
* @param req
1313
* @param res
1414
* @param next
1515
*/
16-
export const authenticate = (req: my.Request, res: my.Response, next: my.NextFunction) => {
17-
log.info('authenticate');
18-
next();
16+
export const authenticate = (request: RequestAPI<Request, Options, RequiredUriUrl>, log: Log) =>
17+
(req: my.Request, res: my.Response, next: my.NextFunction) => {
18+
const token = getToken(req);
19+
20+
if (token === null) {
21+
log.warn('No token given');
22+
return res.failed(403, 'You are not allowed to request this resource!');
23+
}
24+
log.debug('Token is provided');
25+
26+
// Request user info at auth0 with the provided token
27+
request({
28+
method: 'POST',
29+
url: `${process.env.AUTH0_HOST}/tokeninfo`,
30+
form: {
31+
id_token: token
32+
}
33+
}, (error: any, response: RequestResponse, body: any) => {
34+
// Verify if the requests was successful and append user
35+
// information to our extended express request object
36+
if (!error && response.statusCode === 200) {
37+
req.tokeninfo = JSON.parse(body);
38+
log.info(`Retrieved user ${req.tokeninfo.email}`);
39+
return next();
40+
}
41+
42+
// Catch auth0 exception and return it as it is
43+
log.warn(`Could not retrieve the user, because of`, body);
44+
let statusCode = 401;
45+
if (response && response.statusCode) {
46+
statusCode = response.statusCode;
47+
} else {
48+
log.warn('It seems your oauth server is down!');
49+
}
50+
res.failed(statusCode, body);
51+
52+
});
53+
54+
};
55+
56+
/**
57+
* Returns the access token of the given request header
58+
*/
59+
const getToken = (req: my.Request): string | null => {
60+
const authorization = req.headers.authorization;
61+
62+
// Retrieve the token form the Authorization header
63+
if (authorization && authorization.split(' ')[0] === 'Bearer') {
64+
return authorization.split(' ')[1];
65+
}
66+
67+
// No token was provided by the client
68+
return null;
1969
};

src/api/middlewares/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as request from 'request';
2+
import container from '../../container';
3+
import { Log } from '../../core/log';
4+
import { Types } from '../../constants/Types';
5+
import { UserService } from '../services/UsersService';
6+
7+
import { authenticate as Authenticate } from './authenticate';
8+
import { populateUser as PopulateUser } from './populateUser';
9+
10+
11+
/**
12+
* Middlewares
13+
* ------------------------------------
14+
* We build them up here so we can easily use them in the controllers and
15+
* also be able to test the middlewares without any big effort.
16+
*/
17+
export const authenticate = Authenticate(request, new Log('api:middleware.authenticate'));
18+
export const populateUser = PopulateUser(() => container.get<UserService>(Types.UserService), new Log('api:middleware.populateUser'));
19+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import * as request from 'request';
2+
import { Container } from 'inversify';
3+
import { my } from 'my-express';
4+
import { Log } from '../../core/log';
5+
import { UserService } from '../services/UsersService';
6+
7+
// const log = new Log('api:middleware.populateUser');
8+
9+
/**
10+
* populateUser middleware
11+
* -----------------------
12+
* This middleware gets the logged-in user form the database and store it in
13+
* the request object
14+
*
15+
* @param req
16+
* @param res
17+
* @param next
18+
*/
19+
export const populateUser = (lazyUserService: () => UserService, log: Log) =>
20+
(req: my.Request, res: my.Response, next: my.NextFunction) => {
21+
22+
if (!req.tokeninfo || !req.tokeninfo.user_id) {
23+
return res.failed(400, 'Missing token information!');
24+
}
25+
26+
const userService = lazyUserService();
27+
userService.findByUserId(req.tokeninfo.user_id)
28+
.then((user) => {
29+
req.user = user.toJSON();
30+
log.debug(`populated user with the id=${req.user.id} to the request object`);
31+
next();
32+
})
33+
.catch((error) => {
34+
log.warn(`could not populate user to the request object`);
35+
next(error);
36+
});
37+
};

src/api/models/User.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Bookshelf } from '../../core/Bookshelf';
1+
import { Bookshelf } from '../../core/Database';
22
import { Tables } from '../../constants/Tables';
33

44
/**
@@ -14,6 +14,10 @@ export class User extends Bookshelf.Model<User> {
1414
return await User.where<User>({ id: id }).fetch();
1515
}
1616

17+
public static async fetchByUserId(userId: string): Promise<User> {
18+
return await User.where<User>({ auth_0_user_id: userId }).fetch();
19+
}
20+
1721
public get tableName(): string { return Tables.Users; }
1822
public get hasTimestamps(): boolean { return true; }
1923

src/api/repositories/UserRepository.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ export class UserRepository {
3535
return User.fetchById(id);
3636
}
3737

38+
/**
39+
* Retrieves one user entity of the database
40+
*
41+
* @static
42+
* @param {number} id of the user
43+
* @returns {Promise<User>}
44+
*/
45+
public static async findByUserId(userId: string): Promise<User> {
46+
return User.fetchByUserId(userId);
47+
}
48+
3849
/**
3950
* Creates a new user entity in the database and returns
4051
* the new created entity

src/api/requests/UserCreateRequest.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@ export class UserCreateRequest extends RequestBody {
2222
@IsEmail()
2323
email: string;
2424

25+
picture: string;
26+
27+
@IsNotEmpty()
28+
auth0UserId: string;
29+
2530
}
2631

src/api/requests/UserUpdateRequest.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export class UserUpdateRequest extends RequestBody {
2525
@IsEmail()
2626
email: string;
2727

28+
picture: string;
29+
30+
@IsNotEmpty()
31+
auth0UserId: string;
32+
2833
setFirstName(value: string): void {
2934
this.update('firstName', value);
3035
}

0 commit comments

Comments
 (0)