UNPKG

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
"use strict"; 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