archicore 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/interactive.js +145 -61
- package/dist/github/github-service.d.ts +5 -1
- package/dist/github/github-service.js +21 -3
- package/dist/semantic-memory/embedding-service.d.ts +8 -1
- package/dist/semantic-memory/embedding-service.js +141 -47
- package/dist/server/index.js +66 -1
- package/dist/server/routes/admin.js +149 -1
- package/dist/server/routes/auth.js +46 -0
- package/dist/server/routes/github.js +17 -4
- package/dist/server/services/audit-service.d.ts +88 -0
- package/dist/server/services/audit-service.js +380 -0
- package/dist/server/services/auth-service.d.ts +11 -5
- package/dist/server/services/auth-service.js +299 -52
- package/dist/server/services/cache.d.ts +77 -0
- package/dist/server/services/cache.js +245 -0
- package/dist/server/services/database.d.ts +43 -0
- package/dist/server/services/database.js +221 -0
- package/package.json +17 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Log Service for ArchiCore
|
|
3
|
+
*
|
|
4
|
+
* Logs user actions for security and compliance purposes
|
|
5
|
+
* Supports PostgreSQL (primary) with JSON file fallback
|
|
6
|
+
*/
|
|
7
|
+
export type AuditAction = 'auth.login' | 'auth.logout' | 'auth.register' | 'auth.password_change' | 'auth.device_login' | 'user.update' | 'user.tier_change' | 'user.delete' | 'project.create' | 'project.delete' | 'project.analyze' | 'project.simulate' | 'project.ask' | 'project.docs' | 'github.connect' | 'github.disconnect' | 'github.repo_connect' | 'github.repo_disconnect' | 'github.webhook_received' | 'api.key_create' | 'api.key_revoke' | 'api.request' | 'admin.login' | 'admin.user_update' | 'admin.user_delete' | 'admin.tier_change' | 'admin.view_logs';
|
|
8
|
+
export type AuditSeverity = 'info' | 'warning' | 'critical';
|
|
9
|
+
export interface AuditLogEntry {
|
|
10
|
+
id: string;
|
|
11
|
+
timestamp: Date;
|
|
12
|
+
userId?: string;
|
|
13
|
+
username?: string;
|
|
14
|
+
action: AuditAction;
|
|
15
|
+
severity: AuditSeverity;
|
|
16
|
+
ip?: string;
|
|
17
|
+
userAgent?: string;
|
|
18
|
+
details?: Record<string, any>;
|
|
19
|
+
success: boolean;
|
|
20
|
+
errorMessage?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AuditLogQuery {
|
|
23
|
+
userId?: string;
|
|
24
|
+
action?: AuditAction;
|
|
25
|
+
severity?: AuditSeverity;
|
|
26
|
+
startDate?: Date;
|
|
27
|
+
endDate?: Date;
|
|
28
|
+
success?: boolean;
|
|
29
|
+
limit?: number;
|
|
30
|
+
offset?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class AuditService {
|
|
33
|
+
private dataDir;
|
|
34
|
+
private logs;
|
|
35
|
+
private jsonInitialized;
|
|
36
|
+
private saveTimeout;
|
|
37
|
+
constructor(dataDir?: string);
|
|
38
|
+
private useDatabase;
|
|
39
|
+
private ensureJsonInitialized;
|
|
40
|
+
private saveJson;
|
|
41
|
+
private generateId;
|
|
42
|
+
private getSeverity;
|
|
43
|
+
private rowToEntry;
|
|
44
|
+
/**
|
|
45
|
+
* Log an action
|
|
46
|
+
*/
|
|
47
|
+
log(params: {
|
|
48
|
+
userId?: string;
|
|
49
|
+
username?: string;
|
|
50
|
+
action: AuditAction;
|
|
51
|
+
ip?: string;
|
|
52
|
+
userAgent?: string;
|
|
53
|
+
details?: Record<string, any>;
|
|
54
|
+
success?: boolean;
|
|
55
|
+
errorMessage?: string;
|
|
56
|
+
}): Promise<AuditLogEntry>;
|
|
57
|
+
/**
|
|
58
|
+
* Query audit logs
|
|
59
|
+
*/
|
|
60
|
+
query(params?: AuditLogQuery): Promise<{
|
|
61
|
+
logs: AuditLogEntry[];
|
|
62
|
+
total: number;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Get recent logs for a user
|
|
66
|
+
*/
|
|
67
|
+
getUserLogs(userId: string, limit?: number): Promise<AuditLogEntry[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Get failed login attempts for an IP
|
|
70
|
+
*/
|
|
71
|
+
getFailedLogins(ip: string, since: Date): Promise<number>;
|
|
72
|
+
/**
|
|
73
|
+
* Get statistics
|
|
74
|
+
*/
|
|
75
|
+
getStats(days?: number): Promise<{
|
|
76
|
+
totalActions: number;
|
|
77
|
+
uniqueUsers: number;
|
|
78
|
+
failedLogins: number;
|
|
79
|
+
criticalEvents: number;
|
|
80
|
+
actionCounts: Record<string, number>;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Clear old logs (retention policy)
|
|
84
|
+
*/
|
|
85
|
+
cleanup(retentionDays?: number): Promise<number>;
|
|
86
|
+
}
|
|
87
|
+
export declare const auditService: AuditService;
|
|
88
|
+
//# sourceMappingURL=audit-service.d.ts.map
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Log Service for ArchiCore
|
|
3
|
+
*
|
|
4
|
+
* Logs user actions for security and compliance purposes
|
|
5
|
+
* Supports PostgreSQL (primary) with JSON file fallback
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { Logger } from '../../utils/logger.js';
|
|
10
|
+
import { db } from './database.js';
|
|
11
|
+
const DATA_DIR = '.archicore';
|
|
12
|
+
const AUDIT_FILE = 'audit-logs.json';
|
|
13
|
+
const MAX_LOGS_IN_MEMORY = 10000;
|
|
14
|
+
const MAX_LOGS_IN_FILE = 100000;
|
|
15
|
+
// Singleton instance
|
|
16
|
+
let instance = null;
|
|
17
|
+
export class AuditService {
|
|
18
|
+
dataDir = DATA_DIR;
|
|
19
|
+
// JSON fallback
|
|
20
|
+
logs = [];
|
|
21
|
+
jsonInitialized = false;
|
|
22
|
+
saveTimeout = null;
|
|
23
|
+
constructor(dataDir = DATA_DIR) {
|
|
24
|
+
if (instance) {
|
|
25
|
+
return instance;
|
|
26
|
+
}
|
|
27
|
+
this.dataDir = dataDir;
|
|
28
|
+
instance = this;
|
|
29
|
+
}
|
|
30
|
+
useDatabase() {
|
|
31
|
+
return db.isAvailable();
|
|
32
|
+
}
|
|
33
|
+
// ========== JSON FALLBACK METHODS ==========
|
|
34
|
+
async ensureJsonInitialized() {
|
|
35
|
+
if (this.jsonInitialized)
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
await mkdir(this.dataDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
try {
|
|
42
|
+
const path = join(this.dataDir, AUDIT_FILE);
|
|
43
|
+
const data = await readFile(path, 'utf-8');
|
|
44
|
+
const parsed = JSON.parse(data);
|
|
45
|
+
this.logs = parsed.logs.map(log => ({
|
|
46
|
+
...log,
|
|
47
|
+
timestamp: new Date(log.timestamp)
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
this.logs = [];
|
|
52
|
+
}
|
|
53
|
+
this.jsonInitialized = true;
|
|
54
|
+
}
|
|
55
|
+
async saveJson() {
|
|
56
|
+
if (this.saveTimeout) {
|
|
57
|
+
clearTimeout(this.saveTimeout);
|
|
58
|
+
}
|
|
59
|
+
this.saveTimeout = setTimeout(async () => {
|
|
60
|
+
try {
|
|
61
|
+
const path = join(this.dataDir, AUDIT_FILE);
|
|
62
|
+
if (this.logs.length > MAX_LOGS_IN_FILE) {
|
|
63
|
+
this.logs = this.logs.slice(-MAX_LOGS_IN_FILE);
|
|
64
|
+
}
|
|
65
|
+
const data = {
|
|
66
|
+
logs: this.logs,
|
|
67
|
+
lastRotation: new Date()
|
|
68
|
+
};
|
|
69
|
+
await writeFile(path, JSON.stringify(data, null, 2));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
Logger.error('Failed to save audit logs:', error);
|
|
73
|
+
}
|
|
74
|
+
}, 1000);
|
|
75
|
+
}
|
|
76
|
+
// ========== HELPER METHODS ==========
|
|
77
|
+
generateId() {
|
|
78
|
+
return 'audit_' + Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
|
|
79
|
+
}
|
|
80
|
+
getSeverity(action, success) {
|
|
81
|
+
if (action.startsWith('admin.') ||
|
|
82
|
+
action === 'auth.password_change' ||
|
|
83
|
+
action === 'user.delete' ||
|
|
84
|
+
action === 'api.key_revoke') {
|
|
85
|
+
return success ? 'warning' : 'critical';
|
|
86
|
+
}
|
|
87
|
+
if ((action === 'auth.login' && !success) ||
|
|
88
|
+
action === 'auth.register' ||
|
|
89
|
+
action === 'project.delete' ||
|
|
90
|
+
action === 'github.disconnect') {
|
|
91
|
+
return 'warning';
|
|
92
|
+
}
|
|
93
|
+
return 'info';
|
|
94
|
+
}
|
|
95
|
+
rowToEntry(row) {
|
|
96
|
+
return {
|
|
97
|
+
id: row.id,
|
|
98
|
+
timestamp: row.created_at,
|
|
99
|
+
userId: row.user_id || undefined,
|
|
100
|
+
username: row.username || undefined,
|
|
101
|
+
action: row.action,
|
|
102
|
+
severity: row.severity,
|
|
103
|
+
ip: row.ip || undefined,
|
|
104
|
+
userAgent: row.user_agent || undefined,
|
|
105
|
+
details: row.details || undefined,
|
|
106
|
+
success: row.success,
|
|
107
|
+
errorMessage: row.error_message || undefined
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ========== PUBLIC API ==========
|
|
111
|
+
/**
|
|
112
|
+
* Log an action
|
|
113
|
+
*/
|
|
114
|
+
async log(params) {
|
|
115
|
+
const success = params.success !== false;
|
|
116
|
+
const id = this.generateId();
|
|
117
|
+
const severity = this.getSeverity(params.action, success);
|
|
118
|
+
if (this.useDatabase()) {
|
|
119
|
+
try {
|
|
120
|
+
await db.query(`INSERT INTO audit_logs (id, user_id, username, action, severity, ip, user_agent, details, success, error_message)
|
|
121
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
|
|
122
|
+
id,
|
|
123
|
+
params.userId || null,
|
|
124
|
+
params.username || null,
|
|
125
|
+
params.action,
|
|
126
|
+
severity,
|
|
127
|
+
params.ip || null,
|
|
128
|
+
params.userAgent || null,
|
|
129
|
+
params.details ? JSON.stringify(params.details) : null,
|
|
130
|
+
success,
|
|
131
|
+
params.errorMessage || null
|
|
132
|
+
]);
|
|
133
|
+
const entry = {
|
|
134
|
+
id,
|
|
135
|
+
timestamp: new Date(),
|
|
136
|
+
userId: params.userId,
|
|
137
|
+
username: params.username,
|
|
138
|
+
action: params.action,
|
|
139
|
+
severity,
|
|
140
|
+
ip: params.ip,
|
|
141
|
+
userAgent: params.userAgent,
|
|
142
|
+
details: params.details,
|
|
143
|
+
success,
|
|
144
|
+
errorMessage: params.errorMessage
|
|
145
|
+
};
|
|
146
|
+
if (entry.severity === 'critical') {
|
|
147
|
+
Logger.warn(`[AUDIT] Critical event: ${entry.action} by ${entry.username || entry.userId || 'unknown'}`);
|
|
148
|
+
}
|
|
149
|
+
return entry;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
Logger.error('Audit log error:', error);
|
|
153
|
+
// Fall through to JSON fallback
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// JSON fallback
|
|
157
|
+
await this.ensureJsonInitialized();
|
|
158
|
+
const entry = {
|
|
159
|
+
id,
|
|
160
|
+
timestamp: new Date(),
|
|
161
|
+
userId: params.userId,
|
|
162
|
+
username: params.username,
|
|
163
|
+
action: params.action,
|
|
164
|
+
severity,
|
|
165
|
+
ip: params.ip,
|
|
166
|
+
userAgent: params.userAgent,
|
|
167
|
+
details: params.details,
|
|
168
|
+
success,
|
|
169
|
+
errorMessage: params.errorMessage
|
|
170
|
+
};
|
|
171
|
+
this.logs.push(entry);
|
|
172
|
+
if (this.logs.length > MAX_LOGS_IN_MEMORY) {
|
|
173
|
+
this.logs = this.logs.slice(-MAX_LOGS_IN_MEMORY);
|
|
174
|
+
}
|
|
175
|
+
await this.saveJson();
|
|
176
|
+
if (entry.severity === 'critical') {
|
|
177
|
+
Logger.warn(`[AUDIT] Critical event: ${entry.action} by ${entry.username || entry.userId || 'unknown'}`);
|
|
178
|
+
}
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Query audit logs
|
|
183
|
+
*/
|
|
184
|
+
async query(params = {}) {
|
|
185
|
+
if (this.useDatabase()) {
|
|
186
|
+
try {
|
|
187
|
+
const conditions = ['1=1'];
|
|
188
|
+
const values = [];
|
|
189
|
+
let paramIndex = 1;
|
|
190
|
+
if (params.userId) {
|
|
191
|
+
conditions.push(`user_id = $${paramIndex++}`);
|
|
192
|
+
values.push(params.userId);
|
|
193
|
+
}
|
|
194
|
+
if (params.action) {
|
|
195
|
+
conditions.push(`action = $${paramIndex++}`);
|
|
196
|
+
values.push(params.action);
|
|
197
|
+
}
|
|
198
|
+
if (params.severity) {
|
|
199
|
+
conditions.push(`severity = $${paramIndex++}`);
|
|
200
|
+
values.push(params.severity);
|
|
201
|
+
}
|
|
202
|
+
if (params.success !== undefined) {
|
|
203
|
+
conditions.push(`success = $${paramIndex++}`);
|
|
204
|
+
values.push(params.success);
|
|
205
|
+
}
|
|
206
|
+
if (params.startDate) {
|
|
207
|
+
conditions.push(`created_at >= $${paramIndex++}`);
|
|
208
|
+
values.push(params.startDate);
|
|
209
|
+
}
|
|
210
|
+
if (params.endDate) {
|
|
211
|
+
conditions.push(`created_at <= $${paramIndex++}`);
|
|
212
|
+
values.push(params.endDate);
|
|
213
|
+
}
|
|
214
|
+
const whereClause = conditions.join(' AND ');
|
|
215
|
+
// Get total count
|
|
216
|
+
const countResult = await db.query(`SELECT COUNT(*) as count FROM audit_logs WHERE ${whereClause}`, values);
|
|
217
|
+
const total = parseInt(countResult.rows[0].count, 10);
|
|
218
|
+
// Get paginated results
|
|
219
|
+
const limit = params.limit || 50;
|
|
220
|
+
const offset = params.offset || 0;
|
|
221
|
+
const result = await db.query(`SELECT * FROM audit_logs WHERE ${whereClause} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`, [...values, limit, offset]);
|
|
222
|
+
return {
|
|
223
|
+
logs: result.rows.map(row => this.rowToEntry(row)),
|
|
224
|
+
total
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
Logger.error('Audit query error:', error);
|
|
229
|
+
return { logs: [], total: 0 };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// JSON fallback
|
|
233
|
+
await this.ensureJsonInitialized();
|
|
234
|
+
let filtered = [...this.logs];
|
|
235
|
+
if (params.userId) {
|
|
236
|
+
filtered = filtered.filter(log => log.userId === params.userId);
|
|
237
|
+
}
|
|
238
|
+
if (params.action) {
|
|
239
|
+
filtered = filtered.filter(log => log.action === params.action);
|
|
240
|
+
}
|
|
241
|
+
if (params.severity) {
|
|
242
|
+
filtered = filtered.filter(log => log.severity === params.severity);
|
|
243
|
+
}
|
|
244
|
+
if (params.success !== undefined) {
|
|
245
|
+
filtered = filtered.filter(log => log.success === params.success);
|
|
246
|
+
}
|
|
247
|
+
if (params.startDate) {
|
|
248
|
+
filtered = filtered.filter(log => log.timestamp >= params.startDate);
|
|
249
|
+
}
|
|
250
|
+
if (params.endDate) {
|
|
251
|
+
filtered = filtered.filter(log => log.timestamp <= params.endDate);
|
|
252
|
+
}
|
|
253
|
+
filtered.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
254
|
+
const total = filtered.length;
|
|
255
|
+
const offset = params.offset || 0;
|
|
256
|
+
const limit = params.limit || 50;
|
|
257
|
+
filtered = filtered.slice(offset, offset + limit);
|
|
258
|
+
return { logs: filtered, total };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get recent logs for a user
|
|
262
|
+
*/
|
|
263
|
+
async getUserLogs(userId, limit = 20) {
|
|
264
|
+
const result = await this.query({ userId, limit });
|
|
265
|
+
return result.logs;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get failed login attempts for an IP
|
|
269
|
+
*/
|
|
270
|
+
async getFailedLogins(ip, since) {
|
|
271
|
+
if (this.useDatabase()) {
|
|
272
|
+
try {
|
|
273
|
+
const result = await db.query(`SELECT COUNT(*) as count FROM audit_logs WHERE action = $1 AND success = false AND ip = $2 AND created_at >= $3`, ['auth.login', ip, since]);
|
|
274
|
+
return parseInt(result.rows[0].count, 10);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
Logger.error('Failed logins query error:', error);
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
await this.ensureJsonInitialized();
|
|
282
|
+
return this.logs.filter(log => log.action === 'auth.login' &&
|
|
283
|
+
!log.success &&
|
|
284
|
+
log.ip === ip &&
|
|
285
|
+
log.timestamp >= since).length;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get statistics
|
|
289
|
+
*/
|
|
290
|
+
async getStats(days = 7) {
|
|
291
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
292
|
+
if (this.useDatabase()) {
|
|
293
|
+
try {
|
|
294
|
+
// Total actions
|
|
295
|
+
const totalResult = await db.query('SELECT COUNT(*) as count FROM audit_logs WHERE created_at >= $1', [since]);
|
|
296
|
+
const totalActions = parseInt(totalResult.rows[0].count, 10);
|
|
297
|
+
// Unique users
|
|
298
|
+
const usersResult = await db.query('SELECT COUNT(DISTINCT user_id) as count FROM audit_logs WHERE created_at >= $1 AND user_id IS NOT NULL', [since]);
|
|
299
|
+
const uniqueUsers = parseInt(usersResult.rows[0].count, 10);
|
|
300
|
+
// Failed logins
|
|
301
|
+
const failedResult = await db.query(`SELECT COUNT(*) as count FROM audit_logs WHERE action = 'auth.login' AND success = false AND created_at >= $1`, [since]);
|
|
302
|
+
const failedLogins = parseInt(failedResult.rows[0].count, 10);
|
|
303
|
+
// Critical events
|
|
304
|
+
const criticalResult = await db.query(`SELECT COUNT(*) as count FROM audit_logs WHERE severity = 'critical' AND created_at >= $1`, [since]);
|
|
305
|
+
const criticalEvents = parseInt(criticalResult.rows[0].count, 10);
|
|
306
|
+
// Action counts
|
|
307
|
+
const actionResult = await db.query('SELECT action, COUNT(*) as count FROM audit_logs WHERE created_at >= $1 GROUP BY action', [since]);
|
|
308
|
+
const actionCounts = {};
|
|
309
|
+
for (const row of actionResult.rows) {
|
|
310
|
+
actionCounts[row.action] = parseInt(row.count, 10);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
totalActions,
|
|
314
|
+
uniqueUsers,
|
|
315
|
+
failedLogins,
|
|
316
|
+
criticalEvents,
|
|
317
|
+
actionCounts
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
Logger.error('Audit stats error:', error);
|
|
322
|
+
return {
|
|
323
|
+
totalActions: 0,
|
|
324
|
+
uniqueUsers: 0,
|
|
325
|
+
failedLogins: 0,
|
|
326
|
+
criticalEvents: 0,
|
|
327
|
+
actionCounts: {}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// JSON fallback
|
|
332
|
+
await this.ensureJsonInitialized();
|
|
333
|
+
const recentLogs = this.logs.filter(log => log.timestamp >= since);
|
|
334
|
+
const uniqueUsers = new Set(recentLogs.map(log => log.userId).filter(Boolean));
|
|
335
|
+
const actionCounts = {};
|
|
336
|
+
for (const log of recentLogs) {
|
|
337
|
+
actionCounts[log.action] = (actionCounts[log.action] || 0) + 1;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
totalActions: recentLogs.length,
|
|
341
|
+
uniqueUsers: uniqueUsers.size,
|
|
342
|
+
failedLogins: recentLogs.filter(log => log.action === 'auth.login' && !log.success).length,
|
|
343
|
+
criticalEvents: recentLogs.filter(log => log.severity === 'critical').length,
|
|
344
|
+
actionCounts
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Clear old logs (retention policy)
|
|
349
|
+
*/
|
|
350
|
+
async cleanup(retentionDays = 90) {
|
|
351
|
+
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
|
|
352
|
+
if (this.useDatabase()) {
|
|
353
|
+
try {
|
|
354
|
+
const result = await db.query('DELETE FROM audit_logs WHERE created_at < $1', [cutoff]);
|
|
355
|
+
const removed = result.rowCount ?? 0;
|
|
356
|
+
if (removed > 0) {
|
|
357
|
+
Logger.info(`Audit logs cleanup: removed ${removed} old entries`);
|
|
358
|
+
}
|
|
359
|
+
return removed;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
Logger.error('Audit cleanup error:', error);
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// JSON fallback
|
|
367
|
+
await this.ensureJsonInitialized();
|
|
368
|
+
const before = this.logs.length;
|
|
369
|
+
this.logs = this.logs.filter(log => log.timestamp >= cutoff);
|
|
370
|
+
const removed = before - this.logs.length;
|
|
371
|
+
if (removed > 0) {
|
|
372
|
+
await this.saveJson();
|
|
373
|
+
Logger.info(`Audit logs cleanup: removed ${removed} old entries`);
|
|
374
|
+
}
|
|
375
|
+
return removed;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Export singleton instance
|
|
379
|
+
export const auditService = new AuditService();
|
|
380
|
+
//# sourceMappingURL=audit-service.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authentication Service for ArchiCore
|
|
3
|
+
*
|
|
4
|
+
* Supports PostgreSQL (primary) with JSON file fallback
|
|
3
5
|
*/
|
|
4
6
|
import { User, SubscriptionTier, AuthResponse } from '../../types/user.js';
|
|
5
7
|
export declare class AuthService {
|
|
@@ -7,13 +9,11 @@ export declare class AuthService {
|
|
|
7
9
|
private dataDir;
|
|
8
10
|
private users;
|
|
9
11
|
private sessions;
|
|
10
|
-
private
|
|
12
|
+
private jsonInitialized;
|
|
11
13
|
constructor(dataDir?: string);
|
|
12
|
-
/**
|
|
13
|
-
* Get singleton instance of AuthService
|
|
14
|
-
*/
|
|
15
14
|
static getInstance(): AuthService;
|
|
16
|
-
private
|
|
15
|
+
private useDatabase;
|
|
16
|
+
private ensureJsonInitialized;
|
|
17
17
|
private createDefaultAdmin;
|
|
18
18
|
private saveUsers;
|
|
19
19
|
private saveSessions;
|
|
@@ -21,6 +21,8 @@ export declare class AuthService {
|
|
|
21
21
|
private createEmptyUsage;
|
|
22
22
|
private generateToken;
|
|
23
23
|
private sanitizeUser;
|
|
24
|
+
private rowToUser;
|
|
25
|
+
private dbCreateDefaultAdmin;
|
|
24
26
|
register(email: string, username: string, password: string): Promise<AuthResponse>;
|
|
25
27
|
login(email: string, password: string): Promise<AuthResponse>;
|
|
26
28
|
oauthLogin(provider: 'github' | 'google', profile: {
|
|
@@ -41,5 +43,9 @@ export declare class AuthService {
|
|
|
41
43
|
getAllUsers(): Promise<Omit<User, 'passwordHash'>[]>;
|
|
42
44
|
deleteUser(userId: string): Promise<boolean>;
|
|
43
45
|
updateUser(userId: string, updates: Partial<Pick<User, 'username' | 'email' | 'avatar'>>): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Initialize database and create default admin if needed
|
|
48
|
+
*/
|
|
49
|
+
initDatabase(): Promise<void>;
|
|
44
50
|
}
|
|
45
51
|
//# sourceMappingURL=auth-service.d.ts.map
|