Auth implemented

This commit is contained in:
AG
2025-10-13 18:18:34 +03:00
parent 6e587e8aa7
commit 60e9c24440
28 changed files with 1251 additions and 47 deletions

44
backend/src/api/auth.ts Normal file
View File

@@ -0,0 +1,44 @@
// backend/src/api/auth.ts
import express from 'express';
import * as dotenv from 'dotenv';
import * as path from 'path';
import { AuthService } from '../services/AuthService';
import { SessionService } from '../services/SessionService';
import { AuthLogger } from '../services/AuthLogger';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
const SESSION_SECRET = process.env.SESSION_SECRET;
const JWT_SECRET = process.env.JWT_SECRET;
if (!SESSION_SECRET) {
throw new Error('SESSION_SECRET is not defined in the environment variables.');
}
if (!JWT_SECRET) {
throw new Error('JWT_SECRET is not defined in the environment variables.');
}
const router = express.Router();
router.post('/passphrase', (req, res) => {
const { passphrase } = req.body;
const ipAddress = req.ip || ''; // Get IP address for logging, default to empty string if undefined
if (!passphrase) {
AuthLogger.logAttempt('failure', ipAddress);
return res.status(400).json({ message: 'Passphrase is required.' });
}
if (AuthService.validatePassphrase(passphrase)) {
const session = SessionService.createSession();
SessionService.authenticateSession(session.id);
AuthLogger.logAttempt('success', ipAddress);
return res.status(200).json({ message: 'Authentication successful', sessionToken: session.id });
} else {
AuthLogger.logAttempt('failure', ipAddress);
return res.status(401).json({ message: 'Invalid passphrase' });
}
});
export default router;

View File

@@ -5,7 +5,15 @@ import express from 'express';
import http from 'http';
import { createWebSocketServer } from './ws';
import sessionsRouter from './routes/sessions';
import authRouter from './api/auth';
import { authMiddleware } from './middleware/authMiddleware'; // Import the middleware
import cors from 'cors';
import { v4 as uuidv4 } from 'uuid';
import { sessions, SessionState } from './ws'; // Import sessions and SessionState from ws/index.ts
console.log('index.ts: AUTH_PASSPHRASE:', process.env.AUTH_PASSPHRASE);
console.log('index.ts: SESSION_SECRET:', process.env.SESSION_SECRET);
console.log('index.ts: JWT_SECRET:', process.env.JWT_SECRET);
const app = express();
const server = http.createServer(app);
@@ -14,8 +22,28 @@ const server = http.createServer(app);
app.use(express.json());
app.use(cors());
// API Routes
app.use('/', sessionsRouter);
// Public API Routes
app.use('/api/auth', authRouter);
// Public route for creating a new session
app.post('/sessions', (req, res) => {
const sessionId = uuidv4();
sessions.set(sessionId, {
state: SessionState.SETUP,
topic: null,
description: null,
expectedResponses: 0,
submittedCount: 0,
responses: new Map(),
clients: new Map(),
finalResult: null,
});
console.log(`New session created: ${sessionId}`);
res.status(201).json({ sessionId });
});
// Protected API Routes
app.use('/sessions', authMiddleware, sessionsRouter);
// Create and attach WebSocket server
createWebSocketServer(server);

View File

@@ -0,0 +1,21 @@
// backend/src/middleware/authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { SessionService } from '../services/SessionService';
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
const sessionToken = req.headers['x-session-token'] as string; // Assuming token is sent in a header
if (!sessionToken) {
return res.status(401).json({ message: 'No session token provided.' });
}
const session = SessionService.getSession(sessionToken);
if (!session || !session.isAuthenticated) {
return res.status(401).json({ message: 'Invalid or unauthenticated session.' });
}
// Optionally, attach session to request for further use
(req as any).session = session;
next();
};

View File

@@ -4,22 +4,6 @@ import { sessions, SessionState, broadcastToSession, handleWebSocketMessage } fr
const router = express.Router();
router.post('/sessions', (req, res) => {
const sessionId = uuidv4();
sessions.set(sessionId, {
state: SessionState.SETUP,
topic: null,
description: null,
expectedResponses: 0,
submittedCount: 0,
responses: new Map(),
clients: new Map(),
finalResult: null,
});
console.log(`New session created: ${sessionId}`);
res.status(201).json({ sessionId });
});
router.post('/sessions/:sessionId/responses', async (req, res) => {
const { sessionId } = req.params;
const { userId, wants, accepts, afraidToAsk } = req.body;

View File

@@ -0,0 +1,27 @@
// backend/src/services/AuthLogger.ts
import * as fs from 'fs';
import * as path from 'path';
const LOG_FILE_PATH = path.join(__dirname, '../../logs/auth.log');
export class AuthLogger {
private static ensureLogFileExists() {
const logDir = path.dirname(LOG_FILE_PATH);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
if (!fs.existsSync(LOG_FILE_PATH)) {
fs.writeFileSync(LOG_FILE_PATH, '', { encoding: 'utf8' });
}
}
public static logAttempt(
type: 'success' | 'failure',
ipAddress: string,
timestamp: Date = new Date()
): void {
AuthLogger.ensureLogFileExists();
const logEntry = `${timestamp.toISOString()} - ${type.toUpperCase()} - IP: ${ipAddress}\n`;
fs.appendFileSync(LOG_FILE_PATH, logEntry, { encoding: 'utf8' });
}
}

View File

@@ -0,0 +1,26 @@
// backend/src/services/AuthService.ts
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
export class AuthService {
private static passphrase: string | undefined = process.env.AUTH_PASSPHRASE;
public static getPassphrase(): string | undefined {
return AuthService.passphrase;
}
public static isAuthEnabled(): boolean {
return !!AuthService.passphrase && AuthService.passphrase.trim() !== '';
}
public static validatePassphrase(inputPassphrase: string): boolean {
console.log('AuthService: AUTH_PASSPHRASE from process.env:', process.env.AUTH_PASSPHRASE);
console.log('AuthService: Stored passphrase:', AuthService.passphrase);
if (!AuthService.isAuthEnabled()) {
return true; // If auth is not enabled, any passphrase is "valid"
}
return inputPassphrase === AuthService.passphrase;
}
}

View File

@@ -0,0 +1,41 @@
// backend/src/services/SessionService.ts
import { v4 as uuidv4 } from 'uuid';
interface Session {
id: string;
isAuthenticated: boolean;
createdAt: Date;
}
const sessions = new Map<string, Session>();
export class SessionService {
public static createSession(): Session {
const id = uuidv4();
const newSession: Session = {
id,
isAuthenticated: false,
createdAt: new Date(),
};
sessions.set(id, newSession);
return newSession;
}
public static getSession(id: string): Session | undefined {
return sessions.get(id);
}
public static authenticateSession(id: string): boolean {
const session = sessions.get(id);
if (session) {
session.isAuthenticated = true;
sessions.set(id, session);
return true;
}
return false;
}
public static destroySession(id: string): void {
sessions.delete(id);
}
}