@zimpligital/medusa-plugin-auth-otp
Version:
A starter for Medusa plugins.
183 lines • 15.5 kB
JavaScript
;
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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