137 lines
3.6 KiB
TypeScript
137 lines
3.6 KiB
TypeScript
import { SignJWT, jwtVerify } from 'jose';
|
|
import { cookies } from 'next/headers';
|
|
import { NextRequest } from 'next/server';
|
|
|
|
const secretKey = process.env.NEXTAUTH_SECRET;
|
|
|
|
if (!secretKey) {
|
|
throw new Error('NEXTAUTH_SECRET environment variable is not set');
|
|
}
|
|
|
|
const encodedKey = new TextEncoder().encode(secretKey);
|
|
|
|
export interface SessionPayload {
|
|
userId: string;
|
|
email: string;
|
|
role: 'user' | 'admin';
|
|
expiresAt: Date;
|
|
}
|
|
|
|
export async function encrypt(payload: SessionPayload) {
|
|
return new SignJWT({
|
|
userId: payload.userId,
|
|
email: payload.email,
|
|
role: payload.role,
|
|
expiresAt: payload.expiresAt.getTime(),
|
|
})
|
|
.setProtectedHeader({ alg: 'HS256' })
|
|
.setIssuedAt()
|
|
.setExpirationTime('7d')
|
|
.sign(encodedKey);
|
|
}
|
|
|
|
export async function decrypt(session: string | undefined = '') {
|
|
try {
|
|
if (!session) {
|
|
console.log('Failed to verify session: No session provided');
|
|
return null;
|
|
}
|
|
|
|
const { payload } = await jwtVerify(session, encodedKey, {
|
|
algorithms: ['HS256'],
|
|
});
|
|
|
|
const sessionData = {
|
|
userId: payload.userId as string,
|
|
email: payload.email as string,
|
|
role: payload.role as 'user' | 'admin',
|
|
expiresAt: new Date(payload.expiresAt as number),
|
|
};
|
|
|
|
// Check if session is expired
|
|
if (sessionData.expiresAt < new Date()) {
|
|
console.log('Failed to verify session: Session expired');
|
|
return null;
|
|
}
|
|
|
|
return sessionData;
|
|
} catch (error) {
|
|
console.log('Failed to verify session:', error instanceof Error ? error.message : 'Unknown error');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function createSession(payload: Omit<SessionPayload, 'expiresAt'>) {
|
|
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
const session = await encrypt({ ...payload, expiresAt });
|
|
|
|
const cookieStore = await cookies();
|
|
|
|
// For Cloudflare tunnel: external is HTTPS, internal is HTTP
|
|
// Use secure cookies when NEXTAUTH_URL is https (external URL)
|
|
const isSecure = process.env.NEXTAUTH_URL?.startsWith('https') ?? false;
|
|
|
|
const cookieOptions = {
|
|
httpOnly: true,
|
|
secure: isSecure,
|
|
expires: expiresAt,
|
|
sameSite: isSecure ? 'none' : 'lax', // none required for secure cross-site
|
|
path: '/',
|
|
} as const;
|
|
|
|
console.log('CREATE_SESSION: Setting cookie with options:', cookieOptions);
|
|
console.log('CREATE_SESSION: Environment:', {
|
|
NODE_ENV: process.env.NODE_ENV,
|
|
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
|
isSecure
|
|
});
|
|
|
|
cookieStore.set('session', session, cookieOptions);
|
|
}
|
|
|
|
export async function updateSession() {
|
|
const cookieStore = await cookies();
|
|
const session = cookieStore.get('session')?.value;
|
|
const payload = await decrypt(session);
|
|
|
|
if (!session || !payload) {
|
|
return null;
|
|
}
|
|
|
|
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
const newSession = await encrypt({ ...payload, expiresAt: expires });
|
|
|
|
const isSecure = process.env.NEXTAUTH_URL?.startsWith('https') ?? false;
|
|
|
|
cookieStore.set('session', newSession, {
|
|
httpOnly: true,
|
|
secure: isSecure,
|
|
expires: expires,
|
|
sameSite: isSecure ? 'none' : 'lax',
|
|
path: '/',
|
|
});
|
|
}
|
|
|
|
export async function deleteSession() {
|
|
const cookieStore = await cookies();
|
|
cookieStore.delete('session');
|
|
}
|
|
|
|
export async function getSession() {
|
|
const cookieStore = await cookies();
|
|
const session = cookieStore.get('session')?.value;
|
|
return await decrypt(session);
|
|
}
|
|
|
|
export async function verifySession() {
|
|
const cookieStore = await cookies();
|
|
const session = cookieStore.get('session')?.value;
|
|
const payload = await decrypt(session);
|
|
|
|
if (!payload) {
|
|
return { isAuth: false, userId: null, role: null };
|
|
}
|
|
|
|
return { isAuth: true, userId: payload.userId, role: payload.role };
|
|
}
|