checkpoint
This commit is contained in:
@@ -1,8 +1,136 @@
|
||||
import type { H3Event } from "vinxi/http";
|
||||
import { getCookie, setCookie } from "vinxi/http";
|
||||
import { OAuth2Client } from "google-auth-library";
|
||||
import type { Row } from "@libsql/client/web";
|
||||
import { SignJWT, jwtVerify } from "jose";
|
||||
import { env } from "~/env/server";
|
||||
import { getAuthSession } from "./session-helpers";
|
||||
import { ConnectionFactory } from "./database";
|
||||
import { AUTH_CONFIG, expiryToSeconds, getAccessTokenExpiry } from "~/config";
|
||||
|
||||
export const authCookieName = "auth_token";
|
||||
|
||||
type AuthTokenPayload = {
|
||||
sub: string;
|
||||
email: string | null;
|
||||
isAdmin: boolean;
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
};
|
||||
|
||||
function getAuthCookieOptions(rememberMe: boolean) {
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure: env.NODE_ENV === "production",
|
||||
sameSite: "lax" as const,
|
||||
path: "/",
|
||||
maxAge: rememberMe
|
||||
? expiryToSeconds(AUTH_CONFIG.ACCESS_TOKEN_EXPIRY_LONG)
|
||||
: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function getAuthHeaderToken(event: H3Event): string | null {
|
||||
const requestHeader = event.request?.headers?.get?.("authorization") || null;
|
||||
const eventHeader = event.headers
|
||||
? typeof (event.headers as any).get === "function"
|
||||
? (event.headers as any).get("authorization")
|
||||
: (event.headers as any).authorization
|
||||
: null;
|
||||
const nodeHeader = event.node?.req?.headers?.authorization || null;
|
||||
const header = requestHeader || eventHeader || nodeHeader || null;
|
||||
if (!header) return null;
|
||||
const normalized = header.trim();
|
||||
if (!normalized.toLowerCase().startsWith("bearer ")) return null;
|
||||
return normalized.slice("Bearer ".length).trim();
|
||||
}
|
||||
|
||||
export function getAuthTokenFromEvent(event: H3Event): string | null {
|
||||
return getCookie(event, authCookieName) || getAuthHeaderToken(event);
|
||||
}
|
||||
|
||||
export async function verifyAuthToken(
|
||||
token: string
|
||||
): Promise<AuthTokenPayload | null> {
|
||||
try {
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
const { payload } = await jwtVerify(token, secret);
|
||||
if (!payload.sub) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
sub: payload.sub as string,
|
||||
email: (payload.email as string | null) ?? null,
|
||||
isAdmin: (payload.isAdmin as boolean) ?? false,
|
||||
iat: payload.iat,
|
||||
exp: payload.exp
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Auth token verification failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthPayloadFromEvent(
|
||||
event: H3Event
|
||||
): Promise<AuthTokenPayload | null> {
|
||||
const token = getAuthTokenFromEvent(event);
|
||||
if (!token) return null;
|
||||
return verifyAuthToken(token);
|
||||
}
|
||||
|
||||
export async function issueAuthToken({
|
||||
event,
|
||||
userId,
|
||||
rememberMe
|
||||
}: {
|
||||
event: H3Event;
|
||||
userId: string;
|
||||
rememberMe: boolean;
|
||||
}): Promise<string> {
|
||||
const conn = ConnectionFactory();
|
||||
const result = await conn.execute({
|
||||
sql: "SELECT email, is_admin FROM User WHERE id = ?",
|
||||
args: [userId]
|
||||
});
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
const row = result.rows[0] as { email?: string | null; is_admin?: number };
|
||||
const isAdmin = row.is_admin === 1;
|
||||
const email = row.email ?? null;
|
||||
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
const expiry = rememberMe
|
||||
? AUTH_CONFIG.ACCESS_TOKEN_EXPIRY_LONG
|
||||
: getAccessTokenExpiry();
|
||||
|
||||
const token = await new SignJWT({ email, isAdmin })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setSubject(userId)
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(expiry)
|
||||
.sign(secret);
|
||||
|
||||
setCookie(event, authCookieName, token, getAuthCookieOptions(rememberMe));
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function clearAuthToken(event: H3Event): void {
|
||||
setCookie(event, authCookieName, "", {
|
||||
...getAuthCookieOptions(true),
|
||||
maxAge: 0
|
||||
});
|
||||
setCookie(event, "csrf-token", "", {
|
||||
httpOnly: false,
|
||||
secure: env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check authentication status
|
||||
@@ -15,9 +143,8 @@ export async function checkAuthStatus(event: H3Event): Promise<{
|
||||
isAdmin: boolean;
|
||||
}> {
|
||||
try {
|
||||
const session = await getAuthSession(event);
|
||||
|
||||
if (!session || !session.userId) {
|
||||
const payload = await getAuthPayloadFromEvent(event);
|
||||
if (!payload) {
|
||||
return {
|
||||
isAuthenticated: false,
|
||||
userId: null,
|
||||
@@ -27,8 +154,8 @@ export async function checkAuthStatus(event: H3Event): Promise<{
|
||||
|
||||
return {
|
||||
isAuthenticated: true,
|
||||
userId: session.userId,
|
||||
isAdmin: session.isAdmin
|
||||
userId: payload.sub,
|
||||
isAdmin: payload.isAdmin
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Auth check error:", error);
|
||||
@@ -41,7 +168,7 @@ export async function checkAuthStatus(event: H3Event): Promise<{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user ID from session
|
||||
* Get user ID from auth token
|
||||
* @param event - H3Event
|
||||
* @returns User ID or null if not authenticated
|
||||
*/
|
||||
@@ -67,10 +194,8 @@ export async function validateLineageRequest({
|
||||
const { provider, email } = userRow;
|
||||
if (provider === "email") {
|
||||
try {
|
||||
const { jwtVerify } = await import("jose");
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
const { payload } = await jwtVerify(auth_token, secret);
|
||||
if (email !== payload.email) {
|
||||
const payload = await verifyAuthToken(auth_token);
|
||||
if (!payload || email !== payload.email) {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user