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:
Sathira Sri Sathara
2026-09-03 13:56:18 +05:30
parent 267e80e2ec
commit 9d3d431416
54 changed files with 1389 additions and 1076 deletions
+29
View File
@@ -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 };