136 lines
3.0 KiB
TypeScript
136 lines
3.0 KiB
TypeScript
|
|
/**
|
||
|
|
* Session model for Stack.Gallery Registry
|
||
|
|
*/
|
||
|
|
|
||
|
|
import * as plugins from '../plugins.ts';
|
||
|
|
import type { ISession } from '../interfaces/auth.interfaces.ts';
|
||
|
|
import { getDb } from './db.ts';
|
||
|
|
|
||
|
|
@plugins.smartdata.Collection(() => getDb())
|
||
|
|
export class Session
|
||
|
|
extends plugins.smartdata.SmartDataDbDoc<Session, Session>
|
||
|
|
implements ISession
|
||
|
|
{
|
||
|
|
@plugins.smartdata.unI()
|
||
|
|
public id: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.index()
|
||
|
|
public userId: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public userAgent: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public ipAddress: string = '';
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.index()
|
||
|
|
public isValid: boolean = true;
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public invalidatedAt?: Date;
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public invalidatedReason?: string;
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
public lastActivityAt: Date = new Date();
|
||
|
|
|
||
|
|
@plugins.smartdata.svDb()
|
||
|
|
@plugins.smartdata.index()
|
||
|
|
public createdAt: Date = new Date();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a new session
|
||
|
|
*/
|
||
|
|
public static async createSession(data: {
|
||
|
|
userId: string;
|
||
|
|
userAgent: string;
|
||
|
|
ipAddress: string;
|
||
|
|
}): Promise<Session> {
|
||
|
|
const session = new Session();
|
||
|
|
session.id = await Session.getNewId();
|
||
|
|
session.userId = data.userId;
|
||
|
|
session.userAgent = data.userAgent;
|
||
|
|
session.ipAddress = data.ipAddress;
|
||
|
|
session.isValid = true;
|
||
|
|
session.lastActivityAt = new Date();
|
||
|
|
session.createdAt = new Date();
|
||
|
|
await session.save();
|
||
|
|
return session;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find valid session by ID
|
||
|
|
*/
|
||
|
|
public static async findValidSession(sessionId: string): Promise<Session | null> {
|
||
|
|
const session = await Session.getInstance({
|
||
|
|
id: sessionId,
|
||
|
|
isValid: true,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!session) return null;
|
||
|
|
|
||
|
|
// Check if session is expired (7 days)
|
||
|
|
const maxAge = 7 * 24 * 60 * 60 * 1000;
|
||
|
|
if (Date.now() - session.createdAt.getTime() > maxAge) {
|
||
|
|
await session.invalidate('expired');
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return session;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all valid sessions for a user
|
||
|
|
*/
|
||
|
|
public static async getUserSessions(userId: string): Promise<Session[]> {
|
||
|
|
return await Session.getInstances({
|
||
|
|
userId,
|
||
|
|
isValid: true,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Invalidate all sessions for a user
|
||
|
|
*/
|
||
|
|
public static async invalidateAllUserSessions(
|
||
|
|
userId: string,
|
||
|
|
reason: string = 'logout_all'
|
||
|
|
): Promise<number> {
|
||
|
|
const sessions = await Session.getUserSessions(userId);
|
||
|
|
for (const session of sessions) {
|
||
|
|
await session.invalidate(reason);
|
||
|
|
}
|
||
|
|
return sessions.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Invalidate this session
|
||
|
|
*/
|
||
|
|
public async invalidate(reason: string): Promise<void> {
|
||
|
|
this.isValid = false;
|
||
|
|
this.invalidatedAt = new Date();
|
||
|
|
this.invalidatedReason = reason;
|
||
|
|
await this.save();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update last activity
|
||
|
|
*/
|
||
|
|
public async touchActivity(): Promise<void> {
|
||
|
|
this.lastActivityAt = new Date();
|
||
|
|
await this.save();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Lifecycle hook
|
||
|
|
*/
|
||
|
|
public async beforeSave(): Promise<void> {
|
||
|
|
if (!this.id) {
|
||
|
|
this.id = await Session.getNewId();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|