Skip to content

Commit 591d287

Browse files
committed
chore: tighten security checks
1 parent 8999155 commit 591d287

31 files changed

Lines changed: 413 additions & 116 deletions

File tree

apps/www/app/actions/message.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,15 @@ export async function sendMessageAction(
112112

113113
const { question, content, receiverId } = params.data;
114114
const { session } = await getSession();
115+
const senderId = session?.userId ?? null;
115116

116-
if (receiverId === session?.userId) {
117+
if (receiverId === senderId) {
117118
return { error: "You can't send a message to yourself" };
118119
}
119120

120121
const formattedContent = formatContent(content);
121122
const encryptedContent = await aesEncrypt(formattedContent);
122123

123-
const senderId = session?.userId ?? null;
124-
125124
if (senderId) {
126125
const blocked = await db.query.userBlockTable.findFirst({
127126
columns: { id: true },

apps/www/app/api/me/route.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { getSession } from "@/lib/auth";
2+
import { privateJson } from "@/lib/private-json";
23
import { getCurrentUserData } from "@/lib/server/data";
34

45
export async function GET() {
56
try {
67
const { session } = await getSession();
78

89
if (!session) {
9-
return Response.json({ error: "Unauthorized" }, { status: 401 });
10+
return privateJson({ error: "Unauthorized" }, { status: 401 });
1011
}
1112

1213
const result = await getCurrentUserData(session.userId);
13-
return Response.json(result);
14+
return privateJson(result);
1415
} catch (error) {
1516
console.error("Error fetching current user:", error);
16-
return Response.json({ error: "Internal server error" }, { status: 500 });
17+
return privateJson({ error: "Internal server error" }, { status: 500 });
1718
}
1819
}

apps/www/app/api/messages/route.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { NextRequest } from "next/server";
22
import { getSession } from "@/lib/auth";
3+
import { privateJson } from "@/lib/private-json";
34
import { getMessagesPage } from "@/lib/server/data";
45

56
export async function GET(req: NextRequest) {
67
try {
78
const { session } = await getSession();
89

910
if (!session) {
10-
return Response.json({ error: "Unauthorized" }, { status: 401 });
11+
return privateJson({ error: "Unauthorized" }, { status: 401 });
1112
}
1213

1314
const searchParams = req.nextUrl.searchParams;
@@ -21,9 +22,9 @@ export async function GET(req: NextRequest) {
2122
userId: session.userId,
2223
});
2324

24-
return Response.json(result);
25+
return privateJson(result);
2526
} catch (error) {
2627
console.error("Error fetching messages:", error);
27-
return Response.json({ error: "Internal server error" }, { status: 500 });
28+
return privateJson({ error: "Internal server error" }, { status: 500 });
2829
}
2930
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { getSession } from "@/lib/auth";
2+
import { privateJson } from "@/lib/private-json";
23
import { getCurrentNoteData } from "@/lib/server/data";
34

45
export async function GET() {
56
try {
67
const { session } = await getSession();
78

89
if (!session?.userId) {
9-
return Response.json({ error: "Unauthorized" }, { status: 401 });
10+
return privateJson({ error: "Unauthorized" }, { status: 401 });
1011
}
1112

1213
const result = await getCurrentNoteData(session.userId);
13-
return Response.json(result);
14+
return privateJson(result);
1415
} catch (error) {
1516
console.error("Error fetching current note:", error);
16-
return Response.json({ error: "Internal server error" }, { status: 500 });
17+
return privateJson({ error: "Internal server error" }, { status: 500 });
1718
}
1819
}

apps/www/app/api/user/[username]/viewer/route.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getSession } from "@/lib/auth";
2+
import { privateJson } from "@/lib/private-json";
23
import { getUserProfileViewerData } from "@/lib/server/data";
34
import { formatUsername } from "@/lib/utils";
45

@@ -13,12 +14,12 @@ export async function GET(
1314
const result = await getUserProfileViewerData(username, session?.userId);
1415

1516
if (!result) {
16-
return Response.json({ error: "Not found" }, { status: 404 });
17+
return privateJson({ error: "Not found" }, { status: 404 });
1718
}
1819

19-
return Response.json(result);
20+
return privateJson(result);
2021
} catch (error) {
2122
console.error("Error fetching user profile viewer overlay:", error);
22-
return Response.json({ error: "Internal server error" }, { status: 500 });
23+
return privateJson({ error: "Internal server error" }, { status: 500 });
2324
}
2425
}

apps/www/app/auth/google/callback/route.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { cookies } from "next/headers";
77
import type { NextRequest } from "next/server";
88
import * as z from "zod";
99
import { getSession } from "@/lib/auth";
10+
import {
11+
GOOGLE_OAUTH_CODE_VERIFIER_COOKIE_NAME,
12+
GOOGLE_OAUTH_STATE_COOKIE_NAME,
13+
} from "@/lib/cookies";
1014
import { google } from "@/lib/oauth";
1115
import {
1216
createSession,
@@ -28,8 +32,27 @@ export async function GET(req: NextRequest) {
2832

2933
const cookieStore = await cookies();
3034

31-
const storedState = cookieStore.get("google_oauth_state")?.value ?? null;
32-
const codeVerifier = cookieStore.get("google_code_verifier")?.value ?? null;
35+
const storedState =
36+
cookieStore.get(GOOGLE_OAUTH_STATE_COOKIE_NAME)?.value ?? null;
37+
const codeVerifier =
38+
cookieStore.get(GOOGLE_OAUTH_CODE_VERIFIER_COOKIE_NAME)?.value ?? null;
39+
40+
const clearOauthCookies = () => {
41+
cookieStore.set(GOOGLE_OAUTH_STATE_COOKIE_NAME, "", {
42+
path: "/",
43+
httpOnly: true,
44+
secure: process.env.NODE_ENV === "production",
45+
maxAge: 0,
46+
sameSite: "lax",
47+
});
48+
cookieStore.set(GOOGLE_OAUTH_CODE_VERIFIER_COOKIE_NAME, "", {
49+
path: "/",
50+
httpOnly: true,
51+
secure: process.env.NODE_ENV === "production",
52+
maxAge: 0,
53+
sameSite: "lax",
54+
});
55+
};
3356

3457
if (
3558
code === null ||
@@ -42,6 +65,8 @@ export async function GET(req: NextRequest) {
4265
});
4366
}
4467

68+
clearOauthCookies();
69+
4570
if (state !== storedState) {
4671
return new Response(null, {
4772
status: 400,

apps/www/app/auth/google/route.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { generateCodeVerifier, generateState } from "arctic";
22
import { cookies } from "next/headers";
3+
import {
4+
GOOGLE_OAUTH_CODE_VERIFIER_COOKIE_NAME,
5+
GOOGLE_OAUTH_STATE_COOKIE_NAME,
6+
} from "@/lib/cookies";
37
import { google } from "@/lib/oauth";
48

59
export async function GET() {
@@ -10,15 +14,15 @@ export async function GET() {
1014

1115
const cookieStore = await cookies();
1216

13-
cookieStore.set("google_oauth_state", state, {
17+
cookieStore.set(GOOGLE_OAUTH_STATE_COOKIE_NAME, state, {
1418
path: "/",
1519
httpOnly: true,
1620
secure: process.env.NODE_ENV === "production",
1721
maxAge: 60 * 10,
1822
sameSite: "lax",
1923
});
2024

21-
cookieStore.set("google_code_verifier", codeVerifier, {
25+
cookieStore.set(GOOGLE_OAUTH_CODE_VERIFIER_COOKIE_NAME, codeVerifier, {
2226
path: "/",
2327
httpOnly: true,
2428
secure: process.env.NODE_ENV === "production",

apps/www/app/feed/page.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getQueryClient } from "@/lib/get-query-client";
55
import { queryKeys } from "@/lib/query";
66
import type { FeedResponse } from "@/lib/query-types";
77
import { getPostsPage } from "@/lib/server/data";
8+
import { toPublicUser } from "@/types/user";
89
import PostForm from "../post/components/post-form";
910
import { PostList } from "./components/post-list";
1011

@@ -15,9 +16,7 @@ export default async function Feed() {
1516

1617
const { user } = await getSession();
1718
const queryClient = getQueryClient();
18-
const publicUser = user
19-
? (({ passwordHash: _pw, ...rest }) => rest)(user)
20-
: null;
19+
const publicUser = user ? toPublicUser(user) : null;
2120

2221
await queryClient.prefetchInfiniteQuery({
2322
queryKey: queryKeys.posts(),

apps/www/app/inbox/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getQueryClient } from "@/lib/get-query-client";
88
import { queryKeys } from "@/lib/query";
99
import type { MessagesResponse } from "@/lib/query-types";
1010
import { getMessagesPage } from "@/lib/server/data";
11+
import { toPublicUser } from "@/types/user";
1112
import { CurrentUserCard } from "./components/current-user-card";
1213
import { InboxTabs } from "./components/inbox-tabs";
1314

@@ -60,7 +61,7 @@ export default async function InboxPage() {
6061
return (
6162
<main className="max-w-xl mx-auto min-h-screen container">
6263
<Suspense fallback={<UserCardSkeleton />}>
63-
<CurrentUserCard user={user} />
64+
<CurrentUserCard user={user ? toPublicUser(user) : null} />
6465
</Suspense>
6566

6667
<HydrationBoundary state={dehydrate(queryClient)}>

apps/www/app/layout.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { GoogleTagManager } from "@next/third-parties/google";
21
import type { Metadata, Viewport } from "next";
32
import { Geist, Geist_Mono } from "next/font/google";
43
import Script from "next/script";
@@ -9,6 +8,7 @@ import Providers from "./providers";
98
import "./globals.css";
109
import { Suspense } from "react";
1110
import { Menubar } from "@/components/menu-bar";
11+
import { getGtmInlineScript } from "@/lib/gtm";
1212

1313
const geistSans = Geist({
1414
variable: "--font-geist-sans",
@@ -83,6 +83,8 @@ export default function RootLayout({
8383
}: Readonly<{
8484
children: React.ReactNode;
8585
}>) {
86+
const gtmId = process.env.GOOGLE_TAG_ID;
87+
8688
return (
8789
<html lang="en" suppressHydrationWarning>
8890
<body
@@ -97,9 +99,25 @@ export default function RootLayout({
9799
<div className="pt-24">{children}</div>
98100
<Footer />
99101
</Providers>
102+
103+
{gtmId && (
104+
<noscript>
105+
<iframe
106+
src={`https://www.googletagmanager.com/ns.html?id=${gtmId}`}
107+
height="0"
108+
width="0"
109+
style={{ display: "none", visibility: "hidden" }}
110+
title="Google Tag Manager"
111+
/>
112+
</noscript>
113+
)}
100114
</body>
101115

102-
<GoogleTagManager gtmId={process.env.GOOGLE_TAG_ID ?? ""} />
116+
{gtmId && (
117+
<Script id="gtm-loader" strategy="afterInteractive">
118+
{getGtmInlineScript(gtmId)}
119+
</Script>
120+
)}
103121

104122
{process.env.NODE_ENV === "production" && (
105123
<Script

0 commit comments

Comments
 (0)