change template loading
This commit is contained in:
@@ -1,13 +1,21 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server";
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
import { appRouter } from "~/server/api/root";
|
import { appRouter } from "~/server/api/root";
|
||||||
import { createTRPCContext } from "~/server/api/utils";
|
import { createTRPCContext } from "~/server/api/utils";
|
||||||
|
import { getResponseHeaders } from "vinxi/http";
|
||||||
|
|
||||||
export async function GET(event: APIEvent) {
|
export async function GET(event: APIEvent) {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
const code = url.searchParams.get("code");
|
const code = url.searchParams.get("code");
|
||||||
const error = url.searchParams.get("error");
|
const error = url.searchParams.get("error");
|
||||||
|
|
||||||
|
console.log("[GitHub OAuth Callback] Request received:", {
|
||||||
|
hasCode: !!code,
|
||||||
|
codeLength: code?.length,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
console.error("[GitHub OAuth Callback] OAuth error from provider:", error);
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
||||||
@@ -15,6 +23,7 @@ export async function GET(event: APIEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
|
console.error("[GitHub OAuth Callback] Missing authorization code");
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { Location: "/login?error=missing_code" }
|
headers: { Location: "/login?error=missing_code" }
|
||||||
@@ -22,28 +31,77 @@ export async function GET(event: APIEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log("[GitHub OAuth Callback] Creating tRPC caller...");
|
||||||
const ctx = await createTRPCContext(event);
|
const ctx = await createTRPCContext(event);
|
||||||
const caller = appRouter.createCaller(ctx);
|
const caller = appRouter.createCaller(ctx);
|
||||||
|
|
||||||
|
console.log("[GitHub OAuth Callback] Calling githubCallback procedure...");
|
||||||
const result = await caller.auth.githubCallback({ code });
|
const result = await caller.auth.githubCallback({ code });
|
||||||
|
|
||||||
|
console.log("[GitHub OAuth Callback] Result:", result);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return new Response(null, {
|
console.log(
|
||||||
status: 302,
|
"[GitHub OAuth Callback] Login successful, redirecting to:",
|
||||||
headers: { Location: result.redirectTo || "/account" }
|
result.redirectTo
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the response headers that were set by the session (includes Set-Cookie)
|
||||||
|
const responseHeaders = getResponseHeaders(event.nativeEvent);
|
||||||
|
console.log(
|
||||||
|
"[GitHub OAuth Callback] Response headers from event:",
|
||||||
|
Object.keys(responseHeaders)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create redirect response with the session cookie
|
||||||
|
const redirectUrl = result.redirectTo || "/account";
|
||||||
|
const headers = new Headers({
|
||||||
|
Location: redirectUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy Set-Cookie headers from the session response
|
||||||
|
if (responseHeaders["set-cookie"]) {
|
||||||
|
const cookies = Array.isArray(responseHeaders["set-cookie"])
|
||||||
|
? responseHeaders["set-cookie"]
|
||||||
|
: [responseHeaders["set-cookie"]];
|
||||||
|
|
||||||
|
console.log("[GitHub OAuth Callback] Found cookies:", cookies.length);
|
||||||
|
cookies.forEach((cookie) => {
|
||||||
|
headers.append("Set-Cookie", cookie);
|
||||||
|
console.log(
|
||||||
|
"[GitHub OAuth Callback] Adding cookie:",
|
||||||
|
cookie.substring(0, 50) + "..."
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
console.error("[GitHub OAuth Callback] NO SET-COOKIE HEADER FOUND!");
|
||||||
|
console.error("[GitHub OAuth Callback] All headers:", responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(null, {
|
||||||
|
status: 302,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"[GitHub OAuth Callback] Login failed (result.success=false)"
|
||||||
|
);
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { Location: "/login?error=auth_failed" }
|
headers: { Location: "/login?error=auth_failed" }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("GitHub OAuth callback error:", error);
|
console.error("[GitHub OAuth Callback] Error caught:", error);
|
||||||
|
|
||||||
if (error && typeof error === "object" && "code" in error) {
|
if (error && typeof error === "object" && "code" in error) {
|
||||||
const trpcError = error as { code: string; message?: string };
|
const trpcError = error as { code: string; message?: string };
|
||||||
|
|
||||||
|
console.error("[GitHub OAuth Callback] tRPC error:", {
|
||||||
|
code: trpcError.code,
|
||||||
|
message: trpcError.message
|
||||||
|
});
|
||||||
|
|
||||||
if (trpcError.code === "CONFLICT") {
|
if (trpcError.code === "CONFLICT") {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server";
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
import { appRouter } from "~/server/api/root";
|
import { appRouter } from "~/server/api/root";
|
||||||
import { createTRPCContext } from "~/server/api/utils";
|
import { createTRPCContext } from "~/server/api/utils";
|
||||||
|
import { getResponseHeaders } from "vinxi/http";
|
||||||
|
|
||||||
export async function GET(event: APIEvent) {
|
export async function GET(event: APIEvent) {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
const code = url.searchParams.get("code");
|
const code = url.searchParams.get("code");
|
||||||
const error = url.searchParams.get("error");
|
const error = url.searchParams.get("error");
|
||||||
|
|
||||||
|
console.log("[Google OAuth Callback] Request received:", {
|
||||||
|
hasCode: !!code,
|
||||||
|
codeLength: code?.length,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
console.error("[Google OAuth Callback] OAuth error from provider:", error);
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
||||||
@@ -15,6 +23,7 @@ export async function GET(event: APIEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
|
console.error("[Google OAuth Callback] Missing authorization code");
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { Location: "/login?error=missing_code" }
|
headers: { Location: "/login?error=missing_code" }
|
||||||
@@ -22,28 +31,77 @@ export async function GET(event: APIEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log("[Google OAuth Callback] Creating tRPC caller...");
|
||||||
const ctx = await createTRPCContext(event);
|
const ctx = await createTRPCContext(event);
|
||||||
const caller = appRouter.createCaller(ctx);
|
const caller = appRouter.createCaller(ctx);
|
||||||
|
|
||||||
|
console.log("[Google OAuth Callback] Calling googleCallback procedure...");
|
||||||
const result = await caller.auth.googleCallback({ code });
|
const result = await caller.auth.googleCallback({ code });
|
||||||
|
|
||||||
|
console.log("[Google OAuth Callback] Result:", result);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return new Response(null, {
|
console.log(
|
||||||
status: 302,
|
"[Google OAuth Callback] Login successful, redirecting to:",
|
||||||
headers: { Location: result.redirectTo || "/account" }
|
result.redirectTo
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the response headers that were set by the session (includes Set-Cookie)
|
||||||
|
const responseHeaders = getResponseHeaders(event.nativeEvent);
|
||||||
|
console.log(
|
||||||
|
"[Google OAuth Callback] Response headers from event:",
|
||||||
|
Object.keys(responseHeaders)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create redirect response with the session cookie
|
||||||
|
const redirectUrl = result.redirectTo || "/account";
|
||||||
|
const headers = new Headers({
|
||||||
|
Location: redirectUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy Set-Cookie headers from the session response
|
||||||
|
if (responseHeaders["set-cookie"]) {
|
||||||
|
const cookies = Array.isArray(responseHeaders["set-cookie"])
|
||||||
|
? responseHeaders["set-cookie"]
|
||||||
|
: [responseHeaders["set-cookie"]];
|
||||||
|
|
||||||
|
console.log("[Google OAuth Callback] Found cookies:", cookies.length);
|
||||||
|
cookies.forEach((cookie) => {
|
||||||
|
headers.append("Set-Cookie", cookie);
|
||||||
|
console.log(
|
||||||
|
"[Google OAuth Callback] Adding cookie:",
|
||||||
|
cookie.substring(0, 50) + "..."
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
console.error("[Google OAuth Callback] NO SET-COOKIE HEADER FOUND!");
|
||||||
|
console.error("[Google OAuth Callback] All headers:", responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(null, {
|
||||||
|
status: 302,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"[Google OAuth Callback] Login failed (result.success=false)"
|
||||||
|
);
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { Location: "/login?error=auth_failed" }
|
headers: { Location: "/login?error=auth_failed" }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Google OAuth callback error:", error);
|
console.error("[Google OAuth Callback] Error caught:", error);
|
||||||
|
|
||||||
if (error && typeof error === "object" && "code" in error) {
|
if (error && typeof error === "object" && "code" in error) {
|
||||||
const trpcError = error as { code: string; message?: string };
|
const trpcError = error as { code: string; message?: string };
|
||||||
|
|
||||||
|
console.error("[Google OAuth Callback] tRPC error:", {
|
||||||
|
code: trpcError.code,
|
||||||
|
message: trpcError.message
|
||||||
|
});
|
||||||
|
|
||||||
if (trpcError.code === "CONFLICT") {
|
if (trpcError.code === "CONFLICT") {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
|
|||||||
@@ -173,7 +173,13 @@ 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",
|
||||||
{
|
{
|
||||||
@@ -195,12 +201,16 @@ export const authRouter = createTRPCRouter({
|
|||||||
const { access_token } = await tokenResponse.json();
|
const { access_token } = await tokenResponse.json();
|
||||||
|
|
||||||
if (!access_token) {
|
if (!access_token) {
|
||||||
|
console.error("[GitHub Callback] No access token received");
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "Failed to get access token from GitHub"
|
message: "Failed to get access token from GitHub"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
{
|
{
|
||||||
@@ -216,6 +226,9 @@ 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",
|
||||||
{
|
{
|
||||||
@@ -236,8 +249,16 @@ 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...");
|
||||||
const query = `SELECT * FROM User WHERE provider = ? AND display_name = ?`;
|
const query = `SELECT * FROM User WHERE provider = ? AND display_name = ?`;
|
||||||
const params = ["github", login];
|
const params = ["github", login];
|
||||||
const res = await conn.execute({ sql: query, args: params });
|
const res = await conn.execute({ sql: query, args: params });
|
||||||
@@ -246,17 +267,23 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (res.rows[0]) {
|
if (res.rows[0]) {
|
||||||
userId = (res.rows[0] as unknown as User).id;
|
userId = (res.rows[0] as unknown as User).id;
|
||||||
|
console.log("[GitHub Callback] Existing user found:", userId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await conn.execute({
|
await conn.execute({
|
||||||
sql: `UPDATE User SET email = ?, email_verified = ?, image = ? WHERE id = ?`,
|
sql: `UPDATE User SET email = ?, email_verified = ?, image = ? WHERE id = ?`,
|
||||||
args: [email, emailVerified ? 1 : 0, icon, userId]
|
args: [email, emailVerified ? 1 : 0, icon, userId]
|
||||||
});
|
});
|
||||||
|
console.log("[GitHub Callback] User data updated");
|
||||||
} catch (updateError: any) {
|
} catch (updateError: any) {
|
||||||
if (
|
if (
|
||||||
updateError.code === "SQLITE_CONSTRAINT" &&
|
updateError.code === "SQLITE_CONSTRAINT" &&
|
||||||
updateError.message?.includes("User.email")
|
updateError.message?.includes("User.email")
|
||||||
) {
|
) {
|
||||||
|
console.error(
|
||||||
|
"[GitHub Callback] Email conflict during update:",
|
||||||
|
email
|
||||||
|
);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "CONFLICT",
|
code: "CONFLICT",
|
||||||
message:
|
message:
|
||||||
@@ -267,6 +294,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
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 = [
|
||||||
@@ -280,11 +308,16 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await conn.execute({ sql: insertQuery, args: insertParams });
|
await conn.execute({ sql: insertQuery, args: insertParams });
|
||||||
|
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:
|
||||||
@@ -297,6 +330,7 @@ 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)
|
// 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));
|
||||||
@@ -312,6 +346,8 @@ export const authRouter = createTRPCRouter({
|
|||||||
// Set CSRF token for authenticated session
|
// Set CSRF token for authenticated session
|
||||||
setCSRFToken(getH3Event(ctx));
|
setCSRFToken(getH3Event(ctx));
|
||||||
|
|
||||||
|
console.log("[GitHub Callback] Session created successfully");
|
||||||
|
|
||||||
// Log successful OAuth login
|
// Log successful OAuth login
|
||||||
await logAuditEvent({
|
await logAuditEvent({
|
||||||
userId,
|
userId,
|
||||||
@@ -322,11 +358,14 @@ 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"
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("[GitHub Callback] Error during OAuth flow:", error);
|
||||||
|
|
||||||
// Log failed OAuth login
|
// Log failed OAuth login
|
||||||
const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
|
const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
|
||||||
await logAuditEvent({
|
await logAuditEvent({
|
||||||
@@ -345,26 +384,30 @@ export const authRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof TimeoutError) {
|
if (error instanceof TimeoutError) {
|
||||||
console.error("GitHub API timeout:", error.message);
|
console.error("[GitHub Callback] Timeout:", error.message);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "TIMEOUT",
|
code: "TIMEOUT",
|
||||||
message: "GitHub authentication timed out. Please try again."
|
message: "GitHub authentication timed out. Please try again."
|
||||||
});
|
});
|
||||||
} else if (error instanceof NetworkError) {
|
} else if (error instanceof NetworkError) {
|
||||||
console.error("GitHub API network error:", error.message);
|
console.error("[GitHub Callback] Network error:", error.message);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Unable to connect to GitHub. Please try again later."
|
message: "Unable to connect to GitHub. Please try again later."
|
||||||
});
|
});
|
||||||
} else if (error instanceof APIError) {
|
} else if (error instanceof APIError) {
|
||||||
console.error("GitHub API error:", error.status, error.statusText);
|
console.error(
|
||||||
|
"[GitHub Callback] API error:",
|
||||||
|
error.status,
|
||||||
|
error.statusText
|
||||||
|
);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "GitHub authentication failed. Please try again."
|
message: "GitHub authentication failed. Please try again."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("GitHub authentication failed:", error);
|
console.error("[GitHub Callback] Unknown error:", error);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "GitHub authentication failed"
|
message: "GitHub authentication failed"
|
||||||
@@ -377,7 +420,13 @@ 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",
|
||||||
{
|
{
|
||||||
@@ -400,12 +449,16 @@ export const authRouter = createTRPCRouter({
|
|||||||
const { access_token } = await tokenResponse.json();
|
const { access_token } = await tokenResponse.json();
|
||||||
|
|
||||||
if (!access_token) {
|
if (!access_token) {
|
||||||
|
console.error("[Google Callback] No access token received");
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "Failed to get access token from Google"
|
message: "Failed to get access token from Google"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
{
|
{
|
||||||
@@ -423,8 +476,15 @@ export const authRouter = createTRPCRouter({
|
|||||||
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...");
|
||||||
const query = `SELECT * FROM User WHERE provider = ? AND email = ?`;
|
const query = `SELECT * FROM User WHERE provider = ? AND email = ?`;
|
||||||
const params = ["google", email];
|
const params = ["google", email];
|
||||||
const res = await conn.execute({ sql: query, args: params });
|
const res = await conn.execute({ sql: query, args: params });
|
||||||
@@ -433,13 +493,16 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
if (res.rows[0]) {
|
if (res.rows[0]) {
|
||||||
userId = (res.rows[0] as unknown as User).id;
|
userId = (res.rows[0] as unknown as User).id;
|
||||||
|
console.log("[Google Callback] Existing user found:", userId);
|
||||||
|
|
||||||
await conn.execute({
|
await conn.execute({
|
||||||
sql: `UPDATE User SET email = ?, email_verified = ?, display_name = ?, image = ? WHERE id = ?`,
|
sql: `UPDATE User SET email = ?, email_verified = ?, display_name = ?, image = ? WHERE id = ?`,
|
||||||
args: [email, email_verified ? 1 : 0, name, image, userId]
|
args: [email, email_verified ? 1 : 0, name, image, userId]
|
||||||
});
|
});
|
||||||
|
console.log("[Google Callback] User data updated");
|
||||||
} else {
|
} else {
|
||||||
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 = [
|
||||||
@@ -456,11 +519,16 @@ export const authRouter = createTRPCRouter({
|
|||||||
sql: insertQuery,
|
sql: insertQuery,
|
||||||
args: insertParams
|
args: insertParams
|
||||||
});
|
});
|
||||||
|
console.log("[Google 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(
|
||||||
|
"[Google Callback] Email conflict during insert:",
|
||||||
|
email
|
||||||
|
);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "CONFLICT",
|
code: "CONFLICT",
|
||||||
message:
|
message:
|
||||||
@@ -473,6 +541,7 @@ 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));
|
||||||
@@ -488,6 +557,8 @@ export const authRouter = createTRPCRouter({
|
|||||||
// Set CSRF token for authenticated session
|
// Set CSRF token for authenticated session
|
||||||
setCSRFToken(getH3Event(ctx));
|
setCSRFToken(getH3Event(ctx));
|
||||||
|
|
||||||
|
console.log("[Google Callback] Session created successfully");
|
||||||
|
|
||||||
// Log successful OAuth login
|
// Log successful OAuth login
|
||||||
await logAuditEvent({
|
await logAuditEvent({
|
||||||
userId,
|
userId,
|
||||||
@@ -498,11 +569,14 @@ 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"
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("[Google Callback] Error during OAuth flow:", error);
|
||||||
|
|
||||||
// Log failed OAuth login
|
// Log failed OAuth login
|
||||||
const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
|
const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
|
||||||
await logAuditEvent({
|
await logAuditEvent({
|
||||||
@@ -521,26 +595,30 @@ export const authRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof TimeoutError) {
|
if (error instanceof TimeoutError) {
|
||||||
console.error("Google API timeout:", error.message);
|
console.error("[Google Callback] Timeout:", error.message);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "TIMEOUT",
|
code: "TIMEOUT",
|
||||||
message: "Google authentication timed out. Please try again."
|
message: "Google authentication timed out. Please try again."
|
||||||
});
|
});
|
||||||
} else if (error instanceof NetworkError) {
|
} else if (error instanceof NetworkError) {
|
||||||
console.error("Google API network error:", error.message);
|
console.error("[Google Callback] Network error:", error.message);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Unable to connect to Google. Please try again later."
|
message: "Unable to connect to Google. Please try again later."
|
||||||
});
|
});
|
||||||
} else if (error instanceof APIError) {
|
} else if (error instanceof APIError) {
|
||||||
console.error("Google API error:", error.status, error.statusText);
|
console.error(
|
||||||
|
"[Google Callback] API error:",
|
||||||
|
error.status,
|
||||||
|
error.statusText
|
||||||
|
);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Google authentication failed. Please try again."
|
message: "Google authentication failed. Please try again."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("Google authentication failed:", error);
|
console.error("[Google Callback] Unknown error:", error);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Google authentication failed"
|
message: "Google authentication failed"
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { readFileSync } from "fs";
|
|
||||||
import { join } from "path";
|
|
||||||
import { AUTH_CONFIG } from "~/config";
|
import { AUTH_CONFIG } from "~/config";
|
||||||
|
|
||||||
|
// Import email templates as raw strings - Vite will bundle these at build time
|
||||||
|
import loginLinkTemplate from "./login-link.html?raw";
|
||||||
|
import passwordResetTemplate from "./password-reset.html?raw";
|
||||||
|
import emailVerificationTemplate from "./email-verification.html?raw";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert expiry string to human-readable format
|
* Convert expiry string to human-readable format
|
||||||
* @param expiry - Expiry string like "15m", "1h", "7d"
|
* @param expiry - Expiry string like "15m", "1h", "7d"
|
||||||
@@ -19,27 +22,6 @@ export function expiryToHuman(expiry: string): string {
|
|||||||
return expiry;
|
return expiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load email template from file
|
|
||||||
* @param templateName - Name of the template file (without .html extension)
|
|
||||||
* @returns Template content as string
|
|
||||||
*/
|
|
||||||
function loadTemplate(templateName: string): string {
|
|
||||||
try {
|
|
||||||
const templatePath = join(
|
|
||||||
process.cwd(),
|
|
||||||
"src",
|
|
||||||
"server",
|
|
||||||
"email-templates",
|
|
||||||
`${templateName}.html`
|
|
||||||
);
|
|
||||||
return readFileSync(templatePath, "utf-8");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to load email template: ${templateName}`, error);
|
|
||||||
throw new Error(`Email template not found: ${templateName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace placeholders in template with actual values
|
* Replace placeholders in template with actual values
|
||||||
* @param template - Template string with {{PLACEHOLDER}} markers
|
* @param template - Template string with {{PLACEHOLDER}} markers
|
||||||
@@ -68,10 +50,9 @@ export interface LoginLinkEmailParams {
|
|||||||
* Generate login link email HTML
|
* Generate login link email HTML
|
||||||
*/
|
*/
|
||||||
export function generateLoginLinkEmail(params: LoginLinkEmailParams): string {
|
export function generateLoginLinkEmail(params: LoginLinkEmailParams): string {
|
||||||
const template = loadTemplate("login-link");
|
|
||||||
const expiryTime = expiryToHuman(AUTH_CONFIG.EMAIL_LOGIN_LINK_EXPIRY);
|
const expiryTime = expiryToHuman(AUTH_CONFIG.EMAIL_LOGIN_LINK_EXPIRY);
|
||||||
|
|
||||||
return processTemplate(template, {
|
return processTemplate(loginLinkTemplate, {
|
||||||
LOGIN_URL: params.loginUrl,
|
LOGIN_URL: params.loginUrl,
|
||||||
LOGIN_CODE: params.loginCode,
|
LOGIN_CODE: params.loginCode,
|
||||||
EXPIRY_TIME: expiryTime
|
EXPIRY_TIME: expiryTime
|
||||||
@@ -88,10 +69,9 @@ export interface PasswordResetEmailParams {
|
|||||||
export function generatePasswordResetEmail(
|
export function generatePasswordResetEmail(
|
||||||
params: PasswordResetEmailParams
|
params: PasswordResetEmailParams
|
||||||
): string {
|
): string {
|
||||||
const template = loadTemplate("password-reset");
|
|
||||||
const expiryTime = "1 hour"; // Password reset is hardcoded to 1 hour
|
const expiryTime = "1 hour"; // Password reset is hardcoded to 1 hour
|
||||||
|
|
||||||
return processTemplate(template, {
|
return processTemplate(passwordResetTemplate, {
|
||||||
RESET_URL: params.resetUrl,
|
RESET_URL: params.resetUrl,
|
||||||
EXPIRY_TIME: expiryTime
|
EXPIRY_TIME: expiryTime
|
||||||
});
|
});
|
||||||
@@ -107,10 +87,9 @@ export interface EmailVerificationParams {
|
|||||||
export function generateEmailVerificationEmail(
|
export function generateEmailVerificationEmail(
|
||||||
params: EmailVerificationParams
|
params: EmailVerificationParams
|
||||||
): string {
|
): string {
|
||||||
const template = loadTemplate("email-verification");
|
|
||||||
const expiryTime = expiryToHuman(AUTH_CONFIG.EMAIL_VERIFICATION_LINK_EXPIRY);
|
const expiryTime = expiryToHuman(AUTH_CONFIG.EMAIL_VERIFICATION_LINK_EXPIRY);
|
||||||
|
|
||||||
return processTemplate(template, {
|
return processTemplate(emailVerificationTemplate, {
|
||||||
VERIFICATION_URL: params.verificationUrl,
|
VERIFICATION_URL: params.verificationUrl,
|
||||||
EXPIRY_TIME: expiryTime
|
EXPIRY_TIME: expiryTime
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user