// Copyright (c) 2023 Sourcefuse Technologies // // This software is released under the MIT License. // https://opensource.org/licenses/MIT import {Constructor, inject, Provider} from '@loopback/context'; import {repository} from '@loopback/repository'; import {HttpErrors, Request} from '@loopback/rest'; import {verify} from 'jsonwebtoken'; import { AuthenticationBindings, EntityWithIdentifier, IAuthUser, VerifyFunction, } from 'loopback4-authentication'; import moment from 'moment'; import {RevokedTokenRepository} from '../../../repositories'; import {ILogger, LOGGER} from '../../logger-extension'; import {IAuthUserWithPermissions} from '../keys'; export class FacadesBearerTokenVerifyProvider implements Provider { constructor( @repository(RevokedTokenRepository) public revokedTokenRepository: RevokedTokenRepository, @inject(LOGGER.LOGGER_INJECT) private readonly logger: ILogger, @inject(AuthenticationBindings.USER_MODEL, {optional: true}) public authUserModel?: Constructor, ) {} /** * The function verifies a bearer token, checks for token revocation, expiration, and password * expiry, and returns the authenticated user. * @returns The `value()` function returns a BearerFn function that verifies a token. Inside the * function, it first checks if the token is revoked, then verifies the token using a JWT secret key. * If the token is valid, it checks for password expiry and returns either an instance of * `authUserModel` or the user object based on the availability of `authUserModel`. */ value(): VerifyFunction.BearerFn { return async (token: string, req?: Request) => { try { const isRevoked = await this.revokedTokenRepository.get(token); if (isRevoked?.token) { throw new HttpErrors.Unauthorized('TokenRevoked'); } } catch (error) { if (HttpErrors.HttpError.prototype.isPrototypeOf(error)) { throw error; } this.logger.error('Revoked token repository not available !'); } let user: IAuthUserWithPermissions; try { user = verify(token, process.env.JWT_SECRET as string, { issuer: process.env.JWT_ISSUER, algorithms: ['HS256'], }) as IAuthUserWithPermissions; } catch (error) { this.logger.error(JSON.stringify(error)); throw new HttpErrors.Unauthorized('TokenExpired'); } if ( user.passwordExpiryTime && moment().isSameOrAfter(moment(user.passwordExpiryTime)) ) { throw new HttpErrors.Unauthorized('PasswordExpiryError'); } if (this.authUserModel) { return new this.authUserModel(user); } else { return user; } }; } }