/* * This file is part of a proprietary work. * * Copyright (c) 2025 Fossorial, Inc. * All rights reserved. * * This file is licensed under the Fossorial Commercial License. * You may not use this file except in compliance with the License. * Unauthorized use, copying, modification, or distribution is strictly prohibited. * * This file is not licensed under the AGPLv3. */ import { encodeHexLowerCase, } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; import { RemoteExitNode, remoteExitNodes, remoteExitNodeSessions, RemoteExitNodeSession } from "@server/db"; import { db } from "@server/db"; import { eq } from "drizzle-orm"; export const EXPIRES = 1000 * 60 * 60 * 24 * 30; export async function createRemoteExitNodeSession( token: string, remoteExitNodeId: string, ): Promise { const sessionId = encodeHexLowerCase( sha256(new TextEncoder().encode(token)), ); const session: RemoteExitNodeSession = { sessionId: sessionId, remoteExitNodeId, expiresAt: new Date(Date.now() + EXPIRES).getTime(), }; await db.insert(remoteExitNodeSessions).values(session); return session; } export async function validateRemoteExitNodeSessionToken( token: string, ): Promise { const sessionId = encodeHexLowerCase( sha256(new TextEncoder().encode(token)), ); const result = await db .select({ remoteExitNode: remoteExitNodes, session: remoteExitNodeSessions }) .from(remoteExitNodeSessions) .innerJoin(remoteExitNodes, eq(remoteExitNodeSessions.remoteExitNodeId, remoteExitNodes.remoteExitNodeId)) .where(eq(remoteExitNodeSessions.sessionId, sessionId)); if (result.length < 1) { return { session: null, remoteExitNode: null }; } const { remoteExitNode, session } = result[0]; if (Date.now() >= session.expiresAt) { await db .delete(remoteExitNodeSessions) .where(eq(remoteExitNodeSessions.sessionId, session.sessionId)); return { session: null, remoteExitNode: null }; } if (Date.now() >= session.expiresAt - (EXPIRES / 2)) { session.expiresAt = new Date( Date.now() + EXPIRES, ).getTime(); await db .update(remoteExitNodeSessions) .set({ expiresAt: session.expiresAt, }) .where(eq(remoteExitNodeSessions.sessionId, session.sessionId)); } return { session, remoteExitNode }; } export async function invalidateRemoteExitNodeSession(sessionId: string): Promise { await db.delete(remoteExitNodeSessions).where(eq(remoteExitNodeSessions.sessionId, sessionId)); } export async function invalidateAllRemoteExitNodeSessions(remoteExitNodeId: string): Promise { await db.delete(remoteExitNodeSessions).where(eq(remoteExitNodeSessions.remoteExitNodeId, remoteExitNodeId)); } export type SessionValidationResult = | { session: RemoteExitNodeSession; remoteExitNode: RemoteExitNode } | { session: null; remoteExitNode: null };