pstream-backend/server/utils/auth.ts
2025-07-31 12:31:50 -06:00

134 lines
3.5 KiB
TypeScript

import { prisma } from './prisma';
import jwt from 'jsonwebtoken';
const { sign, verify } = jwt;
import { randomUUID } from 'crypto';
// 21 days in ms
const SESSION_EXPIRY_MS = 21 * 24 * 60 * 60 * 1000;
export function useAuth() {
const getSession = async (id: string) => {
const session = await prisma.sessions.findUnique({
where: { id },
});
if (!session) return null;
if (new Date(session.expires_at) < new Date()) return null;
return session;
};
const getSessionAndBump = async (id: string) => {
const session = await getSession(id);
if (!session) return null;
const now = new Date();
const expiryDate = new Date(now.getTime() + SESSION_EXPIRY_MS);
return await prisma.sessions.update({
where: { id },
data: {
accessed_at: now,
expires_at: expiryDate,
},
});
};
const makeSession = async (user: string, device: string, userAgent?: string) => {
if (!userAgent) throw new Error('No useragent provided');
const now = new Date();
const expiryDate = new Date(now.getTime() + SESSION_EXPIRY_MS);
return await prisma.sessions.create({
data: {
id: randomUUID(),
user,
device,
user_agent: userAgent,
created_at: now,
accessed_at: now,
expires_at: expiryDate,
},
});
};
const makeSessionToken = (session: { id: string }) => {
const runtimeConfig = useRuntimeConfig();
const cryptoSecret = runtimeConfig.cryptoSecret || process.env.CRYPTO_SECRET;
if (!cryptoSecret) {
console.error('CRYPTO_SECRET is missing from both runtime config and environment');
console.error('Available runtime config keys:', Object.keys(runtimeConfig));
console.error('Environment variables:', {
CRYPTO_SECRET: process.env.CRYPTO_SECRET ? 'SET' : 'NOT SET',
NODE_ENV: process.env.NODE_ENV,
});
throw new Error('CRYPTO_SECRET environment variable is not set');
}
return sign({ sid: session.id }, cryptoSecret, {
algorithm: 'HS256',
});
};
const verifySessionToken = (token: string) => {
try {
const runtimeConfig = useRuntimeConfig();
const cryptoSecret = runtimeConfig.cryptoSecret || process.env.CRYPTO_SECRET;
if (!cryptoSecret) {
console.error('CRYPTO_SECRET is missing for token verification');
return null;
}
const payload = verify(token, cryptoSecret, {
algorithms: ['HS256'],
});
if (typeof payload === 'string') return null;
return payload as { sid: string };
} catch {
return null;
}
};
const getCurrentSession = async () => {
const event = useEvent();
const authHeader = getRequestHeader(event, 'authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw createError({
statusCode: 401,
message: 'Unauthorized',
});
}
const token = authHeader.split(' ')[1];
const payload = verifySessionToken(token);
if (!payload) {
throw createError({
statusCode: 401,
message: 'Invalid token',
});
}
const session = await getSessionAndBump(payload.sid);
if (!session) {
throw createError({
statusCode: 401,
message: 'Session not found or expired',
});
}
return session;
};
return {
getSession,
getSessionAndBump,
makeSession,
makeSessionToken,
verifySessionToken,
getCurrentSession,
};
}