Files
MP-Server/macropad-relay/src/services/SessionManager.ts
T

122 lines
3.0 KiB
TypeScript
Raw Normal View History

2026-01-05 19:46:33 -08:00
// Session Manager - handles session storage and authentication
import fs from 'fs';
import path from 'path';
import bcrypt from 'bcrypt';
import { config } from '../config';
import { generateSessionId } from '../utils/idGenerator';
import { logger } from '../utils/logger';
export interface Session {
id: string;
passwordHash: string;
createdAt: string;
lastConnected: string;
}
interface SessionStore {
sessions: Record<string, Session>;
}
export class SessionManager {
private sessionsFile: string;
private sessions: Map<string, Session> = new Map();
constructor() {
this.sessionsFile = path.join(config.dataDir, 'sessions.json');
this.load();
}
private load(): void {
try {
if (fs.existsSync(this.sessionsFile)) {
const data = JSON.parse(fs.readFileSync(this.sessionsFile, 'utf-8')) as SessionStore;
for (const [id, session] of Object.entries(data.sessions || {})) {
this.sessions.set(id, session);
}
logger.info(`Loaded ${this.sessions.size} sessions`);
}
} catch (error) {
logger.error('Failed to load sessions:', error);
}
}
private save(): void {
try {
const store: SessionStore = {
sessions: Object.fromEntries(this.sessions)
};
fs.mkdirSync(path.dirname(this.sessionsFile), { recursive: true });
fs.writeFileSync(this.sessionsFile, JSON.stringify(store, null, 2));
} catch (error) {
logger.error('Failed to save sessions:', error);
}
}
async exists(sessionId: string): Promise<boolean> {
return this.sessions.has(sessionId);
}
async createSession(password: string): Promise<Session> {
// Generate unique session ID
let id: string;
do {
id = generateSessionId(config.sessionIdLength);
} while (this.sessions.has(id));
const passwordHash = await bcrypt.hash(password, config.bcryptRounds);
const now = new Date().toISOString();
const session: Session = {
id,
passwordHash,
createdAt: now,
lastConnected: now
};
this.sessions.set(id, session);
this.save();
logger.info(`Created new session: ${id}`);
return session;
}
async validatePassword(sessionId: string, password: string): Promise<boolean> {
const session = this.sessions.get(sessionId);
if (!session) {
return false;
}
const valid = await bcrypt.compare(password, session.passwordHash);
if (valid) {
// Update last connected time
session.lastConnected = new Date().toISOString();
this.save();
}
return valid;
}
getSession(sessionId: string): Session | undefined {
return this.sessions.get(sessionId);
}
updateLastConnected(sessionId: string): void {
const session = this.sessions.get(sessionId);
if (session) {
session.lastConnected = new Date().toISOString();
this.save();
}
}
deleteSession(sessionId: string): boolean {
const deleted = this.sessions.delete(sessionId);
if (deleted) {
this.save();
logger.info(`Deleted session: ${sessionId}`);
}
return deleted;
}
}