├── .gitignore ├── README.md ├── index.js ├── nodemon.json ├── ormconfig.json ├── package-lock.json ├── package.json ├── src ├── app.module.ts ├── configs │ ├── app.config.ts │ ├── index.ts │ └── swagger.config.ts ├── enums │ └── db-error-code.enum.ts ├── filters │ └── http-exception.filter.ts ├── main.ts ├── middlewares │ ├── index.ts │ ├── logger.middleware.ts │ └── oauth.middleware.ts └── oauth │ ├── clients.controller.ts │ ├── dtos │ └── oauth2-request.ts │ ├── models │ ├── access-token.ts │ ├── base.entity.ts │ ├── client.ts │ ├── index.ts │ ├── oauth2.ts │ └── user.ts │ ├── oauth.controller.ts │ ├── oauth.module.ts │ ├── oauth.service.ts │ └── users.controller.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # IDE 3 | /.idea 4 | /.awcache 5 | /.vscode 6 | 7 | # misc 8 | npm-debug.log 9 | 10 | # example 11 | /quick-start 12 | 13 | # tests 14 | /test 15 | /coverage 16 | /.nyc_output 17 | 18 | # dist 19 | /dist 20 | /node_modules/** 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oauth2-server 2 | Oauth2 server based on NestJS, which is a NodeJS framework built with TypeScript. 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | require('./src/main'); 3 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node ./index" 6 | } 7 | -------------------------------------------------------------------------------- /ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "postgres", 6 | "password": "jnppem", 7 | "database": "cgms", 8 | "synchronize": true, 9 | "logging": false, 10 | "entities": [ 11 | "src/**/models/*.ts" 12 | ] 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth2-server", 3 | "version": "1.0.0", 4 | "description": "Nest TypeScript starter repository", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "nodemon", 8 | "prestart:prod": "tsc", 9 | "start:prod": "node dist/main.js" 10 | }, 11 | "dependencies": { 12 | "@nestjs/common": "^4.6.4", 13 | "@nestjs/core": "^4.6.4", 14 | "@nestjs/microservices": "^4.6.4", 15 | "@nestjs/swagger": "^1.1.4", 16 | "@nestjs/testing": "^4.6.1", 17 | "@nestjs/typeorm": "^2.0.0", 18 | "@nestjs/websockets": "^4.5.8", 19 | "class-validator": "^0.8.1", 20 | "express-oauth-server": "^2.0.0", 21 | "pg": "^7.4.1", 22 | "redis": "^2.7.1", 23 | "reflect-metadata": "^0.1.12", 24 | "rxjs": "^5.5.6", 25 | "typeorm": "^0.1.15", 26 | "typescript": "^2.6.2" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^9.3.0", 30 | "nodemon": "^1.14.1", 31 | "ts-node": "^4.1.0" 32 | }, 33 | "main": "index.js", 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/nestjs/typescript-starter.git" 37 | }, 38 | "keywords": [ 39 | "Oauth2", 40 | "Authentication" 41 | ], 42 | "author": "Jerson Pena ", 43 | "bugs": { 44 | "url": "https://github.com/nestjs/typescript-starter/issues" 45 | }, 46 | "homepage": "https://github.com/nestjs/typescript-starter#readme" 47 | } 48 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import {MiddlewaresConsumer, Module, NestModule} from '@nestjs/common'; 2 | import {OAuthModule} from "./oauth/oauth.module"; 3 | import {TypeOrmModule} from "@nestjs/typeorm"; 4 | import {OAuthController} from "./oauth/oauth.controller"; 5 | import {LoggerMiddleware} from "./middlewares"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forRoot(),OAuthModule] 9 | }) 10 | export class ApplicationModule implements NestModule { 11 | 12 | configure(consumer: MiddlewaresConsumer): MiddlewaresConsumer | void { 13 | consumer.apply(LoggerMiddleware).forRoutes(OAuthController); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/configs/app.config.ts: -------------------------------------------------------------------------------- 1 | export const AppConfig: any = { 2 | server: { 3 | port: 80, 4 | basePath: '/api/v1' 5 | } 6 | }; -------------------------------------------------------------------------------- /src/configs/index.ts: -------------------------------------------------------------------------------- 1 | import {AppConfig} from "./app.config"; 2 | export {AppConfig}; -------------------------------------------------------------------------------- /src/configs/swagger.config.ts: -------------------------------------------------------------------------------- 1 | import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger"; 2 | import {AppConfig} from "./app.config"; 3 | 4 | export class SwaggerConfig { 5 | 6 | static set(app) { 7 | const options = new DocumentBuilder() 8 | .setTitle('Oauth2 Server') 9 | .setDescription('Oauth2 Server Documentation') 10 | .setVersion('1.0') 11 | .setContactEmail('jersonsw@outlook.com') 12 | .setSchemes('http') 13 | .addTag('Oauth') 14 | .setBasePath(AppConfig.server.basePath) 15 | .build(); 16 | const document = SwaggerModule.createDocument(app, options); 17 | SwaggerModule.setup('/api/v1/doc', app, document); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/enums/db-error-code.enum.ts: -------------------------------------------------------------------------------- 1 | export enum DbErrorCode { 2 | NOT_NULL_VIOLATION = 23502, 3 | UNIQUE_VIOLATION = 23505, 4 | CHECK_VIOLATION = 23514, 5 | STRING_DATA_RIGHT_TRUNCATION = 22001 6 | } 7 | -------------------------------------------------------------------------------- /src/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import {Catch, ExceptionFilter, HttpException} from "@nestjs/common"; 2 | 3 | @Catch(HttpException) 4 | export class HttpExceptionFilter implements ExceptionFilter { 5 | 6 | catch(exception: HttpException, response: any): any { 7 | response 8 | .status(exception.getStatus()) 9 | .json({message: exception.getResponse()['message']}); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {NestFactory} from '@nestjs/core'; 2 | import {ApplicationModule} from './app.module'; 3 | import {AppConfig} from "./configs"; 4 | import {HttpExceptionFilter} from "./filters/http-exception.filter"; 5 | import {SwaggerConfig} from "./configs/swagger.config"; 6 | import {ValidationPipe} from "@nestjs/common"; 7 | 8 | async function bootstrap() { 9 | const app = await NestFactory.create(ApplicationModule); 10 | app.useGlobalFilters(new HttpExceptionFilter()); 11 | app.setGlobalPrefix(AppConfig.server.basePath); 12 | SwaggerConfig.set(app); 13 | app.useGlobalPipes(new ValidationPipe()); 14 | await app.listen(AppConfig.server.port); 15 | } 16 | 17 | bootstrap().then(() => { 18 | console.log("Application started on port: " + AppConfig.server.port); 19 | }, () => { 20 | console.log("Unable to start the application."); 21 | }); 22 | -------------------------------------------------------------------------------- /src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | import {LoggerMiddleware} from "./logger.middleware"; 2 | import {OAuthMiddleware} from "./oauth.middleware"; 3 | 4 | export {LoggerMiddleware, OAuthMiddleware}; -------------------------------------------------------------------------------- /src/middlewares/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import {ExpressMiddleware, Middleware, NestMiddleware} from "@nestjs/common"; 2 | 3 | @Middleware() 4 | export class LoggerMiddleware implements NestMiddleware { 5 | 6 | async resolve(name:string): Promise { 7 | return async (req, res, next) => { 8 | console.log({headers: req.headers, body: req.body}); 9 | next(); 10 | }; 11 | 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/middlewares/oauth.middleware.ts: -------------------------------------------------------------------------------- 1 | import {ExpressMiddleware, Middleware, NestMiddleware} from "@nestjs/common"; 2 | import OAuth2Server = require("oauth2-server"); 3 | import {oauth2Model} from "../oauth/models"; 4 | 5 | const oauth2Server = new OAuth2Server({ 6 | model: oauth2Model 7 | }); 8 | 9 | @Middleware() 10 | export class OAuthMiddleware implements NestMiddleware { 11 | 12 | async resolve(authenticateOptions?: any): Promise { 13 | return async (req, res, next) => { 14 | const options: undefined | any = authenticateOptions || {}; 15 | const request = new OAuth2Server.Request(req); 16 | const response = new OAuth2Server.Response(res); 17 | console.log(request.headers); 18 | try { 19 | const token = await oauth2Server.authenticate(request, response, options); 20 | req.user = token; 21 | next(); 22 | } catch (err) { 23 | res.status(err.code || 500).json(err); 24 | } 25 | }; 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/oauth/clients.controller.ts: -------------------------------------------------------------------------------- 1 | import {Body, Controller, Get, Param, Post, Put} from "@nestjs/common"; 2 | import {Client} from "./models"; 3 | import {ApiResponse} from "@nestjs/swagger"; 4 | import {OAuthService} from "./oauth.service"; 5 | 6 | @Controller('/oauth/clients') 7 | export class ClientsController { 8 | 9 | constructor(private readonly oauthService: OAuthService) {} 10 | 11 | @Post('/') 12 | @ApiResponse({status: 201, description: 'The client was created successfully'}) 13 | @ApiResponse({status: 400, description: 'Invalid client payload'}) 14 | @ApiResponse({status: 409, description: 'The client already exists'}) 15 | @ApiResponse({status: 500, description: 'Internal server error'}) 16 | @ApiResponse({status: 401, description: 'Unauthenticated'}) 17 | public createClient(@Body() client: Client): Promise { 18 | return this.oauthService.createClient(client); 19 | } 20 | 21 | 22 | @Get('/:id') 23 | @ApiResponse({status: 200, description: 'The client was returned successfully'}) 24 | @ApiResponse({status: 404, description: 'The client was not found'}) 25 | @ApiResponse({status: 500, description: 'Internal server error'}) 26 | @ApiResponse({status: 401, description: 'Unauthenticated'}) 27 | public getClientById(@Param('id') id: string): Promise { 28 | return this.oauthService.getClientById(id); 29 | } 30 | 31 | 32 | @Put('/:id') 33 | @ApiResponse({status: 200, description: 'The client was updated successfully'}) 34 | @ApiResponse({status: 404, description: 'The client was not found'}) 35 | @ApiResponse({status: 500, description: 'Internal server error'}) 36 | @ApiResponse({status: 401, description: 'Unauthenticated'}) 37 | public updateClient(@Param('id') id: string, @Body('client') client: Client): Promise { 38 | client.id = id; 39 | return this.oauthService.updateClient(client); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/oauth/dtos/oauth2-request.ts: -------------------------------------------------------------------------------- 1 | import {ApiModelProperty} from "@nestjs/swagger"; 2 | 3 | export class OAuth2Request { 4 | @ApiModelProperty({type:String,description: 'Email address of the resource owner',required: true}) 5 | email: string; 6 | 7 | @ApiModelProperty({type:String,description: 'Password of the resource owner',required: true}) 8 | password: string; 9 | 10 | @ApiModelProperty({type:String,description: 'Grant type that we are using for authenticate the user',required: true}) 11 | grant_type: string; 12 | } -------------------------------------------------------------------------------- /src/oauth/models/access-token.ts: -------------------------------------------------------------------------------- 1 | import {Column, Entity, JoinColumn, OneToOne, PrimaryColumn} from "typeorm"; 2 | import {User} from "./user"; 3 | import {Client} from "./client"; 4 | 5 | @Entity({schema: 'auth', name: 'tokens'}) 6 | export class AccessToken { 7 | 8 | @PrimaryColumn({ 9 | name: 'access_token', 10 | primary: true, 11 | nullable: false, 12 | length: 80, 13 | default: () => "gen_random_uuid()" 14 | }) 15 | accessToken: string; 16 | 17 | @Column({ 18 | name: 'refresh_token', 19 | unique: true, 20 | nullable: false, 21 | length: 80, 22 | default: () => "gen_random_uuid()" 23 | }) 24 | refreshToken: string; 25 | 26 | @Column('timestamp', {name: 'access_token_expires_at', nullable: false}) 27 | accessTokenExpiresAt: Date; 28 | 29 | @Column('timestamp', {name: 'refresh_token_expires_at', nullable: false}) 30 | refreshTokenExpiresAt: Date; 31 | 32 | @OneToOne(type => Client) 33 | @JoinColumn({name: 'client_id', referencedColumnName: 'id'}) 34 | client: Client; 35 | 36 | @Column({name: 'client_id', nullable: false, length: 80}) 37 | clientId: string; 38 | 39 | @OneToOne(type => User) 40 | @JoinColumn({name: 'user_id', referencedColumnName: 'id'}) 41 | user: User; 42 | 43 | @Column({name: 'user_id', nullable: false, length: 80}) 44 | userId: string; 45 | 46 | @Column({nullable: false, length: 500, default: 'read,write'}) 47 | scope: string; 48 | 49 | 50 | @Column('timestamp', {name: 'created_on', nullable: false, default: () => 'now()'}) 51 | createdOn: Date; 52 | 53 | @Column({name: 'created_from', nullable: true}) 54 | createdFrom: string; 55 | 56 | } -------------------------------------------------------------------------------- /src/oauth/models/base.entity.ts: -------------------------------------------------------------------------------- 1 | import {Column} from "typeorm"; 2 | 3 | export abstract class BaseEntity{ 4 | 5 | @Column('timestamp', {name: 'created_on', nullable: false, default: ()=> 'now()'}) 6 | createdOn: Date; 7 | 8 | @Column('timestamp', {name: 'last_update', nullable: true}) 9 | lastUpdate: Date; 10 | 11 | @Column('timestamp', {name: 'deactivated_on', nullable: true}) 12 | deactivatedOn: Date; 13 | 14 | @Column({name: 'created_from', nullable: true, length: 54, default: '0.0.0.0'}) 15 | createdFrom: string; 16 | } -------------------------------------------------------------------------------- /src/oauth/models/client.ts: -------------------------------------------------------------------------------- 1 | import {Column, Entity, PrimaryColumn} from "typeorm"; 2 | import {ApiModelProperty} from "@nestjs/swagger"; 3 | import {IsUrl, Length} from "class-validator"; 4 | 5 | 6 | @Entity({schema: 'auth', name: 'clients'}) 7 | export class Client { 8 | 9 | @PrimaryColumn({name: 'id', length: 80, nullable: false, default: () => "gen_random_uuid()"}) 10 | id: string; 11 | 12 | @Column({name: 'secret', length: 80, nullable: false, default: () => "gen_random_uuid()"}) 13 | secret: string; 14 | 15 | @ApiModelProperty({type: String, description: 'Name of the client(application)', required: true}) 16 | @Column({name: 'name', length: 20, nullable: false, unique: true}) 17 | @Length(3, 20, { 18 | message: 'The client name must contain between $constraint1 and $constraint2 characters.$value' 19 | }) 20 | name: string; 21 | 22 | @Column({ 23 | name: 'grants', 24 | type: 'simple-array', 25 | isArray: true, 26 | nullable: false, 27 | default: '{authorization_code,password}' 28 | }) 29 | grants: string[]; 30 | 31 | @ApiModelProperty({type: String, description: 'URL when the user will be redirected ', required: true}) 32 | @Column({name: 'redirect_uris', type: 'simple-array', isArray: true, nullable: false}) 33 | @IsUrl({ 34 | require_protocol: true, 35 | require_valid_protocol: true, 36 | allow_underscores: true 37 | }, {each: true, message: '"$value" is not a valid url'}) 38 | redirectUris: string[]; 39 | 40 | @Column({length: 500, nullable: false, default: 'read,write'}) 41 | scope: string; 42 | 43 | @Column({name: 'access_token_lifetime', nullable: false, default: 3600}) 44 | accessTokenLifetime: number; 45 | 46 | @Column({name: 'refresh_token_lifetime', nullable: false, default: 7200}) 47 | refreshTokenLifetime: number; 48 | 49 | @Column({type: 'timestamp', name: 'created_on', nullable: false, default: () => 'now()'}) 50 | createdOn: Date; 51 | 52 | } -------------------------------------------------------------------------------- /src/oauth/models/index.ts: -------------------------------------------------------------------------------- 1 | import {AccessToken} from "./access-token"; 2 | import {Client} from "./client"; 3 | import {User} from "./user"; 4 | import {oauth2Model} from "./oauth2" 5 | 6 | export {AccessToken, Client, User, oauth2Model}; 7 | -------------------------------------------------------------------------------- /src/oauth/models/oauth2.ts: -------------------------------------------------------------------------------- 1 | import OAuth2Server = require("oauth2-server"); 2 | import {getRepository} from "typeorm"; 3 | import {Client} from "./client"; 4 | import {User, User as UserEntity} from "./user"; 5 | import {AccessToken} from "./access-token"; 6 | 7 | export const oauth2Model = { 8 | 9 | getClient: async (clientId: string, clientSecret: string): Promise => { 10 | let client: Client = await getRepository(Client) 11 | .createQueryBuilder('c') 12 | .where('c.id = :clientId AND c.secret = :clientSecret', { 13 | clientId: clientId, 14 | clientSecret: clientSecret 15 | }).getOne(); 16 | if (!client) { 17 | return Promise.reject("The combination of client id and client secret are incorrect"); 18 | } 19 | return Promise.resolve(client); 20 | 21 | }, 22 | saveToken: async (token: AccessToken, client: Client, user: User): Promise => { 23 | token.client = client; 24 | token.clientId = client.id; 25 | token.user = user; 26 | token.userId = user.id; 27 | token.createdFrom = ''; 28 | let bearer = await getRepository(AccessToken).save(token); 29 | if (!bearer) { 30 | return Promise.reject("Unable to create the token"); 31 | } 32 | return Promise.resolve(bearer); 33 | }, 34 | 35 | getAccessToken: async (accessToken: string): Promise => { 36 | let token: AccessToken = await getRepository(AccessToken) 37 | .createQueryBuilder('at') 38 | .where('at.access_token = :accessToken', {accessToken: accessToken}).getOne(); 39 | if (!token) { 40 | return Promise.reject("Token not found"); 41 | } 42 | return Promise.resolve(token); 43 | }, 44 | 45 | verifyScope: async (token: OAuth2Server.Token, scope: string): Promise => { 46 | return true; 47 | }, 48 | 49 | getUser: async (email: string, password: string): Promise => { 50 | let user: UserEntity = await getRepository(UserEntity).createQueryBuilder('u').where('u.email = :email AND u.password = :password', { 51 | email: email, 52 | password: password 53 | }).getOne(); 54 | if (!user) { 55 | return Promise.reject("User not found"); 56 | } 57 | return Promise.resolve(user); 58 | } 59 | }; -------------------------------------------------------------------------------- /src/oauth/models/user.ts: -------------------------------------------------------------------------------- 1 | import {Column, Entity, PrimaryColumn} from "typeorm"; 2 | import {BaseEntity} from "./base.entity"; 3 | import {IsEmail, Length, Matches} from "class-validator"; 4 | import {ApiModelProperty} from "@nestjs/swagger"; 5 | 6 | @Entity({schema: 'auth', name: 'users'}) 7 | export class User extends BaseEntity { 8 | 9 | @PrimaryColumn({nullable: false, primary: true, length: 80, default: () => "gen_random_uuid()"}) 10 | id: string; 11 | 12 | @ApiModelProperty({type: String, description: 'User email', required: true}) 13 | @IsEmail({allow_display_name: true}, { 14 | message: '$value is not a valid email' 15 | }) 16 | @Column({unique: true, nullable: false, length: 30}) 17 | email: string; 18 | 19 | @ApiModelProperty({type: String, description: 'First Name of the user', required: true}) 20 | @Length(3, 20, {message: 'The First Name must have between 3 and 20 characters'}) 21 | @Column({name: 'first_name', nullable: false, length: 20}) 22 | firstName: string; 23 | 24 | @ApiModelProperty({type: String, description: 'Last Name of the user', required: true}) 25 | @Length(3, 20, {message: 'The Last Name must have between 3 and 20 characters'}) 26 | @Column({name: 'last_name', nullable: false, length: 20}) 27 | lastName: string; 28 | 29 | @ApiModelProperty({type: String, description: 'User password', required: true}) 30 | @Matches(/^[^\s\n\t\r]{6,16}$/, 'gi', { 31 | message: 'The password must contain between 6 and 16 characters' 32 | }) 33 | @Column({nullable: false, length: 16}) 34 | password: string; 35 | 36 | @Column({nullable: false, length: 500, default: 'read,write'}) 37 | scope: string; 38 | 39 | @Column({name: 'email_verified', nullable: false, default: false}) 40 | emailVerified: boolean; 41 | 42 | @Column({nullable: false, length: 1, default: 'A'}) 43 | status: string; 44 | } -------------------------------------------------------------------------------- /src/oauth/oauth.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Post, Req, Res} from "@nestjs/common"; 2 | import {OAuthService} from "./oauth.service"; 3 | import {oauth2Model} from "./models/oauth2"; 4 | import * as OAuth2Server from "oauth2-server"; 5 | import {AccessToken} from "./models"; 6 | 7 | const oauth2Server = new OAuth2Server({ 8 | model: oauth2Model 9 | }); 10 | 11 | @Controller('/oauth/auth') 12 | export class OAuthController { 13 | 14 | constructor(private readonly oauthService: OAuthService) { 15 | } 16 | 17 | @Post("/token") 18 | accessToken(@Req() req, @Res() res) { 19 | const request = new OAuth2Server.Request(req); 20 | const response = new OAuth2Server.Response(res); 21 | oauth2Server.token(request, response) 22 | .then((token: AccessToken) => { 23 | res.json(token); 24 | }).catch((err: any) => { 25 | //console.log(err); 26 | res.status(err.code || 500).json(err); 27 | }); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/oauth/oauth.module.ts: -------------------------------------------------------------------------------- 1 | import {Module} from '@nestjs/common'; 2 | import {OAuthController} from "./oauth.controller"; 3 | import {TypeOrmModule} from "@nestjs/typeorm"; 4 | import {OAuthService} from "./oauth.service"; 5 | import {User, Client} from "./models"; 6 | import {UsersController} from "./users.controller"; 7 | import {ClientsController} from "./clients.controller"; 8 | 9 | 10 | 11 | @Module({ 12 | imports: [TypeOrmModule.forFeature([User, Client])], 13 | controllers: [OAuthController, UsersController, ClientsController], 14 | components: [OAuthService] 15 | }) 16 | export class OAuthModule { 17 | } 18 | -------------------------------------------------------------------------------- /src/oauth/oauth.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, Component, ConflictException, InternalServerErrorException, 3 | NotFoundException 4 | } from "@nestjs/common"; 5 | import {InjectRepository} from "@nestjs/typeorm"; 6 | import {Repository} from "typeorm"; 7 | import {Client, User} from "./models"; 8 | import {DbErrorCode} from "../enums/db-error-code.enum"; 9 | 10 | @Component() 11 | export class OAuthService { 12 | 13 | constructor(@InjectRepository(Client) private readonly clientsRepository: Repository, @InjectRepository(User) private readonly usersRepository: Repository) { 14 | } 15 | 16 | async createClient(client: Client): Promise { 17 | 18 | try { 19 | let c: Client = await this.clientsRepository.save(client); 20 | return Promise.resolve(c); 21 | } catch (e) { 22 | if (e.code) { 23 | if (DbErrorCode.UNIQUE_VIOLATION == e.code) { 24 | throw new BadRequestException("Sorry, already exists an client registered with the same name."); 25 | } else if (DbErrorCode.NOT_NULL_VIOLATION == e.code || DbErrorCode.CHECK_VIOLATION == e.code || DbErrorCode.STRING_DATA_RIGHT_TRUNCATION == e.code) { 26 | throw new BadRequestException("Unable to create the client due to invalid client data."); 27 | } 28 | } 29 | throw new InternalServerErrorException("An unexpected error has occurred trying to create the client."); 30 | } 31 | } 32 | 33 | async getClientById(id: string): Promise { 34 | let client: Client = await this.clientsRepository.findOneById(id); 35 | if (!client) { 36 | throw new NotFoundException("The client was not found"); 37 | } 38 | return client; 39 | } 40 | 41 | async updateClient(client: Client): Promise { 42 | let updatedClient: Client = await this.clientsRepository.save(client); 43 | if (!updatedClient) { 44 | throw new NotFoundException("The client was not found"); 45 | } 46 | return updatedClient; 47 | } 48 | 49 | 50 | async createUser(user: User): Promise { 51 | 52 | try { 53 | let u: User = await this.usersRepository.save(user); 54 | return Promise.resolve(u); 55 | } catch (e) { 56 | console.log(e); 57 | if (e.code) { 58 | if (DbErrorCode.UNIQUE_VIOLATION == e.code) { 59 | throw new BadRequestException("Sorry, already exists an user registered with the same name."); 60 | } else if (DbErrorCode.NOT_NULL_VIOLATION == e.code || DbErrorCode.CHECK_VIOLATION == e.code || DbErrorCode.STRING_DATA_RIGHT_TRUNCATION == e.code) { 61 | console.log(e.code); 62 | throw new BadRequestException("Unable to create the user due to invalid user data."); 63 | } 64 | } 65 | throw new InternalServerErrorException("An unexpected error has occurred trying to create the user."); 66 | } 67 | } 68 | 69 | async getUserById(id: string): Promise { 70 | let user: User = await this.usersRepository.findOneById(id); 71 | if (!user) { 72 | throw new NotFoundException("The user was not found"); 73 | } 74 | return user; 75 | } 76 | 77 | async updateUser(user: User): Promise { 78 | let updatedUser: User = await this.usersRepository.save(user); 79 | if (!updatedUser) { 80 | throw new ConflictException("Unable to update the user"); 81 | } 82 | return updatedUser; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/oauth/users.controller.ts: -------------------------------------------------------------------------------- 1 | import {Body, Controller, Get, Param, Post, Put, Req} from "@nestjs/common"; 2 | import {ApiResponse} from "@nestjs/swagger"; 3 | import {User} from "./models"; 4 | import {OAuthService} from "./oauth.service"; 5 | 6 | @Controller('/oauth/users') 7 | export class UsersController { 8 | 9 | constructor(private readonly oauthService: OAuthService) {} 10 | 11 | @Post('/') 12 | @ApiResponse({status: 201, description: 'The user was created successfully'}) 13 | @ApiResponse({status: 400, description: 'Invalid user payload'}) 14 | @ApiResponse({status: 409, description: 'The user already exists'}) 15 | @ApiResponse({status: 500, description: 'Internal server error'}) 16 | @ApiResponse({status: 401, description: 'Unauthenticated'}) 17 | public createUser(@Body() user: User, @Req() req): Promise { 18 | user.createdFrom = req.headers['x-forwarded-for'] || req.connection.remoteAddress; 19 | return this.oauthService.createUser(user); 20 | } 21 | 22 | @Get('/:id') 23 | @ApiResponse({status: 200, description: 'The user was returned successfully'}) 24 | @ApiResponse({status: 404, description: 'The user was not found'}) 25 | @ApiResponse({status: 500, description: 'Internal server error'}) 26 | @ApiResponse({status: 401, description: 'Unauthenticated'}) 27 | public getUserById(@Param('id') id: string): Promise { 28 | return this.oauthService.getUserById(id); 29 | } 30 | 31 | 32 | @Put('/:id') 33 | @ApiResponse({status: 200, description: 'The user was updated successfully'}) 34 | @ApiResponse({status: 404, description: 'The user was not found'}) 35 | @ApiResponse({status: 500, description: 'Internal server error'}) 36 | @ApiResponse({status: 401, description: 'Unauthenticated'}) 37 | public updateUser(@Param('id') id: string, @Body('user') user: User): Promise { 38 | user.id = id; 39 | return this.oauthService.updateUser(user); 40 | } 41 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": false, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es6", 11 | "sourceMap": true, 12 | "allowJs": true, 13 | "outDir": "./dist" 14 | }, 15 | "include": [ 16 | "src/**/*" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | "**/*.spec.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": { 7 | "no-unused-expression": true 8 | }, 9 | "rules": { 10 | "eofline": false, 11 | "quotemark": [ 12 | true, 13 | "single" 14 | ], 15 | "indent": false, 16 | "member-access": [ 17 | false 18 | ], 19 | "ordered-imports": [ 20 | false 21 | ], 22 | "max-line-length": [ 23 | 150 24 | ], 25 | "member-ordering": [ 26 | false 27 | ], 28 | "curly": false, 29 | "interface-name": [ 30 | false 31 | ], 32 | "array-type": [ 33 | false 34 | ], 35 | "no-empty-interface": false, 36 | "no-empty": false, 37 | "arrow-parens": false, 38 | "object-literal-sort-keys": false, 39 | "no-unused-expression": false, 40 | "max-classes-per-file": [ 41 | false 42 | ], 43 | "variable-name": [ 44 | false 45 | ], 46 | "one-line": [ 47 | false 48 | ], 49 | "one-variable-per-declaration": [ 50 | false 51 | ] 52 | }, 53 | "rulesDirectory": [] 54 | } --------------------------------------------------------------------------------