cleanup
This commit is contained in:
@@ -5,7 +5,7 @@ import LikeIcon from "~/components/icons/LikeIcon";
|
|||||||
export interface PostLike {
|
export interface PostLike {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
post_id: string;
|
post_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthenticatedLikeProps {
|
export interface AuthenticatedLikeProps {
|
||||||
@@ -36,15 +36,15 @@ export default function AuthenticatedLike(props: AuthenticatedLikeProps) {
|
|||||||
if (initialHasLiked) {
|
if (initialHasLiked) {
|
||||||
const result = await api.database.removePostLike.mutate({
|
const result = await api.database.removePostLike.mutate({
|
||||||
user_id: props.currentUserID,
|
user_id: props.currentUserID,
|
||||||
post_id: props.projectID.toString()
|
post_id: props.projectID
|
||||||
});
|
});
|
||||||
setLikes(result.newLikes as PostLike[]);
|
setLikes(result.newLikes as unknown as PostLike[]);
|
||||||
} else {
|
} else {
|
||||||
const result = await api.database.addPostLike.mutate({
|
const result = await api.database.addPostLike.mutate({
|
||||||
user_id: props.currentUserID,
|
user_id: props.currentUserID,
|
||||||
post_id: props.projectID.toString()
|
post_id: props.projectID
|
||||||
});
|
});
|
||||||
setLikes(result.newLikes as PostLike[]);
|
setLikes(result.newLikes as unknown as PostLike[]);
|
||||||
}
|
}
|
||||||
setInstantOffset(0);
|
setInstantOffset(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import type {
|
|||||||
UserPublicData,
|
UserPublicData,
|
||||||
ReactionType,
|
ReactionType,
|
||||||
ModificationType,
|
ModificationType,
|
||||||
SortingMode
|
SortingMode,
|
||||||
|
CommentSectionProps
|
||||||
} from "~/types/comment";
|
} from "~/types/comment";
|
||||||
import CommentInputBlock from "./CommentInputBlock";
|
import CommentInputBlock from "./CommentInputBlock";
|
||||||
import CommentSortingSelect from "./CommentSortingSelect";
|
import CommentSortingSelect from "./CommentSortingSelect";
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export default function DeletePostButton(props: DeletePostButtonProps) {
|
|||||||
await api.database.deletePost.mutate({ id: props.postID });
|
await api.database.deletePost.mutate({ id: props.postID });
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Failed to delete post:", error);
|
||||||
alert("Failed to delete post");
|
alert("Failed to delete post");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,11 +271,12 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
|||||||
const headings = contentRef.querySelectorAll<HTMLElement>("h2");
|
const headings = contentRef.querySelectorAll<HTMLElement>("h2");
|
||||||
let referencesSection: HTMLElement | null = null;
|
let referencesSection: HTMLElement | null = null;
|
||||||
|
|
||||||
headings.forEach((heading) => {
|
for (const heading of headings) {
|
||||||
if (heading.textContent?.trim() === referencesHeadingText) {
|
if (heading.textContent?.trim() === referencesHeadingText) {
|
||||||
referencesSection = heading;
|
referencesSection = heading;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
if (referencesSection) {
|
if (referencesSection) {
|
||||||
referencesSection.className = "text-2xl font-bold mb-4 text-text";
|
referencesSection.className = "text-2xl font-bold mb-4 text-text";
|
||||||
@@ -285,7 +286,7 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
|||||||
parentDiv.classList.add("references-heading");
|
parentDiv.classList.add("references-heading");
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentElement = referencesSection.nextElementSibling;
|
let currentElement: Element | null = referencesSection.nextElementSibling;
|
||||||
|
|
||||||
while (currentElement) {
|
while (currentElement) {
|
||||||
if (currentElement.tagName === "P") {
|
if (currentElement.tagName === "P") {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface PostFormProps {
|
|||||||
published: boolean;
|
published: boolean;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
};
|
};
|
||||||
userID: number;
|
userID: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PostForm(props: PostFormProps) {
|
export default function PostForm(props: PostFormProps) {
|
||||||
@@ -89,7 +89,7 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
tags: null,
|
tags: null,
|
||||||
author_id: props.userID
|
author_id: props.userID
|
||||||
});
|
});
|
||||||
const newId = result.data as number;
|
const newId = Number(result.data);
|
||||||
setCreatedPostId(newId);
|
setCreatedPostId(newId);
|
||||||
setHasSaved(true);
|
setHasSaved(true);
|
||||||
return newId;
|
return newId;
|
||||||
|
|||||||
79
src/lib/auth-callback-utils.ts
Normal file
79
src/lib/auth-callback-utils.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
|
import { createServerCaller } from "~/server/api/root";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result from an auth callback tRPC procedure
|
||||||
|
*/
|
||||||
|
interface AuthCallbackResult {
|
||||||
|
success: boolean;
|
||||||
|
redirectTo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a successful auth callback result by redirecting
|
||||||
|
*/
|
||||||
|
export function redirectSuccess(result: AuthCallbackResult) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 302,
|
||||||
|
headers: { Location: result.redirectTo || "/account" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to login with an error parameter
|
||||||
|
*/
|
||||||
|
export function redirectError(error: string) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 302,
|
||||||
|
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle tRPC CONFLICT error (email already in use)
|
||||||
|
*/
|
||||||
|
export function isConflictError(error: unknown): boolean {
|
||||||
|
return (
|
||||||
|
error != null &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"code" in error &&
|
||||||
|
(error as { code: string }).code === "CONFLICT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an auth callback handler that calls a tRPC procedure and redirects
|
||||||
|
*/
|
||||||
|
export function createAuthCallbackHandler<Params extends object>(
|
||||||
|
procedureName: string,
|
||||||
|
callProcedure: (
|
||||||
|
caller: ReturnType<typeof createServerCaller> extends Promise<infer T>
|
||||||
|
? T
|
||||||
|
: never,
|
||||||
|
params: Params
|
||||||
|
) => Promise<AuthCallbackResult>,
|
||||||
|
handleError?: (error: unknown) => Response
|
||||||
|
) {
|
||||||
|
return async (event: APIEvent, params: Params) => {
|
||||||
|
try {
|
||||||
|
const caller = await createServerCaller(event);
|
||||||
|
const result = await callProcedure(caller, params);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return redirectSuccess(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectError("auth_failed");
|
||||||
|
} catch (error) {
|
||||||
|
if (handleError) {
|
||||||
|
return handleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isConflictError(error)) {
|
||||||
|
return redirectError("email_in_use");
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectError("server_error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import { env } from "~/env/server";
|
|||||||
*
|
*
|
||||||
* URL: https://freno.me/api/Gaze/appcast.xml
|
* URL: https://freno.me/api/Gaze/appcast.xml
|
||||||
*/
|
*/
|
||||||
export async function GET(event: APIEvent) {
|
export async function GET(_event: APIEvent) {
|
||||||
const bucket = env.VITE_DOWNLOAD_BUCKET_STRING;
|
const bucket = env.VITE_DOWNLOAD_BUCKET_STRING;
|
||||||
const key = "api/Gaze/appcast.xml";
|
const key = "api/Gaze/appcast.xml";
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export async function GET(event: APIEvent) {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/xml; charset=utf-8",
|
"Content-Type": "application/xml; charset=utf-8",
|
||||||
"Cache-Control": "public, max-age=300", // Cache for 5 minutes
|
"Cache-Control": "public, max-age=300", // Cache for 5 minutes
|
||||||
"Access-Control-Allow-Origin": "*" // Allow CORS for appcast
|
"Access-Control-Allow-Origin": "*" // Allow CORS for Sparkle appcast
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { env } from "~/env/server";
|
|||||||
*
|
*
|
||||||
* URL: https://freno.me/api/InputHalo/appcast.xml
|
* URL: https://freno.me/api/InputHalo/appcast.xml
|
||||||
*/
|
*/
|
||||||
export async function GET(event: APIEvent) {
|
export async function GET(_event: APIEvent) {
|
||||||
const bucket = env.VITE_DOWNLOAD_BUCKET_STRING;
|
const bucket = env.VITE_DOWNLOAD_BUCKET_STRING;
|
||||||
const key = "api/InputHalo/appcast.xml";
|
const key = "api/InputHalo/appcast.xml";
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export async function GET(event: APIEvent) {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/xml; charset=utf-8",
|
"Content-Type": "application/xml; charset=utf-8",
|
||||||
"Cache-Control": "public, max-age=300", // Cache for 5 minutes
|
"Cache-Control": "public, max-age=300", // Cache for 5 minutes
|
||||||
"Access-Control-Allow-Origin": "*" // Allow CORS for appcast
|
"Access-Control-Allow-Origin": "*" // Allow CORS for Sparkle appcast
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,86 +1,26 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server";
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
import { createServerCaller } from "~/server/api/root";
|
import {
|
||||||
|
createAuthCallbackHandler,
|
||||||
|
redirectError
|
||||||
|
} from "~/lib/auth-callback-utils";
|
||||||
|
|
||||||
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 redirectError(error);
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
console.error("[GitHub OAuth Callback] Missing authorization code");
|
return redirectError("missing_code");
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=missing_code" }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const handler = createAuthCallbackHandler<{ code: string }>(
|
||||||
console.log("[GitHub OAuth Callback] Creating tRPC caller...");
|
"githubCallback",
|
||||||
const caller = await createServerCaller(event);
|
(caller, params) => caller.auth.githubCallback(params)
|
||||||
|
);
|
||||||
|
|
||||||
console.log("[GitHub OAuth Callback] Calling githubCallback procedure...");
|
return handler(event, { code });
|
||||||
const result = await caller.auth.githubCallback({ code });
|
|
||||||
|
|
||||||
console.log("[GitHub OAuth Callback] Result:", result);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log(
|
|
||||||
"[GitHub OAuth Callback] Login successful, redirecting to:",
|
|
||||||
result.redirectTo
|
|
||||||
);
|
|
||||||
|
|
||||||
// Auth handler already set cookie headers
|
|
||||||
// Just redirect - the cookies are already in the response
|
|
||||||
const redirectUrl = result.redirectTo || "/account";
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: redirectUrl }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
"[GitHub OAuth Callback] Login failed (result.success=false)"
|
|
||||||
);
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=auth_failed" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[GitHub OAuth Callback] Error caught:", error);
|
|
||||||
|
|
||||||
if (error && typeof error === "object" && "code" in error) {
|
|
||||||
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") {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=email_in_use" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=server_error" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,26 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server";
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
import { createServerCaller } from "~/server/api/root";
|
import {
|
||||||
|
createAuthCallbackHandler,
|
||||||
|
redirectError
|
||||||
|
} from "~/lib/auth-callback-utils";
|
||||||
|
|
||||||
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 redirectError(error);
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: `/login?error=${encodeURIComponent(error)}` }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
console.error("[Google OAuth Callback] Missing authorization code");
|
return redirectError("missing_code");
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=missing_code" }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const handler = createAuthCallbackHandler<{ code: string }>(
|
||||||
console.log("[Google OAuth Callback] Creating tRPC caller...");
|
"googleCallback",
|
||||||
const caller = await createServerCaller(event);
|
(caller, params) => caller.auth.googleCallback(params)
|
||||||
|
);
|
||||||
|
|
||||||
console.log("[Google OAuth Callback] Calling googleCallback procedure...");
|
return handler(event, { code });
|
||||||
const result = await caller.auth.googleCallback({ code });
|
|
||||||
|
|
||||||
console.log("[Google OAuth Callback] Result:", result);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log(
|
|
||||||
"[Google OAuth Callback] Login successful, redirecting to:",
|
|
||||||
result.redirectTo
|
|
||||||
);
|
|
||||||
|
|
||||||
// Auth handler already set cookie headers
|
|
||||||
// Just redirect - the cookies are already in the response
|
|
||||||
const redirectUrl = result.redirectTo || "/account";
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: redirectUrl }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
"[Google OAuth Callback] Login failed (result.success=false)"
|
|
||||||
);
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=auth_failed" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[Google OAuth Callback] Error caught:", error);
|
|
||||||
|
|
||||||
if (error && typeof error === "object" && "code" in error) {
|
|
||||||
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") {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=email_in_use" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=server_error" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,32 @@
|
|||||||
import type { APIEvent } from "@solidjs/start/server";
|
import type { APIEvent } from "@solidjs/start/server";
|
||||||
import { createServerCaller } from "~/server/api/root";
|
import {
|
||||||
|
createAuthCallbackHandler,
|
||||||
|
redirectError
|
||||||
|
} from "~/lib/auth-callback-utils";
|
||||||
|
|
||||||
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 email = url.searchParams.get("email");
|
const email = url.searchParams.get("email");
|
||||||
const token = url.searchParams.get("token");
|
const token = url.searchParams.get("token");
|
||||||
|
|
||||||
console.log("[Email Login Callback] Request received:", {
|
|
||||||
email,
|
|
||||||
hasToken: !!token,
|
|
||||||
tokenLength: token?.length
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!email || !token) {
|
if (!email || !token) {
|
||||||
console.error("[Email Login Callback] Missing required parameters:", {
|
return redirectError("missing_params");
|
||||||
hasEmail: !!email,
|
|
||||||
hasToken: !!token
|
|
||||||
});
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=missing_params" }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const handler = createAuthCallbackHandler<{
|
||||||
console.log("[Email Login Callback] Creating tRPC caller...");
|
email: string;
|
||||||
// Create tRPC caller to invoke the emailLogin procedure
|
token: string;
|
||||||
const caller = await createServerCaller(event);
|
}>(
|
||||||
|
"emailLogin",
|
||||||
console.log("[Email Login Callback] Calling emailLogin procedure...");
|
(caller, params) => caller.auth.emailLogin(params),
|
||||||
// Call the email login handler - rememberMe will be read from JWT payload
|
(error) => {
|
||||||
const result = await caller.auth.emailLogin({
|
// Check for token expiration
|
||||||
email,
|
const message = error instanceof Error ? error.message : "";
|
||||||
token
|
const isTokenError =
|
||||||
});
|
message.includes("expired") || message.includes("invalid");
|
||||||
|
return redirectError(isTokenError ? "link_expired" : "server_error");
|
||||||
console.log("[Email Login Callback] Login result:", result);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log(
|
|
||||||
"[Email Login Callback] Login successful, redirecting to:",
|
|
||||||
result.redirectTo
|
|
||||||
);
|
|
||||||
|
|
||||||
// Auth handler already set cookie headers
|
|
||||||
// Just redirect - the cookies are already in the response
|
|
||||||
const redirectUrl = result.redirectTo || "/account";
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: redirectUrl }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
"[Email Login Callback] Login failed (result.success=false)"
|
|
||||||
);
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: { Location: "/login?error=auth_failed" }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
);
|
||||||
console.error("[Email Login Callback] Error caught:", error);
|
|
||||||
|
|
||||||
// Check if it's a token expiration error
|
return handler(event, { email, token });
|
||||||
const errorMessage =
|
|
||||||
error instanceof Error ? error.message : "server_error";
|
|
||||||
const isTokenError =
|
|
||||||
errorMessage.includes("expired") || errorMessage.includes("invalid");
|
|
||||||
|
|
||||||
console.error("[Email Login Callback] Error details:", {
|
|
||||||
errorMessage,
|
|
||||||
isTokenError,
|
|
||||||
errorType: error instanceof Error ? error.constructor.name : typeof error
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: 302,
|
|
||||||
headers: {
|
|
||||||
Location: isTokenError
|
|
||||||
? "/login?error=link_expired"
|
|
||||||
: "/login?error=server_error"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ export const reactionTypeSchema = z.enum([
|
|||||||
"moneyEye",
|
"moneyEye",
|
||||||
"sick",
|
"sick",
|
||||||
"upsideDown",
|
"upsideDown",
|
||||||
"worried"
|
"worried",
|
||||||
|
"upVote",
|
||||||
|
"downVote"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { H3Event } from "vinxi/http";
|
import type { H3Event } from "vinxi/http";
|
||||||
import { getCookie, setCookie } from "vinxi/http";
|
import { getCookie, setCookie, getHeader } from "vinxi/http";
|
||||||
import { OAuth2Client } from "google-auth-library";
|
import { OAuth2Client } from "google-auth-library";
|
||||||
import type { Row } from "@libsql/client/web";
|
import type { Row } from "@libsql/client/web";
|
||||||
import { SignJWT, jwtVerify } from "jose";
|
import { SignJWT, jwtVerify } from "jose";
|
||||||
import { env } from "~/env/server";
|
import { env } from "~/env/server";
|
||||||
import { ConnectionFactory } from "./database";
|
import { ConnectionFactory } from "./db-connections";
|
||||||
import { AUTH_CONFIG, expiryToSeconds, getAccessTokenExpiry } from "~/config";
|
import { AUTH_CONFIG, expiryToSeconds, getAccessTokenExpiry } from "~/config";
|
||||||
|
|
||||||
export const authCookieName = "auth_token";
|
export const authCookieName = "auth_token";
|
||||||
@@ -30,7 +30,7 @@ function getAuthCookieOptions(rememberMe: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAuthHeaderToken(event: H3Event): string | null {
|
function getAuthHeaderToken(event: H3Event): string | null {
|
||||||
const requestHeader = event.request?.headers?.get?.("authorization") || null;
|
const requestHeader = getHeader(event, "authorization") || null;
|
||||||
const eventHeader = event.headers
|
const eventHeader = event.headers
|
||||||
? typeof (event.headers as any).get === "function"
|
? typeof (event.headers as any).get === "function"
|
||||||
? (event.headers as any).get("authorization")
|
? (event.headers as any).get("authorization")
|
||||||
@@ -199,6 +199,7 @@ export async function validateLineageRequest({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("Failed to verify email auth token:", err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (provider == "apple") {
|
} else if (provider == "apple") {
|
||||||
|
|||||||
@@ -11,43 +11,13 @@ import {
|
|||||||
TimeoutError,
|
TimeoutError,
|
||||||
APIError
|
APIError
|
||||||
} from "~/server/fetch-utils";
|
} from "~/server/fetch-utils";
|
||||||
|
import {
|
||||||
let mainDBConnection: ReturnType<typeof createClient> | null = null;
|
ConnectionFactory,
|
||||||
let lineageDBConnection: ReturnType<typeof createClient> | null = null;
|
LineageConnectionFactory,
|
||||||
let nessaDBConnection: ReturnType<typeof createClient> | null = null;
|
NessaConnectionFactory
|
||||||
|
} from "~/server/db-connections";
|
||||||
export function ConnectionFactory() {
|
// Re-export connection factories to avoid circular import with auth.ts
|
||||||
if (!mainDBConnection) {
|
export { ConnectionFactory, LineageConnectionFactory, NessaConnectionFactory };
|
||||||
const config = {
|
|
||||||
url: env.TURSO_DB_URL,
|
|
||||||
authToken: env.TURSO_DB_TOKEN
|
|
||||||
};
|
|
||||||
mainDBConnection = createClient(config);
|
|
||||||
}
|
|
||||||
return mainDBConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LineageConnectionFactory() {
|
|
||||||
if (!lineageDBConnection) {
|
|
||||||
const config = {
|
|
||||||
url: env.TURSO_LINEAGE_URL,
|
|
||||||
authToken: env.TURSO_LINEAGE_TOKEN
|
|
||||||
};
|
|
||||||
lineageDBConnection = createClient(config);
|
|
||||||
}
|
|
||||||
return lineageDBConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NessaConnectionFactory() {
|
|
||||||
if (!nessaDBConnection) {
|
|
||||||
const config = {
|
|
||||||
url: env.NESSA_DB_URL,
|
|
||||||
authToken: env.NESSA_DB_TOKEN
|
|
||||||
};
|
|
||||||
nessaDBConnection = createClient(config);
|
|
||||||
}
|
|
||||||
return nessaDBConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function LineageDBInit() {
|
export async function LineageDBInit() {
|
||||||
const turso = createAPIClient({
|
const turso = createAPIClient({
|
||||||
@@ -209,7 +179,7 @@ export async function getUserBasicInfo(event: H3Event): Promise<{
|
|||||||
return { email: null, isAuthenticated: false };
|
return { email: null, isAuthenticated: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = res.rows[0] as { email: string | null };
|
const user = res.rows[0] as unknown as { email: string | null };
|
||||||
return {
|
return {
|
||||||
email: user.email,
|
email: user.email,
|
||||||
isAuthenticated: true
|
isAuthenticated: true
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
import { defineMiddleware } from "vinxi/http";
|
import { defineMiddleware, setHeaders } from "vinxi/http";
|
||||||
|
|
||||||
// Security headers middleware — sets CSP and hardening headers on all responses
|
// Security headers middleware — sets CSP and hardening headers on all responses
|
||||||
export default defineMiddleware((_event, next) => {
|
export default defineMiddleware({
|
||||||
return next().then((response) => {
|
onRequest: (event) => {
|
||||||
response.headers.set(
|
setHeaders(event, {
|
||||||
"Content-Security-Policy",
|
"Content-Security-Policy":
|
||||||
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'",
|
||||||
);
|
"X-Content-Type-Options": "nosniff",
|
||||||
response.headers.set("X-Content-Type-Options", "nosniff");
|
"X-Frame-Options": "DENY",
|
||||||
response.headers.set("X-Frame-Options", "DENY");
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||||
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
"Permissions-Policy": "camera=(), microphone=(), geolocation=()"
|
||||||
response.headers.set(
|
});
|
||||||
"Permissions-Policy",
|
}
|
||||||
"camera=(), microphone=(), geolocation=()"
|
|
||||||
);
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ function getCookieValue(event: H3Event, name: string): string | undefined {
|
|||||||
try {
|
try {
|
||||||
const value = getCookie(event, name);
|
const value = getCookie(event, name);
|
||||||
if (value) return value;
|
if (value) return value;
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
console.warn(
|
||||||
|
"[security] getCookie failed, falling back to header parse:",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cookieHeader =
|
const cookieHeader =
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ export type ReactionType =
|
|||||||
| "moneyEye"
|
| "moneyEye"
|
||||||
| "sick"
|
| "sick"
|
||||||
| "upsideDown"
|
| "upsideDown"
|
||||||
| "worried";
|
| "worried"
|
||||||
|
| "upVote"
|
||||||
|
| "downVote";
|
||||||
|
|
||||||
export interface UserPublicData {
|
export interface UserPublicData {
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user