general: console cleanup
This commit is contained in:
@@ -136,12 +136,9 @@ export async function attemptTokenRefresh(
|
||||
refreshToken: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
console.log("[Token Refresh SSR] Attempting server-side refresh");
|
||||
|
||||
// Step 1: Get current session from Vinxi
|
||||
const session = await getAuthSession(event);
|
||||
if (!session) {
|
||||
console.warn("[Token Refresh SSR] No valid session found");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -149,10 +146,6 @@ export async function attemptTokenRefresh(
|
||||
const clientIP = getClientIP(event);
|
||||
const userAgent = getUserAgent(event);
|
||||
|
||||
// Step 3: Rotate session (includes validation, breach detection, cookie update)
|
||||
console.log(
|
||||
`[Token Refresh SSR] Rotating tokens for session ${session.sessionId}`
|
||||
);
|
||||
const newSession = await rotateAuthSession(
|
||||
event,
|
||||
session,
|
||||
@@ -161,14 +154,11 @@ export async function attemptTokenRefresh(
|
||||
);
|
||||
|
||||
if (!newSession) {
|
||||
console.warn("[Token Refresh SSR] Token rotation failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log("[Token Refresh SSR] Token refresh successful");
|
||||
return newSession.userId;
|
||||
} catch (error) {
|
||||
console.error("[Token Refresh SSR] Error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -179,13 +169,7 @@ export const authRouter = createTRPCRouter({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { code } = input;
|
||||
|
||||
console.log(
|
||||
"[GitHub Callback] Starting OAuth flow with code:",
|
||||
code.substring(0, 10) + "..."
|
||||
);
|
||||
|
||||
try {
|
||||
console.log("[GitHub Callback] Exchanging code for access token...");
|
||||
const tokenResponse = await fetchWithTimeout(
|
||||
"https://github.com/login/oauth/access_token",
|
||||
{
|
||||
@@ -214,9 +198,6 @@ export const authRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[GitHub Callback] Access token received, fetching user data..."
|
||||
);
|
||||
const userResponse = await fetchWithTimeout(
|
||||
"https://api.github.com/user",
|
||||
{
|
||||
@@ -232,9 +213,6 @@ export const authRouter = createTRPCRouter({
|
||||
const login = user.login;
|
||||
const icon = user.avatar_url;
|
||||
|
||||
console.log("[GitHub Callback] User data received:", { login });
|
||||
|
||||
console.log("[GitHub Callback] Fetching user emails...");
|
||||
const emailsResponse = await fetchWithTimeout(
|
||||
"https://api.github.com/user/emails",
|
||||
{
|
||||
@@ -255,40 +233,20 @@ export const authRouter = createTRPCRouter({
|
||||
const email = primaryEmail?.email || null;
|
||||
const emailVerified = primaryEmail?.verified || false;
|
||||
|
||||
console.log(
|
||||
"[GitHub Callback] Primary email:",
|
||||
email,
|
||||
"verified:",
|
||||
emailVerified
|
||||
);
|
||||
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
console.log("[GitHub Callback] Checking if user exists...");
|
||||
|
||||
// Strategy 1: Check if this GitHub identity already linked
|
||||
let userId = await findUserByProvider("github", login);
|
||||
|
||||
let isNewUser = false;
|
||||
let isLinkedAccount = false;
|
||||
|
||||
if (userId) {
|
||||
console.log(
|
||||
"[GitHub Callback] Existing GitHub provider found:",
|
||||
userId
|
||||
);
|
||||
// Update provider info
|
||||
await updateProviderLastUsed(userId, "github");
|
||||
} else {
|
||||
// Strategy 2: Check if email matches existing user (account linking)
|
||||
if (email) {
|
||||
userId = await findUserByEmail(email);
|
||||
if (userId) {
|
||||
console.log(
|
||||
"[GitHub Callback] Found existing user by email, linking GitHub account:",
|
||||
userId
|
||||
);
|
||||
// Link GitHub to existing account
|
||||
try {
|
||||
await linkProvider(userId, "github", {
|
||||
providerUserId: login,
|
||||
@@ -298,10 +256,6 @@ export const authRouter = createTRPCRouter({
|
||||
});
|
||||
isLinkedAccount = true;
|
||||
} catch (linkError: any) {
|
||||
console.error(
|
||||
"[GitHub Callback] Failed to link provider:",
|
||||
linkError.message
|
||||
);
|
||||
throw new TRPCError({
|
||||
code: "CONFLICT",
|
||||
message: linkError.message
|
||||
@@ -313,7 +267,6 @@ export const authRouter = createTRPCRouter({
|
||||
// Strategy 3: Create new user
|
||||
if (!userId) {
|
||||
userId = uuidV4();
|
||||
console.log("[GitHub Callback] Creating new user:", userId);
|
||||
|
||||
const insertQuery = `INSERT INTO User (id, email, email_verified, display_name, provider, image) VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
const insertParams = [
|
||||
@@ -337,16 +290,11 @@ export const authRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
isNewUser = true;
|
||||
console.log("[GitHub Callback] New user created");
|
||||
} catch (insertError: any) {
|
||||
if (
|
||||
insertError.code === "SQLITE_CONSTRAINT" &&
|
||||
insertError.message?.includes("User.email")
|
||||
) {
|
||||
console.error(
|
||||
"[GitHub Callback] Email conflict during insert:",
|
||||
email
|
||||
);
|
||||
throw new TRPCError({
|
||||
code: "CONFLICT",
|
||||
message:
|
||||
@@ -360,8 +308,6 @@ export const authRouter = createTRPCRouter({
|
||||
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
console.log("[GitHub Callback] Creating session for user:", userId);
|
||||
// Create session with Vinxi (OAuth defaults to remember me)
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
await createAuthSession(
|
||||
@@ -372,13 +318,8 @@ export const authRouter = createTRPCRouter({
|
||||
clientIP,
|
||||
userAgent
|
||||
);
|
||||
|
||||
// Set CSRF token for authenticated session
|
||||
setCSRFToken(getH3Event(ctx));
|
||||
|
||||
console.log("[GitHub Callback] Session created successfully");
|
||||
|
||||
// Log successful OAuth login
|
||||
await logAuditEvent({
|
||||
userId,
|
||||
eventType: "auth.login.success",
|
||||
@@ -392,7 +333,6 @@ export const authRouter = createTRPCRouter({
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log("[GitHub Callback] OAuth flow completed successfully");
|
||||
return {
|
||||
success: true,
|
||||
redirectTo: "/account"
|
||||
@@ -454,13 +394,7 @@ export const authRouter = createTRPCRouter({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { code } = input;
|
||||
|
||||
console.log(
|
||||
"[Google Callback] Starting OAuth flow with code:",
|
||||
code.substring(0, 10) + "..."
|
||||
);
|
||||
|
||||
try {
|
||||
console.log("[Google Callback] Exchanging code for access token...");
|
||||
const tokenResponse = await fetchWithTimeout(
|
||||
"https://oauth2.googleapis.com/token",
|
||||
{
|
||||
@@ -490,9 +424,6 @@ export const authRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[Google Callback] Access token received, fetching user data..."
|
||||
);
|
||||
const userResponse = await fetchWithTimeout(
|
||||
"https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
{
|
||||
@@ -509,39 +440,19 @@ export const authRouter = createTRPCRouter({
|
||||
const image = userData.picture;
|
||||
const email = userData.email;
|
||||
const email_verified = userData.email_verified;
|
||||
|
||||
console.log("[Google Callback] User data received:", {
|
||||
name,
|
||||
email,
|
||||
email_verified
|
||||
});
|
||||
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
console.log("[Google Callback] Checking if user exists...");
|
||||
|
||||
// Strategy 1: Check if this Google identity already linked
|
||||
let userId = await findUserByProvider("google", email);
|
||||
|
||||
let isNewUser = false;
|
||||
let isLinkedAccount = false;
|
||||
|
||||
if (userId) {
|
||||
console.log(
|
||||
"[Google Callback] Existing Google provider found:",
|
||||
userId
|
||||
);
|
||||
// Update provider info
|
||||
await updateProviderLastUsed(userId, "google");
|
||||
} else {
|
||||
// Strategy 2: Check if email matches existing user (account linking)
|
||||
userId = await findUserByEmail(email);
|
||||
if (userId) {
|
||||
console.log(
|
||||
"[Google Callback] Found existing user by email, linking Google account:",
|
||||
userId
|
||||
);
|
||||
// Link Google to existing account
|
||||
try {
|
||||
await linkProvider(userId, "google", {
|
||||
providerUserId: email,
|
||||
@@ -562,10 +473,8 @@ export const authRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Create new user
|
||||
if (!userId) {
|
||||
userId = uuidV4();
|
||||
console.log("[Google Callback] Creating new user:", userId);
|
||||
|
||||
const insertQuery = `INSERT INTO User (id, email, email_verified, display_name, provider, image) VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
const insertParams = [
|
||||
@@ -592,7 +501,6 @@ export const authRouter = createTRPCRouter({
|
||||
});
|
||||
|
||||
isNewUser = true;
|
||||
console.log("[Google Callback] New user created");
|
||||
} catch (insertError: any) {
|
||||
if (
|
||||
insertError.code === "SQLITE_CONSTRAINT" &&
|
||||
@@ -615,7 +523,6 @@ export const authRouter = createTRPCRouter({
|
||||
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
console.log("[Google Callback] Creating session for user:", userId);
|
||||
// Create session with Vinxi (OAuth defaults to remember me)
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
@@ -628,12 +535,8 @@ export const authRouter = createTRPCRouter({
|
||||
userAgent
|
||||
);
|
||||
|
||||
// Set CSRF token for authenticated session
|
||||
setCSRFToken(getH3Event(ctx));
|
||||
|
||||
console.log("[Google Callback] Session created successfully");
|
||||
|
||||
// Log successful OAuth login
|
||||
await logAuditEvent({
|
||||
userId,
|
||||
eventType: "auth.login.success",
|
||||
@@ -647,7 +550,6 @@ export const authRouter = createTRPCRouter({
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log("[Google Callback] OAuth flow completed successfully");
|
||||
return {
|
||||
success: true,
|
||||
redirectTo: "/account"
|
||||
@@ -655,7 +557,6 @@ export const authRouter = createTRPCRouter({
|
||||
} catch (error) {
|
||||
console.error("[Google Callback] Error during OAuth flow:", error);
|
||||
|
||||
// Log failed OAuth login
|
||||
const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
|
||||
await logAuditEvent({
|
||||
eventType: "auth.login.failed",
|
||||
@@ -716,17 +617,9 @@ export const authRouter = createTRPCRouter({
|
||||
const { email, token } = input;
|
||||
|
||||
try {
|
||||
console.log("[Email Login] Attempting login for:", email);
|
||||
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
const { payload } = await jwtVerify(token, secret);
|
||||
|
||||
console.log("[Email Login] JWT verified successfully. Payload:", {
|
||||
email: payload.email,
|
||||
rememberMe: payload.rememberMe,
|
||||
exp: payload.exp
|
||||
});
|
||||
|
||||
if (payload.email !== email) {
|
||||
console.error("[Email Login] Email mismatch:", {
|
||||
payloadEmail: payload.email,
|
||||
@@ -738,9 +631,7 @@ export const authRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
// Use rememberMe from JWT payload (source of truth), default to false
|
||||
const rememberMe = (payload.rememberMe as boolean) ?? false;
|
||||
console.log("[Email Login] Using rememberMe from JWT:", rememberMe);
|
||||
|
||||
const conn = ConnectionFactory();
|
||||
const query = `SELECT * FROM User WHERE email = ?`;
|
||||
@@ -758,13 +649,9 @@ export const authRouter = createTRPCRouter({
|
||||
const userId = (res.rows[0] as unknown as User).id;
|
||||
const isAdmin = userId === env.ADMIN_ID;
|
||||
|
||||
console.log("[Email Login] User found:", { userId, isAdmin });
|
||||
|
||||
// Create session with Vinxi (handles DB + encrypted cookie)
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
|
||||
console.log("[Email Login] Creating auth session...");
|
||||
await createAuthSession(
|
||||
getH3Event(ctx),
|
||||
userId,
|
||||
@@ -773,13 +660,8 @@ export const authRouter = createTRPCRouter({
|
||||
clientIP,
|
||||
userAgent
|
||||
);
|
||||
|
||||
// Set CSRF token for authenticated session
|
||||
setCSRFToken(getH3Event(ctx));
|
||||
|
||||
console.log("[Email Login] Session created successfully");
|
||||
|
||||
// Log successful email link login
|
||||
await logAuditEvent({
|
||||
userId,
|
||||
eventType: "auth.login.success",
|
||||
@@ -837,13 +719,6 @@ export const authRouter = createTRPCRouter({
|
||||
const { email, code, rememberMe } = input;
|
||||
|
||||
try {
|
||||
console.log(
|
||||
"[Email Code Login] Attempting login for:",
|
||||
email,
|
||||
"with code:",
|
||||
code
|
||||
);
|
||||
|
||||
const conn = ConnectionFactory();
|
||||
const res = await conn.execute({
|
||||
sql: "SELECT * FROM User WHERE email = ?",
|
||||
@@ -911,9 +786,6 @@ export const authRouter = createTRPCRouter({
|
||||
const shouldRemember =
|
||||
rememberMe ?? (payload.rememberMe as boolean) ?? false;
|
||||
|
||||
console.log("[Email Code Login] Code verified, creating session");
|
||||
|
||||
// Create session
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
await createAuthSession(
|
||||
@@ -924,13 +796,8 @@ export const authRouter = createTRPCRouter({
|
||||
clientIP,
|
||||
userAgent
|
||||
);
|
||||
|
||||
// Set CSRF token
|
||||
setCSRFToken(getH3Event(ctx));
|
||||
|
||||
console.log("[Email Code Login] Session created successfully");
|
||||
|
||||
// Log successful code login
|
||||
await logAuditEvent({
|
||||
userId,
|
||||
eventType: "auth.login.success",
|
||||
@@ -1840,11 +1707,8 @@ export const authRouter = createTRPCRouter({
|
||||
const session = await getAuthSession(getH3Event(ctx));
|
||||
|
||||
if (session) {
|
||||
// Step 2: Revoke entire token family (all devices)
|
||||
await revokeTokenFamily(session.tokenFamily, "user_logout");
|
||||
console.log(`Token family ${session.tokenFamily} revoked on signout`);
|
||||
|
||||
// Step 3: Log signout event
|
||||
const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
|
||||
await logAuditEvent({
|
||||
userId: session.userId,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
export async function migrateMultiAuth() {
|
||||
const conn = ConnectionFactory();
|
||||
console.log("[Migration] Starting multi-auth migration...");
|
||||
|
||||
|
||||
try {
|
||||
// Step 1: Check if UserProvider table exists
|
||||
@@ -17,11 +17,9 @@ export async function migrateMultiAuth() {
|
||||
});
|
||||
|
||||
if (tableCheck.rows.length > 0) {
|
||||
console.log(
|
||||
"[Migration] UserProvider table already exists, skipping creation"
|
||||
);
|
||||
|
||||
} else {
|
||||
console.log("[Migration] Creating UserProvider table...");
|
||||
|
||||
await conn.execute(`
|
||||
CREATE TABLE UserProvider (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -37,7 +35,7 @@ export async function migrateMultiAuth() {
|
||||
)
|
||||
`);
|
||||
|
||||
console.log("[Migration] Creating UserProvider indexes...");
|
||||
|
||||
await conn.execute(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_user_provider_provider_user ON UserProvider (provider, provider_user_id)"
|
||||
);
|
||||
@@ -64,11 +62,9 @@ export async function migrateMultiAuth() {
|
||||
);
|
||||
|
||||
if (hasDeviceName) {
|
||||
console.log(
|
||||
"[Migration] Session table already has device columns, skipping"
|
||||
);
|
||||
|
||||
} else {
|
||||
console.log("[Migration] Adding device columns to Session table...");
|
||||
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN device_name TEXT");
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN device_type TEXT");
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN browser TEXT");
|
||||
@@ -79,14 +75,12 @@ export async function migrateMultiAuth() {
|
||||
await conn.execute("ALTER TABLE Session ADD COLUMN last_active_at TEXT");
|
||||
|
||||
// Update existing rows to set last_active_at = last_used
|
||||
console.log(
|
||||
"[Migration] Updating existing sessions with last_active_at..."
|
||||
);
|
||||
|
||||
await conn.execute(
|
||||
"UPDATE Session SET last_active_at = COALESCE(last_used, created_at) WHERE last_active_at IS NULL"
|
||||
);
|
||||
|
||||
console.log("[Migration] Creating Session indexes...");
|
||||
|
||||
await conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_session_last_active ON Session (last_active_at)"
|
||||
);
|
||||
@@ -96,14 +90,12 @@ export async function migrateMultiAuth() {
|
||||
}
|
||||
|
||||
// Step 3: Migrate existing users to UserProvider table
|
||||
console.log("[Migration] Checking for users to migrate...");
|
||||
|
||||
const usersResult = await conn.execute({
|
||||
sql: "SELECT id, email, provider, display_name, image, apple_user_string FROM User WHERE provider IS NOT NULL"
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[Migration] Found ${usersResult.rows.length} users to migrate`
|
||||
);
|
||||
|
||||
|
||||
let migratedCount = 0;
|
||||
for (const row of usersResult.rows) {
|
||||
@@ -111,9 +103,7 @@ export async function migrateMultiAuth() {
|
||||
|
||||
// Skip apple provider users (they're for Life and Lineage mobile app, not website auth)
|
||||
if (user.provider === "apple") {
|
||||
console.log(
|
||||
`[Migration] Skipping user ${user.id} with apple provider (mobile app only)`
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -124,9 +114,7 @@ export async function migrateMultiAuth() {
|
||||
});
|
||||
|
||||
if (existingProvider.rows.length > 0) {
|
||||
console.log(
|
||||
`[Migration] User ${user.id} already migrated, skipping`
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -198,34 +186,30 @@ export async function migrateMultiAuth() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Migration] Migrated ${migratedCount} users successfully`);
|
||||
|
||||
|
||||
// Step 4: Verification
|
||||
console.log("[Migration] Running verification queries...");
|
||||
|
||||
const providerCount = await conn.execute({
|
||||
sql: "SELECT COUNT(*) as count FROM UserProvider"
|
||||
});
|
||||
console.log(
|
||||
`[Migration] Total providers in UserProvider table: ${(providerCount.rows[0] as any).count}`
|
||||
);
|
||||
|
||||
|
||||
const multiProviderUsers = await conn.execute({
|
||||
sql: `SELECT COUNT(*) as count FROM (
|
||||
SELECT user_id FROM UserProvider GROUP BY user_id HAVING COUNT(*) > 1
|
||||
)`
|
||||
});
|
||||
console.log(
|
||||
`[Migration] Users with multiple providers: ${(multiProviderUsers.rows[0] as any).count}`
|
||||
);
|
||||
|
||||
console.log("[Migration] Multi-auth migration completed successfully!");
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
migratedUsers: migratedCount,
|
||||
totalProviders: (providerCount.rows[0] as any).count
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[Migration] Migration failed:", error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -234,11 +218,11 @@ export async function migrateMultiAuth() {
|
||||
if (require.main === module) {
|
||||
migrateMultiAuth()
|
||||
.then((result) => {
|
||||
console.log("[Migration] Result:", result);
|
||||
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Migration] Error:", error);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,48 +201,14 @@ export async function createAuthSession(
|
||||
: undefined // Session cookie (expires on browser close)
|
||||
};
|
||||
|
||||
console.log("[Session Create] Creating session with data:", {
|
||||
userId: sessionData.userId,
|
||||
sessionId: sessionData.sessionId,
|
||||
tokenFamily: sessionData.tokenFamily,
|
||||
rememberMe: sessionData.rememberMe,
|
||||
maxAge: configWithMaxAge.maxAge
|
||||
});
|
||||
|
||||
// Use updateSession to set session data directly
|
||||
console.log(
|
||||
"[Session Create] Input sessionData:",
|
||||
JSON.stringify(sessionData, null, 2)
|
||||
);
|
||||
|
||||
const session = await updateSession(event, configWithMaxAge, sessionData);
|
||||
console.log(
|
||||
"[Session Create] Returned session object:",
|
||||
JSON.stringify(
|
||||
{
|
||||
id: session.id,
|
||||
data: session.data,
|
||||
createdAt: session.createdAt
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
|
||||
// Explicitly seal/flush the session to ensure cookie is written
|
||||
// This is important in serverless environments where response might stream early
|
||||
const { sealSession } = await import("vinxi/http");
|
||||
sealSession(event, configWithMaxAge);
|
||||
console.log("[Session Create] Explicitly sealed session cookie");
|
||||
|
||||
console.log("[Session Create] Session created via updateSession API:", {
|
||||
sessionId: session.id,
|
||||
hasData: !!session.data,
|
||||
dataKeys: session.data ? Object.keys(session.data) : [],
|
||||
dataIsEmpty: session.data && Object.keys(session.data).length === 0,
|
||||
inputDataKeys: Object.keys(sessionData),
|
||||
configPassword: configWithMaxAge.password?.substring(0, 10) + "...",
|
||||
configMaxAge: configWithMaxAge.maxAge
|
||||
});
|
||||
|
||||
// Set a separate sessionId cookie for DB fallback (in case main session cookie fails)
|
||||
setCookie(event, "session_id", sessionId, {
|
||||
@@ -253,29 +219,16 @@ export async function createAuthSession(
|
||||
maxAge: configWithMaxAge.maxAge
|
||||
});
|
||||
|
||||
console.log("[Session Create] Set session_id fallback cookie:", sessionId);
|
||||
|
||||
// Verify session was actually set by reading it back
|
||||
try {
|
||||
const cookieName = sessionConfig.name || "session";
|
||||
const cookieValue = getCookie(event, cookieName);
|
||||
console.log("[Session Create] Immediate verification:", {
|
||||
cookieName,
|
||||
hasCookie: !!cookieValue,
|
||||
cookieLength: cookieValue?.length || 0
|
||||
});
|
||||
|
||||
// Try reading back the session immediately using the same config
|
||||
const verifySession = await getSession<SessionData>(
|
||||
event,
|
||||
configWithMaxAge
|
||||
);
|
||||
console.log("[Session Create] Read-back verification:", {
|
||||
hasData: !!verifySession.data,
|
||||
dataMatches: verifySession.data?.userId === sessionData.userId,
|
||||
userId: verifySession.data?.userId,
|
||||
sessionId: verifySession.data?.sessionId
|
||||
});
|
||||
} catch (verifyError) {
|
||||
console.error("[Session Create] Failed to verify session:", verifyError);
|
||||
}
|
||||
@@ -314,12 +267,7 @@ export async function getAuthSession(
|
||||
const { unsealSession } = await import("vinxi/http");
|
||||
const cookieName = sessionConfig.name || "session";
|
||||
const cookieValue = getCookie(event, cookieName);
|
||||
console.log(
|
||||
"[Session Get] skipUpdate mode, cookieName:",
|
||||
cookieName,
|
||||
"has cookie:",
|
||||
!!cookieValue
|
||||
);
|
||||
|
||||
if (!cookieValue) {
|
||||
return null;
|
||||
}
|
||||
@@ -327,42 +275,21 @@ export async function getAuthSession(
|
||||
try {
|
||||
// unsealSession returns Partial<Session<T>>, not T directly
|
||||
const session = await unsealSession(event, sessionConfig, cookieValue);
|
||||
console.log("[Session Get] Unsealed session:", {
|
||||
hasData: !!session?.data,
|
||||
dataType: typeof session?.data,
|
||||
dataKeys: session?.data ? Object.keys(session.data) : []
|
||||
});
|
||||
|
||||
if (!session?.data || typeof session.data !== "object") {
|
||||
console.log("[Session Get] Invalid session structure");
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = session.data as SessionData;
|
||||
console.log("[Session Get] Session data:", {
|
||||
hasUserId: !!data.userId,
|
||||
hasSessionId: !!data.sessionId,
|
||||
hasRefreshToken: !!data.refreshToken
|
||||
});
|
||||
|
||||
if (!data.userId || !data.sessionId) {
|
||||
console.log("[Session Get] Missing userId or sessionId");
|
||||
|
||||
// Fallback: Try to restore from DB using session_id cookie
|
||||
const sessionIdCookie = getCookie(event, "session_id");
|
||||
if (sessionIdCookie) {
|
||||
console.log(
|
||||
"[Session Get] Attempting DB fallback (skipUpdate path) with session_id:",
|
||||
sessionIdCookie
|
||||
);
|
||||
const restored = await restoreSessionFromDB(event, sessionIdCookie);
|
||||
if (restored) {
|
||||
console.log(
|
||||
"[Session Get] Successfully restored session from DB (skipUpdate path)"
|
||||
);
|
||||
return restored;
|
||||
}
|
||||
console.log("[Session Get] DB fallback failed (skipUpdate path)");
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -375,7 +302,6 @@ export async function getAuthSession(
|
||||
data.refreshToken
|
||||
);
|
||||
|
||||
console.log("[Session Get] DB validation result:", isValid);
|
||||
return isValid ? data : null;
|
||||
} catch (err) {
|
||||
console.error("[Session Get] Error in skipUpdate path:", err);
|
||||
@@ -384,34 +310,17 @@ export async function getAuthSession(
|
||||
}
|
||||
|
||||
// Normal path - allow session updates
|
||||
console.log("[Session Get] Normal path, getting session");
|
||||
|
||||
const session = await getSession<SessionData>(event, sessionConfig);
|
||||
console.log("[Session Get] Got session:", {
|
||||
hasData: !!session.data,
|
||||
dataType: typeof session.data,
|
||||
dataKeys: session.data ? Object.keys(session.data) : []
|
||||
});
|
||||
|
||||
if (!session.data || !session.data.userId || !session.data.sessionId) {
|
||||
console.log(
|
||||
"[Session Get] Missing data or userId/sessionId in normal path"
|
||||
);
|
||||
|
||||
// Fallback: Try to restore from DB using session_id cookie
|
||||
const sessionIdCookie = getCookie(event, "session_id");
|
||||
if (sessionIdCookie) {
|
||||
console.log(
|
||||
"[Session Get] Attempting DB fallback with session_id:",
|
||||
sessionIdCookie
|
||||
);
|
||||
const restored = await restoreSessionFromDB(event, sessionIdCookie);
|
||||
if (restored) {
|
||||
console.log("[Session Get] Successfully restored session from DB");
|
||||
return restored;
|
||||
}
|
||||
console.log(
|
||||
"[Session Get] DB fallback failed - session not found or invalid"
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -443,9 +352,6 @@ export async function getAuthSession(
|
||||
// If headers already sent, we can't read the session cookie properly
|
||||
// This can happen in SSR when response streaming has started
|
||||
if (error?.code === "ERR_HTTP_HEADERS_SENT") {
|
||||
console.warn(
|
||||
"Cannot access session - headers already sent, retrying with skipUpdate"
|
||||
);
|
||||
// Retry with skipUpdate
|
||||
return getAuthSession(event, true);
|
||||
}
|
||||
@@ -479,7 +385,6 @@ async function restoreSessionFromDB(
|
||||
});
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
console.log("[Session Restore] Session not found in DB:", sessionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -487,21 +392,16 @@ async function restoreSessionFromDB(
|
||||
|
||||
// Validate session is still valid
|
||||
if (dbSession.revoked === 1) {
|
||||
console.log("[Session Restore] Session is revoked");
|
||||
return null;
|
||||
}
|
||||
|
||||
const expiresAt = new Date(dbSession.expires_at as string);
|
||||
if (expiresAt < new Date()) {
|
||||
console.log("[Session Restore] Session expired");
|
||||
return null;
|
||||
}
|
||||
|
||||
// We can't restore the refresh token (it's hashed in DB)
|
||||
// So we need to generate a new one and rotate the session
|
||||
console.log(
|
||||
"[Session Restore] Session valid but refresh token lost - rotating session"
|
||||
);
|
||||
|
||||
// Get IP and user agent
|
||||
const { getRequestIP } = await import("vinxi/http");
|
||||
@@ -520,10 +420,6 @@ async function restoreSessionFromDB(
|
||||
dbSession.token_family as string // Reuse family
|
||||
);
|
||||
|
||||
console.log(
|
||||
"[Session Restore] Created new session via rotation:",
|
||||
newSession.sessionId
|
||||
);
|
||||
return newSession;
|
||||
} catch (error) {
|
||||
console.error("[Session Restore] Error restoring session:", error);
|
||||
@@ -606,7 +502,6 @@ export async function invalidateAuthSession(
|
||||
sessionId: string
|
||||
): Promise<void> {
|
||||
const conn = ConnectionFactory();
|
||||
console.log(`[Session] Invalidating session ${sessionId}`);
|
||||
|
||||
await conn.execute({
|
||||
sql: "UPDATE Session SET revoked = 1 WHERE id = ?",
|
||||
@@ -644,9 +539,7 @@ export async function revokeTokenFamily(
|
||||
});
|
||||
|
||||
// Revoke all sessions in family
|
||||
console.log(
|
||||
`[Token Family] Revoking entire family ${tokenFamily} (reason: ${reason}). Sessions affected: ${sessions.rows.length}`
|
||||
);
|
||||
|
||||
await conn.execute({
|
||||
sql: "UPDATE Session SET revoked = 1 WHERE token_family = ?",
|
||||
args: [tokenFamily]
|
||||
@@ -665,8 +558,6 @@ export async function revokeTokenFamily(
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
console.warn(`Token family ${tokenFamily} revoked: ${reason}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -699,16 +590,10 @@ export async function detectTokenReuse(sessionId: string): Promise<boolean> {
|
||||
|
||||
// Grace period for race conditions
|
||||
if (timeSinceRotation < AUTH_CONFIG.REFRESH_TOKEN_REUSE_WINDOW_MS) {
|
||||
console.warn(
|
||||
`[Token Reuse] Within grace period (${timeSinceRotation}ms < ${AUTH_CONFIG.REFRESH_TOKEN_REUSE_WINDOW_MS}ms), allowing for session ${sessionId}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reuse detected outside grace period - this is a breach!
|
||||
console.error(
|
||||
`[Token Reuse] BREACH DETECTED! Session ${sessionId} rotated ${timeSinceRotation}ms ago. Child session: ${childSession.id}`
|
||||
);
|
||||
|
||||
// Get token family and revoke entire family
|
||||
const sessionInfo = await conn.execute({
|
||||
@@ -755,10 +640,6 @@ export async function rotateAuthSession(
|
||||
ipAddress: string,
|
||||
userAgent: string
|
||||
): Promise<SessionData | null> {
|
||||
console.log(
|
||||
`[Token Rotation] Starting rotation for session ${oldSessionData.sessionId}`
|
||||
);
|
||||
|
||||
// Validate old session exists in DB
|
||||
const isValid = await validateSessionInDB(
|
||||
oldSessionData.sessionId,
|
||||
@@ -767,18 +648,12 @@ export async function rotateAuthSession(
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
console.warn(
|
||||
`[Token Rotation] Invalid session during rotation for ${oldSessionData.sessionId}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Detect token reuse (breach detection)
|
||||
const reuseDetected = await detectTokenReuse(oldSessionData.sessionId);
|
||||
if (reuseDetected) {
|
||||
console.error(
|
||||
`[Token Rotation] Token reuse detected for session ${oldSessionData.sessionId}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -795,9 +670,6 @@ export async function rotateAuthSession(
|
||||
|
||||
const rotationCount = sessionCheck.rows[0].rotation_count as number;
|
||||
if (rotationCount >= AUTH_CONFIG.MAX_ROTATION_COUNT) {
|
||||
console.warn(
|
||||
`[Token Rotation] Max rotation count reached for session ${oldSessionData.sessionId}`
|
||||
);
|
||||
await invalidateAuthSession(event, oldSessionData.sessionId);
|
||||
return null;
|
||||
}
|
||||
@@ -833,9 +705,5 @@ export async function rotateAuthSession(
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[Token Rotation] Successfully rotated session ${oldSessionData.sessionId} -> ${newSessionData.sessionId}`
|
||||
);
|
||||
|
||||
return newSessionData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user