lbx-jwt
Version:
Provides JWT authentication for loopback applications. Includes storing roles inside tokens and handling refreshing. Built-in reuse detection.
125 lines • 5.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwoFactorService = void 0;
const tslib_1 = require("tslib");
const crypto_1 = tslib_1.__importDefault(require("crypto"));
const core_1 = require("@loopback/core");
const rest_1 = require("@loopback/rest");
const hi_base32_utilities_1 = require("../encapsulation/hi-base32.utilities");
const otp_auth_utilities_1 = require("../encapsulation/otp-auth.utilities");
const keys_1 = require("../keys");
const repositories_1 = require("../repositories");
/**
* Handles everything connected to two factor authentication.
*/
let TwoFactorService = class TwoFactorService {
constructor(forceTwoFactor, baseUserRepository, twoFactorHeader, twoFactorLabel) {
this.forceTwoFactor = forceTwoFactor;
this.baseUserRepository = baseUserRepository;
this.twoFactorHeader = twoFactorHeader;
this.twoFactorLabel = twoFactorLabel;
}
/**
* Generates a secret and a two factor auth url to use for a qr code.
* Both values gets saved to the user credentials of the user with the given id.
* @param userId - The id of the user that wants to activate two factor authentication.
* @param options - Additional options eg. Transaction.
* @returns The qr code url.
*/
async turnOn2FA(userId, options) {
const user = await this.baseUserRepository.findById(userId);
if (user.twoFactorEnabled === true) {
throw new rest_1.HttpErrors.BadRequest('The requesting user has already configured two factor authentication.');
}
const secret = this.generateSecret();
const totp = otp_auth_utilities_1.OtpAuthUtilities.createTOTP({
label: this.twoFactorLabel,
secret: secret
});
await this.baseUserRepository.credentials(userId).patch({
twoFactorSecret: secret,
twoFactorAuthUrl: totp.toString()
}, options);
return totp.toString();
}
/**
* Confirms the setup of two factor authentication for the user with the given id.
* @param userId - The id of the user that wants to activate two factor authentication.
* @param code - The code that is used to confirm that the user has the correct secret setup.
* @param options - Additional options eg. Transaction.
*/
async confirmTurnOn2FA(userId, code, options) {
await this.validateCode(userId, code, options);
await this.baseUserRepository.updateById(userId, { twoFactorEnabled: true }, options);
}
/**
* Turns off 2fa for the user with the given id.
* @param userId - The id of the user to turn 2fa off for.
* @param options - Additional options eg. Transaction.
*/
async turnOff2FA(userId, options) {
if (this.forceTwoFactor) {
throw new rest_1.HttpErrors.BadRequest(`
2 Factor Authentication is enforced.
Override LbxJwtBindings.FORCE_TWO_FACTOR if you want to enable turning it off.
`);
}
await this.baseUserRepository.credentials(userId).patch({
twoFactorSecret: undefined,
twoFactorAuthUrl: undefined
}, options);
await this.baseUserRepository.updateById(userId, { twoFactorEnabled: false }, options);
}
/**
* Extracts a two factor code from the given request by reading the custom header.
* @param request - The request of which the two factor code should be read.
* @returns The found two factor code.
* @throws When the custom header wasn't found, is empty or not 6 digits long.
*/
extractCodeFromRequest(request) {
if (!request.rawHeaders.find(h => h === this.twoFactorHeader)) {
throw new rest_1.HttpErrors.Unauthorized(`"${this.twoFactorHeader}" header not found`);
}
const code = request.get(this.twoFactorHeader);
if (!code) {
throw new rest_1.HttpErrors.Unauthorized('No two factor code has been provided.');
}
if (code.length !== 6) {
throw new rest_1.HttpErrors.Unauthorized('The provided two factor code is not 6 digits long.');
}
return code;
}
/**
* Validates the given two factor code for the user with the given id.
* @param userId - The id of the user that tries to do something that requires a 2fa code.
* @param code - The two factor code to validate.
* @param options - Additional options eg. Transaction.
*/
async validateCode(userId, code, options) {
const credentials = await this.baseUserRepository.credentials(userId).get(undefined, options);
const totp = otp_auth_utilities_1.OtpAuthUtilities.createTOTP({
label: this.twoFactorLabel,
secret: credentials.twoFactorSecret
});
if (totp.validate({ token: code }) == undefined) {
throw new rest_1.HttpErrors.Unauthorized('The provided two factor code is invalid.');
}
}
generateSecret() {
const buffer = crypto_1.default.randomBytes(15);
const base32 = hi_base32_utilities_1.HiBase32Utilities
.encode(buffer)
.replaceAll('=', '')
.substring(0, 24);
return base32;
}
};
exports.TwoFactorService = TwoFactorService;
exports.TwoFactorService = TwoFactorService = tslib_1.__decorate([
tslib_1.__param(0, (0, core_1.inject)(keys_1.LbxJwtBindings.FORCE_TWO_FACTOR)),
tslib_1.__param(1, (0, core_1.inject)(keys_1.LbxJwtBindings.BASE_USER_REPOSITORY)),
tslib_1.__param(2, (0, core_1.inject)(keys_1.LbxJwtBindings.TWO_FACTOR_HEADER)),
tslib_1.__param(3, (0, core_1.inject)(keys_1.LbxJwtBindings.TWO_FACTOR_LABEL, { optional: true })),
tslib_1.__metadata("design:paramtypes", [Boolean, repositories_1.BaseUserRepository, String, String])
], TwoFactorService);
//# sourceMappingURL=two-factor.service.js.map