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
JavaScript
;
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