Enhance authentication module with code and OTP verification
- Added CodeAuth class for verifying authentication codes and OTPs against the database. - Implemented methods to check host status, validate codes, and manage OTP expiration. - Updated KeycloakAuth to retrieve user information from the database. - Refactored createAuthMiddleware to handle authentication checks for Socket.IO connections.
This commit is contained in:
parent
ce15d3dbfc
commit
03eb0a61c1
130
src/auth/auth.js
130
src/auth/auth.js
@ -4,6 +4,14 @@ import jwt from 'jsonwebtoken';
|
||||
import log4js from 'log4js';
|
||||
// Load configuration
|
||||
import { loadConfig } from '../config.js';
|
||||
import {
|
||||
editObject,
|
||||
getObject,
|
||||
getObjectByFilter
|
||||
} from '../database/database.js';
|
||||
import { hostModel } from '../database/schemas/management/host.schema.js';
|
||||
import { userModel } from '../database/schemas/management/user.schema.js';
|
||||
import { generateAuthCode } from '../utils.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
@ -11,7 +19,7 @@ const logger = log4js.getLogger('Auth');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export class KeycloakAuth {
|
||||
constructor(config) {
|
||||
constructor() {
|
||||
this.config = config.auth;
|
||||
this.tokenCache = new Map(); // Cache for verified tokens
|
||||
}
|
||||
@ -66,7 +74,7 @@ export class KeycloakAuth {
|
||||
|
||||
// Parse token to extract user info
|
||||
const decodedToken = jwt.decode(token);
|
||||
const user = {
|
||||
const decodedUser = {
|
||||
id: decodedToken.sub,
|
||||
username: decodedToken.preferred_username,
|
||||
email: decodedToken.email,
|
||||
@ -74,6 +82,11 @@ export class KeycloakAuth {
|
||||
roles: this.extractRoles(decodedToken)
|
||||
};
|
||||
|
||||
const user = await getObjectByFilter({
|
||||
model: userModel,
|
||||
filter: { username: decodedUser.username }
|
||||
});
|
||||
|
||||
// Cache the verified token
|
||||
const expiresAt = introspection.exp * 1000; // Convert to milliseconds
|
||||
this.tokenCache.set(token, { expiresAt, user });
|
||||
@ -120,28 +133,101 @@ export class KeycloakAuth {
|
||||
}
|
||||
}
|
||||
|
||||
// Socket.IO middleware for authentication
|
||||
export function createAuthMiddleware(auth) {
|
||||
return async (socket, next) => {
|
||||
const { token } = socket.handshake.auth;
|
||||
|
||||
if (!token) {
|
||||
return next(new Error('Authentication token is required'));
|
||||
}
|
||||
|
||||
export class CodeAuth {
|
||||
// Verify a code with the database
|
||||
async verifyCode(id, authCode) {
|
||||
try {
|
||||
const authResult = await auth.verifyToken(token);
|
||||
|
||||
if (!authResult.valid) {
|
||||
return next(new Error('Invalid authentication token'));
|
||||
const host = await getObject({ model: hostModel, id, cached: true });
|
||||
if (host == undefined) {
|
||||
const error = 'Host not found.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
|
||||
// Attach user information to socket
|
||||
socket.user = authResult.user;
|
||||
next();
|
||||
} catch (err) {
|
||||
logger.error('Authentication error:', err);
|
||||
next(new Error('Authentication failed'));
|
||||
if (host.active == false) {
|
||||
const error = 'Host not active.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
if (host.authCode == undefined || host.authCode == '') {
|
||||
const error = 'No authCode on database.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
if (host.authCode != authCode) {
|
||||
const error = 'authCode does not match.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
return { valid: true, host: host };
|
||||
} catch (error) {
|
||||
logger.error('Code verification error:', error.message);
|
||||
return { valid: false };
|
||||
}
|
||||
}
|
||||
|
||||
async verifyOtp(otp) {
|
||||
try {
|
||||
const host = await getObjectByFilter({
|
||||
model: hostModel,
|
||||
filter: { otp: otp },
|
||||
cached: false
|
||||
});
|
||||
if (host == undefined) {
|
||||
const error = 'No host found with OTP.';
|
||||
logger.warn(error);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
const id = host._id.toString();
|
||||
if (host.active == false) {
|
||||
const error = 'Host is not active.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
if (host.otp == undefined) {
|
||||
const error = 'No OTP on database.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
if (host.otpExpiresAt == undefined) {
|
||||
const error = 'No OTP expiry.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
if (host.otpExpiresAt < Date.now()) {
|
||||
const error = 'OTP expired.';
|
||||
logger.warn(error, 'Host:', id);
|
||||
return { valid: false, error: error };
|
||||
}
|
||||
const authCodeHost = await editObject({
|
||||
model: hostModel,
|
||||
id: id,
|
||||
updateData: { authCode: generateAuthCode() }
|
||||
});
|
||||
return { valid: true, host: authCodeHost };
|
||||
} catch (error) {
|
||||
logger.error('Code verification error:', error.message);
|
||||
return { valid: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Socket.IO middleware for authentication
|
||||
export function createAuthMiddleware(socketUser) {
|
||||
return async (packet, next) => {
|
||||
const [event] = packet; // event name is always first element
|
||||
|
||||
// Allow the 'authenticate' event through without checks
|
||||
|
||||
logger.trace('Event:', event);
|
||||
if (event === 'authenticate') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (socketUser.authenticated) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
return next(new Error('Authentication is required.'));
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user