From d05307b9d437fea159eccf54ade474def0d596af Mon Sep 17 00:00:00 2001 From: Madaky <17172989+madaky@users.noreply.github.com> Date: Thu, 2 Jul 2020 03:45:12 +0530 Subject: [PATCH] feat: implemented awthentication-jwt refreshtoken services --- .../acceptance/jwt.component.test.ts | 1 - .../src/__tests__/fixtures/application.ts | 4 +- .../fixtures/controllers/user.controller.ts | 28 ++-- .../src/__tests__/unit/jwt.service.ts | 15 +-- .../src/interceptors/index.ts | 3 - .../refresh-token-generate.interceptor.ts | 84 ------------ .../refresh-token-grant.interceptor.ts | 97 -------------- .../src/jwt-authentication-component.ts | 35 ++--- extensions/authentication-jwt/src/keys.ts | 25 +++- .../repositories/refresh-token.repository.ts | 4 +- .../authentication-jwt/src/services/index.ts | 1 + .../src/services/jwt.service.ts | 7 +- .../src/services/refreshtoken.service.ts | 122 ++++++++++++++++++ .../src/services/user.service.ts | 2 +- 14 files changed, 181 insertions(+), 247 deletions(-) delete mode 100644 extensions/authentication-jwt/src/interceptors/index.ts delete mode 100644 extensions/authentication-jwt/src/interceptors/refresh-token-generate.interceptor.ts delete mode 100644 extensions/authentication-jwt/src/interceptors/refresh-token-grant.interceptor.ts create mode 100644 extensions/authentication-jwt/src/services/refreshtoken.service.ts diff --git a/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts b/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts index 5ff1bc376d28..c81a64f72e43 100644 --- a/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts +++ b/extensions/authentication-jwt/src/__tests__/acceptance/jwt.component.test.ts @@ -15,7 +15,6 @@ import {UserServiceBindings} from '../..'; import {OPERATION_SECURITY_SPEC, SECURITY_SCHEME_SPEC} from '../../'; import {UserRepository} from '../../repositories'; import {TestApplication} from '../fixtures/application'; -import {RefreshTokenBindings} from '../../keys'; describe('jwt authentication', () => { let app: TestApplication; diff --git a/extensions/authentication-jwt/src/__tests__/fixtures/application.ts b/extensions/authentication-jwt/src/__tests__/fixtures/application.ts index 9502261b49e7..7044cf2bf6b8 100644 --- a/extensions/authentication-jwt/src/__tests__/fixtures/application.ts +++ b/extensions/authentication-jwt/src/__tests__/fixtures/application.ts @@ -14,7 +14,7 @@ import path from 'path'; import { JWTAuthenticationComponent, UserServiceBindings, - RefreshTokenBindings, + RefreshTokenServiceBindings, } from '../../'; import {DbDataSource} from './datasources/db.datasource'; import {MySequence} from './sequence'; @@ -39,7 +39,7 @@ export class TestApplication extends BootMixin( // Bind datasource this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME); //Bind datasource for refreshtoken table - this.dataSource(DbDataSource, RefreshTokenBindings.DATASOURCE_NAME); + this.dataSource(DbDataSource, RefreshTokenServiceBindings.DATASOURCE_NAME); this.component(RestExplorerComponent); this.projectRoot = __dirname; diff --git a/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts b/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts index 6c45aeb0bfde..29e4c9602337 100644 --- a/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts +++ b/extensions/authentication-jwt/src/__tests__/fixtures/controllers/user.controller.ts @@ -8,7 +8,7 @@ import { TokenService, UserService, } from '@loopback/authentication'; -import {inject, intercept} from '@loopback/core'; +import {inject} from '@loopback/core'; import {get, post, requestBody} from '@loopback/rest'; import {SecurityBindings, securityId, UserProfile} from '@loopback/security'; import { @@ -18,11 +18,13 @@ import { TokenObject, RefreshGrantRequestBody, RefreshGrant, + RefreshTokenServiceBindings, } from '../../../'; import {model, property} from '@loopback/repository'; import {genSalt, hash} from 'bcryptjs'; import {UserRepository} from '../../../repositories'; import {Credentials} from '../../../services/user.service'; +import {RefreshTokenService} from '../../../keys'; const CredentialsSchema = { type: 'object', @@ -66,6 +68,8 @@ export class UserController { private user: UserProfile, @inject(UserServiceBindings.USER_REPOSITORY) public userRepository: UserRepository, + @inject(RefreshTokenServiceBindings.REFRESH_TOKEN_SERVICE) + public refreshService: RefreshTokenService, ) {} @post('/users/signup', { @@ -172,7 +176,6 @@ export class UserController { }, }, }) - @intercept('refresh-token-generate') async refreshLogin( @requestBody(CredentialsRequestBody) credentials: Credentials, ): Promise { @@ -182,11 +185,12 @@ export class UserController { const userProfile: UserProfile = this.userService.convertToUserProfile( user, ); - // create a JSON Web Token based on the user profile - const token = { - accessToken: await this.jwtService.generateToken(userProfile), - }; - return token; + const accessToken = await this.jwtService.generateToken(userProfile); + const tokens = await this.refreshService.generateToken( + userProfile, + accessToken, + ); + return tokens; } @post('/refresh', { @@ -198,8 +202,8 @@ export class UserController { schema: { type: 'object', properties: { - refreshToken: { - type: 'string', + accessToken: { + type: 'object', }, }, }, @@ -208,11 +212,9 @@ export class UserController { }, }, }) - @intercept('refresh-token-grant') async refresh( @requestBody(RefreshGrantRequestBody) refreshGrant: RefreshGrant, - ): Promise<{token: string}> { - const token = ''; - return {token}; + ): Promise { + return this.refreshService.refreshToken(refreshGrant.refreshToken); } } diff --git a/extensions/authentication-jwt/src/__tests__/unit/jwt.service.ts b/extensions/authentication-jwt/src/__tests__/unit/jwt.service.ts index 65fc807f8cde..901b5926a8ee 100644 --- a/extensions/authentication-jwt/src/__tests__/unit/jwt.service.ts +++ b/extensions/authentication-jwt/src/__tests__/unit/jwt.service.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {HttpErrors} from '@loopback/rest'; -import {securityId, UserProfile} from '@loopback/security'; +import {securityId} from '@loopback/security'; import {expect} from '@loopback/testlab'; import {JWTService} from '../../'; @@ -19,17 +19,11 @@ describe('token service', () => { id: '1', name: 'test', }; - type Setter = (value?: T) => void; + const TOKEN_SECRET_VALUE = 'myjwts3cr3t'; const TOKEN_EXPIRES_IN_VALUE = '60'; - const setCurrentUser: Setter = userProfile => { - return userProfile; - }; - const jwtService = new JWTService( - TOKEN_SECRET_VALUE, - TOKEN_EXPIRES_IN_VALUE, - setCurrentUser, - ); + + const jwtService = new JWTService(TOKEN_SECRET_VALUE, TOKEN_EXPIRES_IN_VALUE); it('token service generateToken() succeeds', async () => { const token = await jwtService.generateToken(USER_PROFILE); @@ -39,7 +33,6 @@ describe('token service', () => { it('token service verifyToken() succeeds', async () => { const token = await jwtService.generateToken(USER_PROFILE); const userProfileFromToken = await jwtService.verifyToken(token); - expect(userProfileFromToken).to.deepEqual(DECODED_USER_PROFILE); }); diff --git a/extensions/authentication-jwt/src/interceptors/index.ts b/extensions/authentication-jwt/src/interceptors/index.ts deleted file mode 100644 index e01b37c74a75..000000000000 --- a/extensions/authentication-jwt/src/interceptors/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './refresh-token-generate.interceptor'; - -export * from './refresh-token-grant.interceptor'; diff --git a/extensions/authentication-jwt/src/interceptors/refresh-token-generate.interceptor.ts b/extensions/authentication-jwt/src/interceptors/refresh-token-generate.interceptor.ts deleted file mode 100644 index 0bc5fc772fe3..000000000000 --- a/extensions/authentication-jwt/src/interceptors/refresh-token-generate.interceptor.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Getter, - inject, - Interceptor, - InvocationContext, - InvocationResult, - Provider, - uuid, - ValueOrPromise, -} from '@loopback/context'; -import {repository} from '@loopback/repository'; -import {SecurityBindings, UserProfile} from '@loopback/security'; -import {promisify} from 'util'; -import {RefreshTokenRepository} from '../repositories'; -import {RefreshTokenInterceptorBindings} from '../keys'; -import {HttpErrors} from '@loopback/rest'; - -/** - * This class will be bound to the application as an `Interceptor` during - * `boot` - */ -const jwt = require('jsonwebtoken'); -const signAsync = promisify(jwt.sign); -export class RefreshTokenGenerateInterceptor implements Provider { - constructor( - @inject(RefreshTokenInterceptorBindings.REFRESH_SECRET) - private refreshSecret: string, - @inject(RefreshTokenInterceptorBindings.REFRESH_EXPIRES_IN) - private refreshExpiresIn: string, - @inject(RefreshTokenInterceptorBindings.REFRESH_ISSURE) - private refreshIssure: string, - @repository(RefreshTokenRepository) - public refreshTokenRepository: RefreshTokenRepository, - @inject.getter(SecurityBindings.USER, {optional: true}) - private getCurrentUser: Getter, - ) {} - - /** - * This method is used by LoopBack context to produce an interceptor function - * for the binding. - * - * @returns An interceptor function - */ - value() { - return this.intercept.bind(this); - } - - /** - * The logic to intercept an invocation - * @param invocationCtx - Invocation context - * @param next - A function to invoke next interceptor or the target method - */ - async intercept( - invocationCtx: InvocationContext, - next: () => ValueOrPromise, - ) { - try { - // Add pre-invocation logic here - let result = await next(); - // Add post-invocation logic here - const currentUser = await this.getCurrentUser(); - const data = { - token: uuid(), - }; - const refreshToken = await signAsync(data, this.refreshSecret, { - expiresIn: Number(this.refreshExpiresIn), - issuer: this.refreshIssure, - }); - result = Object.assign(result, { - refreshToken: refreshToken, - }); - await this.refreshTokenRepository.create({ - userId: currentUser.id, - refreshToken: result.refreshToken, - }); - return result; - } catch (error) { - // Add error handling logic here - throw new HttpErrors.Unauthorized( - `Error verifying token : ${error.message}`, - ); - } - } -} diff --git a/extensions/authentication-jwt/src/interceptors/refresh-token-grant.interceptor.ts b/extensions/authentication-jwt/src/interceptors/refresh-token-grant.interceptor.ts deleted file mode 100644 index e6214920c15f..000000000000 --- a/extensions/authentication-jwt/src/interceptors/refresh-token-grant.interceptor.ts +++ /dev/null @@ -1,97 +0,0 @@ -import {TokenService} from '@loopback/authentication'; -import { - inject, - Interceptor, - InvocationContext, - InvocationResult, - Provider, - ValueOrPromise, -} from '@loopback/context'; -import {repository} from '@loopback/repository/'; -import {HttpErrors} from '@loopback/rest'; -import {UserProfile} from '@loopback/security'; -import {promisify} from 'util'; -import { - RefreshTokenInterceptorBindings, - UserServiceBindings, - TokenServiceBindings, -} from '../keys'; -import {MyUserService} from '../services'; -import {RefreshTokenRepository} from '../repositories'; - -const jwt = require('jsonwebtoken'); -const verifyAsync = promisify(jwt.verify); -/** - * This class will be bound to the application as an `Interceptor` during - * `boot` - */ -export class RefreshTokenGrantInterceptor implements Provider { - constructor( - @inject(RefreshTokenInterceptorBindings.REFRESH_SECRET) - private refreshSecret: string, - @inject(UserServiceBindings.USER_SERVICE) public userService: MyUserService, - @repository(RefreshTokenRepository) - public refreshTokenRepository: RefreshTokenRepository, - @inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService, - ) {} - /** - * This method is used by LoopBack context to produce an interceptor function - * for the binding. - * - * @returns An interceptor function - */ - value() { - return this.intercept.bind(this); - } - - /** - * The logic to intercept an invocation - * @param invocationCtx - Invocation context - * @param next - A function to invoke next interceptor or the target method - */ - async intercept( - invocationCtx: InvocationContext, - next: () => ValueOrPromise, - ) { - try { - // Add pre-invocation logic here - let result = await next(); - // Add post-invocation logic here - const refreshToken = invocationCtx.args[0].refreshToken; - - if (!refreshToken) { - throw new HttpErrors.Unauthorized( - `Error verifying token : 'refresh token' is null`, - ); - } - - await verifyAsync(refreshToken, this.refreshSecret); - const userRefreshData = await this.refreshTokenRepository.findOne({ - where: {refreshToken: refreshToken}, - }); - - if (!userRefreshData) { - throw new HttpErrors.Unauthorized( - `Error verifying token : Invalid Token`, - ); - } - const user = await this.userService.findUserById( - userRefreshData.userId.toString(), - ); - const userProfile: UserProfile = this.userService.convertToUserProfile( - user, - ); - // create a JSON Web Token based on the user profile - const token = await this.jwtService.generateToken(userProfile); - result = { - accessToken: token, - }; - return result; - } catch (error) { - // Add error handling logic here - throw new HttpErrors.Unauthorized( - `Error verifying token : ${error.message}`, - ); - } - } -} diff --git a/extensions/authentication-jwt/src/jwt-authentication-component.ts b/extensions/authentication-jwt/src/jwt-authentication-component.ts index 08d473bdbe76..8944c11286a1 100644 --- a/extensions/authentication-jwt/src/jwt-authentication-component.ts +++ b/extensions/authentication-jwt/src/jwt-authentication-component.ts @@ -16,23 +16,18 @@ import { TokenServiceBindings, TokenServiceConstants, UserServiceBindings, - RefreshTokenInterceptorConstants, - RefreshTokenInterceptorBindings, - RefreshTokenBindings, + RefreshTokenConstants, + RefreshTokenServiceBindings, } from './keys'; import { UserCredentialsRepository, UserRepository, RefreshTokenRepository, } from './repositories'; -import {MyUserService} from './services'; +import {MyUserService, RefreshtokenService} from './services'; import {JWTAuthenticationStrategy} from './services/jwt.auth.strategy'; import {JWTService} from './services/jwt.service'; import {SecuritySpecEnhancer} from './services/security.spec.enhancer'; -import { - RefreshTokenGenerateInterceptor, - RefreshTokenGrantInterceptor, -} from './interceptors'; export class JWTAuthenticationComponent implements Component { bindings: Binding[] = [ @@ -53,24 +48,22 @@ export class JWTAuthenticationComponent implements Component { ), createBindingFromClass(SecuritySpecEnhancer), ///refresh bindings - Binding.bind( - RefreshTokenInterceptorConstants.REFRESH_INTERCEPTOR_NAME, - ).toProvider(RefreshTokenGenerateInterceptor), - Binding.bind( - RefreshTokenInterceptorConstants.REFRESH_INTERCEPTOR_GRANT_TYPE, - ).toProvider(RefreshTokenGrantInterceptor), + Binding.bind(RefreshTokenServiceBindings.REFRESH_TOKEN_SERVICE).toClass( + RefreshtokenService, + ), + // Refresh token bindings - Binding.bind(RefreshTokenInterceptorBindings.REFRESH_SECRET).to( - RefreshTokenInterceptorConstants.REFRESH_SECRET_VALUE, + Binding.bind(RefreshTokenServiceBindings.REFRESH_SECRET).to( + RefreshTokenConstants.REFRESH_SECRET_VALUE, ), - Binding.bind(RefreshTokenInterceptorBindings.REFRESH_EXPIRES_IN).to( - RefreshTokenInterceptorConstants.REFRESH_EXPIRES_IN_VALUE, + Binding.bind(RefreshTokenServiceBindings.REFRESH_EXPIRES_IN).to( + RefreshTokenConstants.REFRESH_EXPIRES_IN_VALUE, ), - Binding.bind(RefreshTokenInterceptorBindings.REFRESH_ISSURE).to( - RefreshTokenInterceptorConstants.REFRESH_ISSURE_VALUE, + Binding.bind(RefreshTokenServiceBindings.REFRESH_ISSURE).to( + RefreshTokenConstants.REFRESH_ISSURE_VALUE, ), //refresh token repository binding - Binding.bind(RefreshTokenBindings.REFRESH_REPOSITORY).toClass( + Binding.bind(RefreshTokenServiceBindings.REFRESH_REPOSITORY).toClass( RefreshTokenRepository, ), ]; diff --git a/extensions/authentication-jwt/src/keys.ts b/extensions/authentication-jwt/src/keys.ts index f006a2014780..62ee60366a28 100644 --- a/extensions/authentication-jwt/src/keys.ts +++ b/extensions/authentication-jwt/src/keys.ts @@ -7,6 +7,7 @@ import {TokenService, UserService} from '@loopback/authentication'; import {BindingKey} from '@loopback/core'; import {User} from './models'; import {Credentials} from './services/user.service'; +import {UserProfile} from '@loopback/security'; export namespace TokenServiceConstants { export const TOKEN_SECRET_VALUE = 'myjwts3cr3t'; @@ -34,15 +35,16 @@ export namespace UserServiceBindings { export const USER_CREDENTIALS_REPOSITORY = 'repositories.UserCredentialsRepository'; } -export namespace RefreshTokenInterceptorConstants { - export const REFRESH_INTERCEPTOR_NAME = 'refresh-token-generate'; - export const REFRESH_INTERCEPTOR_GRANT_TYPE = 'refresh-token-grant'; +export namespace RefreshTokenConstants { export const REFRESH_SECRET_VALUE = 'r3fr35htok3n'; export const REFRESH_EXPIRES_IN_VALUE = '216000'; export const REFRESH_ISSURE_VALUE = 'loopback4'; } -export namespace RefreshTokenInterceptorBindings { +export namespace RefreshTokenServiceBindings { + export const REFRESH_TOKEN_SERVICE = BindingKey.create( + 'services.authentication.jwt.refresh.tokenservice', + ); export const REFRESH_SECRET = BindingKey.create( 'authentication.jwt.refresh.secret', ); @@ -52,9 +54,6 @@ export namespace RefreshTokenInterceptorBindings { export const REFRESH_ISSURE = BindingKey.create( 'authentication.jwt.referesh.issure', ); -} - -export namespace RefreshTokenBindings { export const DATASOURCE_NAME = 'refreshdb'; export const REFRESH_REPOSITORY = 'repositories.RefreshTokenRepository'; } @@ -85,3 +84,15 @@ export type TokenObject = { expiresIn?: string | undefined; refreshToken?: string | undefined; }; + +export interface RefreshTokenService { + /** + * Generate Token and return the Token Object + */ + generateToken(userProfile: UserProfile, token: string): Promise; + /** + * Verifies the validity of a token string and returns a new Token + * + */ + refreshToken(refreshToken: string): Promise; +} diff --git a/extensions/authentication-jwt/src/repositories/refresh-token.repository.ts b/extensions/authentication-jwt/src/repositories/refresh-token.repository.ts index fbd92176e265..b0f6db53c262 100644 --- a/extensions/authentication-jwt/src/repositories/refresh-token.repository.ts +++ b/extensions/authentication-jwt/src/repositories/refresh-token.repository.ts @@ -1,7 +1,7 @@ import {inject} from '@loopback/core'; import {DefaultCrudRepository, juggler} from '@loopback/repository'; import {RefreshToken, RefreshTokenRelations} from '../models'; -import {RefreshTokenBindings, UserServiceBindings} from '../keys'; +import {RefreshTokenServiceBindings} from '../keys'; export class RefreshTokenRepository extends DefaultCrudRepository< RefreshToken, @@ -9,7 +9,7 @@ export class RefreshTokenRepository extends DefaultCrudRepository< RefreshTokenRelations > { constructor( - @inject(`datasources.${RefreshTokenBindings.DATASOURCE_NAME}`) + @inject(`datasources.${RefreshTokenServiceBindings.DATASOURCE_NAME}`) dataSource: juggler.DataSource, ) { super(RefreshToken, dataSource); diff --git a/extensions/authentication-jwt/src/services/index.ts b/extensions/authentication-jwt/src/services/index.ts index c611cee39d63..0ec9d3079ee3 100644 --- a/extensions/authentication-jwt/src/services/index.ts +++ b/extensions/authentication-jwt/src/services/index.ts @@ -7,3 +7,4 @@ export * from './jwt.auth.strategy'; export * from './jwt.service'; export * from './security.spec.enhancer'; export * from './user.service'; +export * from './refreshtoken.service'; diff --git a/extensions/authentication-jwt/src/services/jwt.service.ts b/extensions/authentication-jwt/src/services/jwt.service.ts index 2928a3115ee9..599867a9ddc1 100644 --- a/extensions/authentication-jwt/src/services/jwt.service.ts +++ b/extensions/authentication-jwt/src/services/jwt.service.ts @@ -4,9 +4,9 @@ // License text available at https://opensource.org/licenses/MIT import {TokenService} from '@loopback/authentication'; -import {inject, Setter} from '@loopback/core'; +import {inject} from '@loopback/core'; import {HttpErrors} from '@loopback/rest'; -import {securityId, UserProfile, SecurityBindings} from '@loopback/security'; +import {securityId, UserProfile} from '@loopback/security'; import {promisify} from 'util'; import {TokenServiceBindings} from '../keys'; @@ -20,8 +20,6 @@ export class JWTService implements TokenService { private jwtSecret: string, @inject(TokenServiceBindings.TOKEN_EXPIRES_IN) private jwtExpiresIn: string, - @inject.setter(SecurityBindings.USER) - readonly setCurrentUser: Setter, ) {} async verifyToken(token: string): Promise { @@ -59,7 +57,6 @@ export class JWTService implements TokenService { 'Error generating token : userProfile is null', ); } - this.setCurrentUser(userProfile); const userInfoForToken = { id: userProfile[securityId], name: userProfile.name, diff --git a/extensions/authentication-jwt/src/services/refreshtoken.service.ts b/extensions/authentication-jwt/src/services/refreshtoken.service.ts new file mode 100644 index 000000000000..b002f4a99b50 --- /dev/null +++ b/extensions/authentication-jwt/src/services/refreshtoken.service.ts @@ -0,0 +1,122 @@ +import {bind, inject, BindingScope, uuid} from '@loopback/core'; +import { + RefreshTokenServiceBindings, + TokenObject, + UserServiceBindings, + TokenServiceBindings, +} from '../keys'; +import {repository} from '@loopback/repository'; +import {UserProfile, securityId} from '@loopback/security'; +import {promisify} from 'util'; +import {HttpErrors} from '@loopback/rest'; +import {TokenService} from '@loopback/authentication'; +import {RefreshTokenRepository} from '../repositories'; +import {MyUserService} from './user.service'; +import {RefreshToken} from '../models/refresh-token.model'; + +const jwt = require('jsonwebtoken'); +const signAsync = promisify(jwt.sign); +const verifyAsync = promisify(jwt.verify); + +@bind({scope: BindingScope.TRANSIENT}) +export class RefreshtokenService { + constructor( + @inject(RefreshTokenServiceBindings.REFRESH_SECRET) + private refreshSecret: string, + @inject(RefreshTokenServiceBindings.REFRESH_EXPIRES_IN) + private refreshExpiresIn: string, + @inject(RefreshTokenServiceBindings.REFRESH_ISSURE) + private refreshIssure: string, + @repository(RefreshTokenRepository) + public refreshTokenRepository: RefreshTokenRepository, + @inject(UserServiceBindings.USER_SERVICE) public userService: MyUserService, + @inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService, + ) {} + /* + * Generate Refresh Token + * UserProfile and roken generated by JWTService + */ + async generateToken( + userProfile: UserProfile, + token: string, + ): Promise { + const data = { + token: uuid(), + }; + const refreshToken = await signAsync(data, this.refreshSecret, { + expiresIn: Number(this.refreshExpiresIn), + issuer: this.refreshIssure, + }); + const result = { + accessToken: token, + refreshToken: refreshToken, + }; + await this.refreshTokenRepository.create({ + userId: userProfile[securityId], + refreshToken: result.refreshToken, + }); + return result; + } + /* + * Refresh JWT Token + * Issued refreshToken + */ + async refreshToken(refreshToken: string): Promise { + try { + if (!refreshToken) { + throw new HttpErrors.Unauthorized( + `Error verifying token : 'refresh token' is null`, + ); + } + + const userRefreshData = await this.verifyToken(refreshToken); + const user = await this.userService.findUserById( + userRefreshData.userId.toString(), + ); + const userProfile: UserProfile = this.userService.convertToUserProfile( + user, + ); + // create a JSON Web Token based on the user profile + const token = await this.jwtService.generateToken(userProfile); + + return { + accessToken: token, + }; + } catch (error) { + throw new HttpErrors.Unauthorized( + `Error verifying token : ${error.message}`, + ); + } + } + /* + * [TODO] test and endpoint + */ + async revokeToken(refreshToken: string) { + try { + await this.refreshTokenRepository.delete( + new RefreshToken({refreshToken: refreshToken}), + ); + } catch (e) { + // ignore + } + } + async verifyToken(refreshToken: string) { + try { + await verifyAsync(refreshToken, this.refreshSecret); + const userRefreshData = await this.refreshTokenRepository.findOne({ + where: {refreshToken: refreshToken}, + }); + + if (!userRefreshData) { + throw new HttpErrors.Unauthorized( + `Error verifying token : Invalid Token`, + ); + } + return userRefreshData; + } catch (error) { + throw new HttpErrors.Unauthorized( + `Error verifying token : ${error.message}`, + ); + } + } +} diff --git a/extensions/authentication-jwt/src/services/user.service.ts b/extensions/authentication-jwt/src/services/user.service.ts index 2a2be04ef871..00834dc868f0 100644 --- a/extensions/authentication-jwt/src/services/user.service.ts +++ b/extensions/authentication-jwt/src/services/user.service.ts @@ -8,7 +8,7 @@ import {repository} from '@loopback/repository'; import {HttpErrors} from '@loopback/rest'; import {securityId, UserProfile} from '@loopback/security'; import {compare} from 'bcryptjs'; -import {User} from '../models'; +import {User} from '../models/user.model'; import {UserRepository} from '../repositories'; /**