feat: implement identity and security features
- Added account types and privileged account types constants. - Created admin user controller for updating user security fields. - Developed role assignment controller for managing user roles. - Implemented validation middleware for request schemas. - Defined user role and auth session models for database interactions. - Created services for authentication, email notifications, and OTP handling. - Developed OAuth service for Google and Apple authentication. - Added JWT utility functions for token generation and verification. - Implemented comprehensive tests for authentication, session management, and password policies. - Created migration for updating user schema and adding new tables for auth sessions and user identities.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
const crypto = require("crypto");
|
||||
const redis = require("../../config/redisClient");
|
||||
|
||||
const otpHash = (challengeId, otp) => crypto.createHmac("sha256", process.env.JWT_SECRET).update(`${challengeId}:${otp}`).digest("hex");
|
||||
const generateOtp = () => crypto.randomInt(0, 1000000).toString().padStart(6, "0");
|
||||
|
||||
const createLoginChallenge = async ({ userId, rememberMe, context }) => {
|
||||
const challengeId = crypto.randomUUID();
|
||||
const otp = generateOtp();
|
||||
await redis.set(`login:${challengeId}`, JSON.stringify({ userId, otpHash: otpHash(challengeId, otp), attempts: 0, rememberMe, context }), "EX", Number(process.env.LOGIN_OTP_TTL_SECONDS || 900));
|
||||
return { challengeId, otp };
|
||||
};
|
||||
|
||||
const VERIFY_SCRIPT = `
|
||||
local raw=redis.call('GET',KEYS[1]); if not raw then return {-3} end
|
||||
local value=cjson.decode(raw)
|
||||
if value.otpHash==ARGV[1] then redis.call('DEL',KEYS[1]); return {1,value.userId,cjson.encode(value)} end
|
||||
value.attempts=(value.attempts or 0)+1
|
||||
if value.attempts>=tonumber(ARGV[2]) then redis.call('DEL',KEYS[1]); return {-2} end
|
||||
redis.call('SET',KEYS[1],cjson.encode(value),'KEEPTTL'); return {-1}
|
||||
`;
|
||||
const verifyLoginChallenge = async (challengeId, otp) => {
|
||||
const result = await redis.eval(VERIFY_SCRIPT, 1, `login:${challengeId}`, otpHash(challengeId, otp), Number(process.env.LOGIN_OTP_MAX_ATTEMPTS || 5));
|
||||
if (Number(result[0]) !== 1) throw Object.assign(new Error("Invalid or expired challenge"), { code: "INVALID_OTP", status: 401 });
|
||||
const stored = JSON.parse(result[2]);
|
||||
return { userId: result[1], rememberMe: Boolean(stored.rememberMe), context: stored.context || {} };
|
||||
};
|
||||
|
||||
module.exports = { generateOtp, otpHash, createLoginChallenge, verifyLoginChallenge };
|
||||
Reference in New Issue
Block a user