UNPKG

lbx-jwt

Version:

Provides JWT authentication for loopback applications. Includes storing roles inside tokens and handling refreshing. Built-in reuse detection.

159 lines 8.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RefreshTokenService = void 0; const tslib_1 = require("tslib"); const core_1 = require("@loopback/core"); const rest_1 = require("@loopback/rest"); const security_1 = require("@loopback/security"); const access_token_service_1 = require("./access-token.service"); const base_user_service_1 = require("./base-user.service"); const convert_ms_to_seconds_function_1 = require("./convert-ms-to-seconds.function"); const jwt_utilities_1 = require("../encapsulation/jwt.utilities"); const keys_1 = require("../keys"); const repositories_1 = require("../repositories"); /** * Handles refreshing of auth tokens. */ let RefreshTokenService = class RefreshTokenService { constructor(refreshTokenSecret, refreshTokenExpiresInMs, refreshIssuer, baseUserRepository, refreshTokenRepository, userService, accessTokenService, accessTokenExpiresInMs) { this.refreshTokenSecret = refreshTokenSecret; this.refreshTokenExpiresInMs = refreshTokenExpiresInMs; this.refreshIssuer = refreshIssuer; this.baseUserRepository = baseUserRepository; this.refreshTokenRepository = refreshTokenRepository; this.userService = userService; this.accessTokenService = accessTokenService; this.accessTokenExpiresInMs = accessTokenExpiresInMs; } /** * Generate a refresh token, bind it with the given user profile, then store them in backend. * @param userProfile - The user profile for which the token should be generated. * @param token - The access token of the user. * @returns An object containing the access and the refresh token. */ async generateToken(userProfile, token) { const payload = { baseUserId: userProfile[security_1.securityId], tokenId: (0, core_1.generateUniqueId)() }; const refreshTokenValue = await jwt_utilities_1.JwtUtilities.signAsync(payload, this.refreshTokenSecret, { expiresIn: (0, convert_ms_to_seconds_function_1.convertMsToSeconds)(this.refreshTokenExpiresInMs), issuer: 'api' }); const refreshToken = { baseUserId: userProfile[security_1.securityId], tokenValue: refreshTokenValue, familyId: (0, core_1.generateUniqueId)(), blacklisted: false, expirationDate: new Date(Date.now() + this.refreshTokenExpiresInMs) }; await this.refreshTokenRepository.create(refreshToken); return { accessToken: token, refreshToken: refreshTokenValue }; } /** * Refresh the access token bound with the given refresh token. * @param refreshTokenValue - The refresh token value used to refresh the token. * @param options - Additional options eg. Transaction. * @returns An object containing the new access and the new refresh token. */ async refreshToken(refreshTokenValue, options) { const refreshToken = await this.verifyToken(refreshTokenValue, options); if (refreshToken.blacklisted) { await this.refreshTokenRepository.deleteAll({ familyId: refreshToken.familyId }); throw new rest_1.HttpErrors.Unauthorized('The given refresh token has already been used.'); } const user = await this.baseUserRepository.findById(refreshToken.baseUserId, undefined, options); const userProfile = this.userService.convertToUserProfile(user); const newAccessTokenValue = await this.accessTokenService.generateToken(userProfile); if (!this.refreshTokenIsExpired(refreshToken)) { return { accessToken: newAccessTokenValue, refreshToken: refreshTokenValue }; } const newRefreshTokenPayload = { baseUserId: userProfile[security_1.securityId], tokenId: (0, core_1.generateUniqueId)() }; const newRefreshTokenValue = await jwt_utilities_1.JwtUtilities.signAsync(newRefreshTokenPayload, this.refreshTokenSecret, { expiresIn: (0, convert_ms_to_seconds_function_1.convertMsToSeconds)(this.refreshTokenExpiresInMs), issuer: this.refreshIssuer }); const refreshTokenData = { baseUserId: userProfile[security_1.securityId], tokenValue: newRefreshTokenValue, familyId: refreshToken.familyId, blacklisted: false, expirationDate: new Date(Date.now() + this.refreshTokenExpiresInMs) }; await this.refreshTokenRepository.create(refreshTokenData, options); await this.refreshTokenRepository.updateById(refreshToken.id, { blacklisted: true }, options); await this.refreshTokenRepository.deleteAll({ expirationDate: { lte: new Date() } }, options); return { accessToken: newAccessTokenValue, refreshToken: newRefreshTokenValue }; } refreshTokenIsExpired(refreshToken) { const createdAt = new Date(new Date(refreshToken.expirationDate).getTime() - this.refreshTokenExpiresInMs); const accessTokenLifeTimeInMs = Date.now() - createdAt.getTime(); return accessTokenLifeTimeInMs > this.accessTokenExpiresInMs; } /** * Revokes the family of the given token. * That means that every refresh token that comes from the same original login gets deleted. * @param refreshTokenValue - The value of the token that should be revoked. */ async revokeTokenFamily(refreshTokenValue) { try { const refreshToken = await this.refreshTokenRepository.findOne({ where: { tokenValue: refreshTokenValue } }); if (!refreshToken) { return; } await this.refreshTokenRepository.deleteAll({ familyId: refreshToken.familyId }); } catch (error) { // ignore } } /** * Verify the validity of a refresh token, and make sure it exists in backend. * @param refreshToken - The refresh token that should be verified. * @param options - Additional options eg. Transaction. * @returns The found refresh token with its relations or an error. */ async verifyToken(refreshToken, options) { try { await jwt_utilities_1.JwtUtilities.verifyAsync(refreshToken, this.refreshTokenSecret); const userRefreshData = await this.refreshTokenRepository.findOne({ where: { tokenValue: refreshToken } }, options); if (!userRefreshData) { throw new rest_1.HttpErrors.Unauthorized('Error verifying token: Invalid Token'); } return userRefreshData; } catch (error) { throw new rest_1.HttpErrors.Unauthorized( // eslint-disable-next-line typescript/no-unsafe-member-access `Error verifying refresh token: ${error.message}`); } } }; exports.RefreshTokenService = RefreshTokenService; exports.RefreshTokenService = RefreshTokenService = tslib_1.__decorate([ tslib_1.__param(0, (0, core_1.inject)(keys_1.LbxJwtBindings.REFRESH_TOKEN_SECRET)), tslib_1.__param(1, (0, core_1.inject)(keys_1.LbxJwtBindings.REFRESH_TOKEN_EXPIRES_IN_MS)), tslib_1.__param(2, (0, core_1.inject)(keys_1.LbxJwtBindings.REFRESH_TOKEN_ISSUER)), tslib_1.__param(3, (0, core_1.inject)(keys_1.LbxJwtBindings.BASE_USER_REPOSITORY)), tslib_1.__param(4, (0, core_1.inject)(keys_1.LbxJwtBindings.REFRESH_TOKEN_REPOSITORY)), tslib_1.__param(5, (0, core_1.inject)(keys_1.LbxJwtBindings.BASE_USER_SERVICE)), tslib_1.__param(6, (0, core_1.inject)(keys_1.LbxJwtBindings.ACCESS_TOKEN_SERVICE)), tslib_1.__param(7, (0, core_1.inject)(keys_1.LbxJwtBindings.ACCESS_TOKEN_EXPIRES_IN_MS)), tslib_1.__metadata("design:paramtypes", [String, Number, String, repositories_1.BaseUserRepository, repositories_1.RefreshTokenRepository, base_user_service_1.BaseUserService, access_token_service_1.AccessTokenService, Number]) ], RefreshTokenService); //# sourceMappingURL=refresh-token.service.js.map