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