UNPKG

@zimpligital/medusa-plugin-auth-otp

Version:
183 lines 15.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const client_sns_1 = require("@aws-sdk/client-sns"); const utils_1 = require("@medusajs/framework/utils"); const date_fns_1 = require("date-fns"); const scrypt_kdf_1 = __importDefault(require("scrypt-kdf")); const otp_requests_1 = require("./models/otp-requests"); class AuthOTPModuleService extends (0, utils_1.MedusaService)({ OtpRequest: otp_requests_1.OtpRequest, }) { constructor({}, options) { super(...arguments); this.options_ = options; this.validateOptions(); this.client = new client_sns_1.SNSClient({ region: options.awsSNSRegion, credentials: { accessKeyId: options.awsSNSAccessKeyId, secretAccessKey: options.awsSNSAccessKeySecret, }, }); this.jwtSecret = options.jwtSecret; } validateOptions() { if (!this.options_.awsSNSAccessKeyId) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'AWS SNS Access Key Id is required'); } if (!this.options_.awsSNSAccessKeySecret) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'AWS SNS Access Key Secret is required'); } if (!this.options_.awsSNSRegion) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'AWS SNS Region is required'); } if (!this.options_.jwtSecret) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'JWT Secret is required'); } } getSubject() { return this.options_.otpConfigs?.subject || 'Medusa OTP'; } getMessage({ otp, ref_code }) { const defaultTemplate = 'Your OTP is {otp} [ref code: {ref_code}]'; const messageTemplate = this.options_.otpConfigs?.message || defaultTemplate; let message = ''; message = messageTemplate .replace('{otp}', otp) .replace('{ref_code}', ref_code); const webUrl = this.options_.otpConfigs?.webUrl; if (webUrl) { const url = webUrl.replace(/(^\w+:|^)\/\//, ''); message += '\n\n'; message += `@${url} #${otp}`; } return message; } async sendSMS(input) { const subject = this.getSubject(); const message = this.getMessage(input); const command = new client_sns_1.PublishCommand({ Subject: subject, Message: message, PhoneNumber: input.to, }); const snsResult = await this.client.send(command); return { ...snsResult, }; } async saveOtpRequest(input) { const hashedOtp = await this.hashOTP(input.otp); const otpRequest = await this.createOtpRequests({ otp_hash: hashedOtp, ref_code: input.ref_code, phone: input.phone, country_code: input.country_code, expired_at: this.getExpiredAt(), }); return otpRequest; } async varifyOtpRequest(input) { const otpRequest = input.otp_request; if (!otpRequest) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, 'OTP request not found', 'NOT_FOUND'); } if (otpRequest.status === 'exceeded_attempts') { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'OTP attempts exceeded', 'OTP_ATTEMPTS_EXCEEDED'); } if (otpRequest.status !== 'pending') { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'OTP request not pending', 'NOT_FOUND'); } const now = new Date(); const isExpired = otpRequest.expired_at && (0, date_fns_1.isBefore)(otpRequest.expired_at, now); if (isExpired) { throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'OTP request expired', 'OTP_EXPIRED'); } const verifyAttemptLimit = this.getVerifyAttemptLimit(); if (otpRequest.attempts && otpRequest.attempts >= verifyAttemptLimit) { if (!otpRequest.attempts_exceeded_at) { await this.updateOtpRequests({ id: otpRequest.id, attempts_exceeded_at: new Date(), status: 'exceeded_attempts', }); } throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'OTP attemps exceeded', 'OTP_ATTEMPTS_EXCEEDED'); } const hashedOtp = otpRequest.otp_hash; const isMatch = await this.verifyOTP(input.otp, hashedOtp); if (!isMatch) { const attempts = (otpRequest.attempts || 0) + 1; await this.updateOtpRequests({ id: otpRequest.id, attempts, }); throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, 'OTP is invalid', 'INVALID_OTP'); } return isMatch; } generateJwtToken(payload) { return (0, utils_1.generateJwtToken)(payload, { expiresIn: '1d', secret: this.jwtSecret, }); } getExpiredAt() { const expiryInSecs = this.options_.otpConfigs?.expiry || 90; const now = new Date(); return (0, date_fns_1.addSeconds)(now, expiryInSecs); } // Get the retry delay in seconds getRetryDelay() { const retryDelayInSecs = this.options_.otpConfigs?.retryDelay || 60; return retryDelayInSecs; } getVerifyAttemptLimit() { const verifyAttemptLimit = this.options_.otpConfigs?.verifyAttemptLimit || 5; return verifyAttemptLimit; } async hashOTP(otp) { const hashConfig = { logN: 15, r: 8, p: 1 }; const passwordHash = await scrypt_kdf_1.default.kdf(otp, hashConfig); return passwordHash.toString('base64'); } async verifyOTP(otp, hash) { const passwordHash = Buffer.from(hash, 'base64'); const result = await scrypt_kdf_1.default.verify(passwordHash, otp); return result; } // support too many requests & exceeded attempts async getRequestRetryAt(input) { const lastRequest = await this.listOtpRequests({ phone: input.phone, // status: 'exceeded_attempts', }, { take: 1, order: { created_at: 'desc', }, }).then((requests) => requests[0]); if (!lastRequest) { return null; } const retryDelayInSecs = this.getRetryDelay(); // Check if the last request is pending if (lastRequest.status === 'pending') { const createdAt = lastRequest.created_at; const availableRetryAt = (0, date_fns_1.addSeconds)(createdAt, retryDelayInSecs); return availableRetryAt; } // Check if the last request is exceeded attempts const attemptExceededAt = lastRequest.attempts_exceeded_at; if (!attemptExceededAt) { return null; } const retryAt = (0, date_fns_1.addSeconds)(attemptExceededAt, retryDelayInSecs); return retryAt; } } exports.default = AuthOTPModuleService; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL2F1dGgtb3RwL3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSxvREFBZ0U7QUFDaEUscURBSW1DO0FBQ25DLHVDQUFnRDtBQUNoRCw0REFBZ0M7QUFFaEMsd0RBQW1EO0FBR25ELE1BQXFCLG9CQUFxQixTQUFRLElBQUEscUJBQWEsRUFBQztJQUMvRCxVQUFVLEVBQVYseUJBQVU7Q0FDVixDQUFDO0lBS0QsWUFBWSxFQUFFLEVBQUUsT0FBZ0I7UUFDL0IsS0FBSyxDQUFDLEdBQUcsU0FBUyxDQUFDLENBQUM7UUFFcEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUM7UUFDeEIsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1FBRXZCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxzQkFBUyxDQUFDO1lBQzNCLE1BQU0sRUFBRSxPQUFPLENBQUMsWUFBWTtZQUM1QixXQUFXLEVBQUU7Z0JBQ1osV0FBVyxFQUFFLE9BQU8sQ0FBQyxpQkFBaUI7Z0JBQ3RDLGVBQWUsRUFBRSxPQUFPLENBQUMscUJBQXFCO2FBQzlDO1NBQ0QsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDO0lBQ3BDLENBQUM7SUFFRCxlQUFlO1FBQ2QsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztZQUN0QyxNQUFNLElBQUksbUJBQVcsQ0FDcEIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUM5QixtQ0FBbUMsQ0FDbkMsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1lBQzFDLE1BQU0sSUFBSSxtQkFBVyxDQUNwQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLHVD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