Skip to content

Commit 80e0818

Browse files
committed
feat: add authentication utilities and configuration interfaces
1 parent 62fb99d commit 80e0818

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

src/server/types/user.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export interface AuthConfig {
2+
jwtSecret: string;
3+
jwtExpiresIn: string;
4+
refreshTokenExpiresIn: string;
5+
bcryptRounds: number;
6+
maxLoginAttempts: number;
7+
lockoutDuration: number; // minutes
8+
maxSessionsPerUser: number;
9+
}
10+
11+
12+
export interface User {
13+
id: string;
14+
username: string;
15+
email?: string;
16+
password_hash: string;
17+
name: string;
18+
avatar?: string;
19+
role: 'admin' | 'user' | 'viewer';
20+
is_active: boolean;
21+
failed_login_attempts: number;
22+
locked_until?: Date;
23+
last_login_at?: Date;
24+
password_changed_at?: Date;
25+
created_at: Date;
26+
updated_at: Date;
27+
}
28+
29+
export interface Session {
30+
id: string;
31+
user_id: string;
32+
token_hash: string;
33+
refresh_token_hash?: string;
34+
ip_address?: string;
35+
user_agent?: string;
36+
is_valid: boolean;
37+
expires_at: Date;
38+
refresh_expires_at?: Date;
39+
created_at: Date;
40+
last_used_at: Date;
41+
}
42+
43+
export interface JwtPayload {
44+
userId: string;
45+
username: string;
46+
role: string;
47+
sessionId: string;
48+
type: 'access' | 'refresh';
49+
}

src/server/utils/auth.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import path from 'path';
2+
import fs from 'fs-extra';
3+
import crypto from 'crypto';
4+
import { AuthConfig } from '../types/user.js';
5+
import { getDbClient } from './db.js';
6+
import { Request } from 'express';
7+
8+
export const getAuthConfig = (): AuthConfig => {
9+
const envPath = path.join(process.cwd(), 'env.json');
10+
let envData: any = {};
11+
12+
if (fs.existsSync(envPath)) {
13+
envData = JSON.parse(fs.readFileSync(envPath, 'utf-8'));
14+
}
15+
16+
const authConfig = envData.auth || {};
17+
18+
return {
19+
jwtSecret: authConfig.jwtSecret || process.env.JWT_SECRET || crypto.randomBytes(64).toString('hex'),
20+
jwtExpiresIn: authConfig.jwtExpiresIn || '1h',
21+
refreshTokenExpiresIn: authConfig.refreshTokenExpiresIn || '7d',
22+
bcryptRounds: authConfig.bcryptRounds || 12,
23+
maxLoginAttempts: authConfig.maxLoginAttempts || 5,
24+
lockoutDuration: authConfig.lockoutDuration || 15,
25+
maxSessionsPerUser: authConfig.maxSessionsPerUser || 5
26+
};
27+
};
28+
29+
30+
/**
31+
* Hash a token for secure storage
32+
*/
33+
export const hashToken = (token: string): string => {
34+
return crypto.createHash('sha256').update(token).digest('hex');
35+
};
36+
37+
38+
/**
39+
* Get client IP address
40+
*/
41+
export const getClientIp = (req: Request): string => {
42+
const forwarded = req.headers['x-forwarded-for'];
43+
if (typeof forwarded === 'string') {
44+
return forwarded.split(',')[0].trim();
45+
}
46+
return req.ip || req.socket.remoteAddress || 'unknown';
47+
};
48+
49+
/**
50+
* Log audit event
51+
*/
52+
export const logAuditEvent = async (
53+
userId: string | null,
54+
action: string,
55+
req: Request,
56+
details?: any
57+
): Promise<void> => {
58+
try {
59+
const sql = getDbClient();
60+
await sql`
61+
INSERT INTO auth_audit_log (user_id, action, ip_address, user_agent, details)
62+
VALUES (${userId}, ${action}, ${getClientIp(req)}, ${req.headers['user-agent'] || null}, ${details ? JSON.stringify(details) : null})
63+
`;
64+
} catch (error) {
65+
console.error('Failed to log audit event:', error);
66+
}
67+
};
68+
69+
/**
70+
* Parse duration string to milliseconds
71+
*/
72+
export const parseDuration = (duration: string): number => {
73+
const match = duration.match(/^(\d+)([smhd])$/);
74+
if (!match) return 3600000; // default 1 hour
75+
76+
const value = parseInt(match[1]);
77+
const unit = match[2];
78+
79+
switch (unit) {
80+
case 's': return value * 1000;
81+
case 'm': return value * 60 * 1000;
82+
case 'h': return value * 60 * 60 * 1000;
83+
case 'd': return value * 24 * 60 * 60 * 1000;
84+
default: return 3600000;
85+
}
86+
};

0 commit comments

Comments
 (0)