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
126
src/auth/auth.js
126
src/auth/auth.js
@ -4,6 +4,14 @@ import jwt from 'jsonwebtoken';
|
|||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
// Load configuration
|
// Load configuration
|
||||||
import { loadConfig } from '../config.js';
|
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();
|
const config = loadConfig();
|
||||||
|
|
||||||
@ -11,7 +19,7 @@ const logger = log4js.getLogger('Auth');
|
|||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
export class KeycloakAuth {
|
export class KeycloakAuth {
|
||||||
constructor(config) {
|
constructor() {
|
||||||
this.config = config.auth;
|
this.config = config.auth;
|
||||||
this.tokenCache = new Map(); // Cache for verified tokens
|
this.tokenCache = new Map(); // Cache for verified tokens
|
||||||
}
|
}
|
||||||
@ -66,7 +74,7 @@ export class KeycloakAuth {
|
|||||||
|
|
||||||
// Parse token to extract user info
|
// Parse token to extract user info
|
||||||
const decodedToken = jwt.decode(token);
|
const decodedToken = jwt.decode(token);
|
||||||
const user = {
|
const decodedUser = {
|
||||||
id: decodedToken.sub,
|
id: decodedToken.sub,
|
||||||
username: decodedToken.preferred_username,
|
username: decodedToken.preferred_username,
|
||||||
email: decodedToken.email,
|
email: decodedToken.email,
|
||||||
@ -74,6 +82,11 @@ export class KeycloakAuth {
|
|||||||
roles: this.extractRoles(decodedToken)
|
roles: this.extractRoles(decodedToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const user = await getObjectByFilter({
|
||||||
|
model: userModel,
|
||||||
|
filter: { username: decodedUser.username }
|
||||||
|
});
|
||||||
|
|
||||||
// Cache the verified token
|
// Cache the verified token
|
||||||
const expiresAt = introspection.exp * 1000; // Convert to milliseconds
|
const expiresAt = introspection.exp * 1000; // Convert to milliseconds
|
||||||
this.tokenCache.set(token, { expiresAt, user });
|
this.tokenCache.set(token, { expiresAt, user });
|
||||||
@ -120,28 +133,101 @@ export class KeycloakAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Socket.IO middleware for authentication
|
export class CodeAuth {
|
||||||
export function createAuthMiddleware(auth) {
|
// Verify a code with the database
|
||||||
return async (socket, next) => {
|
async verifyCode(id, authCode) {
|
||||||
const { token } = socket.handshake.auth;
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return next(new Error('Authentication token is required'));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await auth.verifyToken(token);
|
const host = await getObject({ model: hostModel, id, cached: true });
|
||||||
|
if (host == undefined) {
|
||||||
if (!authResult.valid) {
|
const error = 'Host not found.';
|
||||||
return next(new Error('Invalid authentication token'));
|
logger.warn(error, 'Host:', id);
|
||||||
|
return { valid: false, error: error };
|
||||||
|
}
|
||||||
|
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 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach user information to socket
|
async verifyOtp(otp) {
|
||||||
socket.user = authResult.user;
|
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();
|
next();
|
||||||
} catch (err) {
|
return;
|
||||||
logger.error('Authentication error:', err);
|
|
||||||
next(new Error('Authentication failed'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (socketUser.authenticated) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return next(new Error('Authentication is required.'));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user