From 66b1830b441822c5b57e095a3d88e47619d3d4c8 Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Thu, 23 Jul 2026 16:44:31 +0900
Subject: [PATCH 01/13] Disable oxc/no-async-await
Co-authored-by: Jiwon Kwon
---
.oxlintrc.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/.oxlintrc.json b/.oxlintrc.json
index 721797a..bb2d393 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -26,6 +26,7 @@
"eslint/curly": ["error", "multi-line"],
"node/no-top-level-await": "off",
"oxc/no-optional-chaining": "off",
+ "oxc/no-async-await": "off",
"eslint/eqeqeq": ["off", "smart"],
"eslint/func-style": ["off"],
"eslint/id-length": ["warn", { "exceptionPatterns": ["^_", "^[Tertv]$"] }],
From f8a684a046f5dc46ca6ffdf368ed152a6b3f81f6 Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Thu, 23 Jul 2026 16:44:38 +0900
Subject: [PATCH 02/13] Create sign-in.tsx
Co-authored-by: Jiwon Kwon
---
packages/web/src/routes/sign-in.tsx | 72 +++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100644 packages/web/src/routes/sign-in.tsx
diff --git a/packages/web/src/routes/sign-in.tsx b/packages/web/src/routes/sign-in.tsx
new file mode 100644
index 0000000..562c92b
--- /dev/null
+++ b/packages/web/src/routes/sign-in.tsx
@@ -0,0 +1,72 @@
+// DrFed: A web-based platform for developing and debugging ActivityPub apps
+// Copyright (C) 2026 DrFed team
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import { action, useSubmission } from "@solidjs/router";
+import { Show } from "solid-js";
+
+const signin = action(async (formData: FormData) => {
+ "use server";
+
+ const email = formData.get("email");
+ if (typeof email !== "string") {
+ return { ok: false, message: "이메일이 올바르지 않습니다." };
+ }
+
+ await fetch("http://0.0.0.0:8888/graphql", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ query: `
+ mutation Login($email: Email!, $verifyUrl: URITemplate!) { loginByEmail(email: $email, verifyUrl: $verifyUrl) { token } }
+ `,
+ variables: {
+ email,
+ verifyUrl: "http://localhost:5173/confirm/{token}?code={code}",
+ },
+ }),
+ });
+
+ return { ok: true, message: "인증 메일을 확인해 주세요." };
+}, "signin");
+
+export default function SignInPage() {
+ const submission = useSubmission(signin);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
From 1a51847ebf6e0357779c1cba6998d196b32800fd Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Thu, 23 Jul 2026 16:45:42 +0900
Subject: [PATCH 03/13] FIXME: Add allowlist
Co-authored-by: Jiwon Kwon
---
packages/graphql/src/index.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts
index 115ec9a..87320c9 100644
--- a/packages/graphql/src/index.ts
+++ b/packages/graphql/src/index.ts
@@ -92,7 +92,9 @@ function mockTransport() {
const fillOptions = (opt: YogaServerOptions): Required => ({
mailer: opt?.mailer ?? mockTransport(),
emailFrom: opt?.emailFrom ?? "noreply@drfed.org",
- origins: opt?.origins ?? new Set(["https://drfed.org"]),
+ // FIXME: Properly parametrize the following allowlist:
+ origins:
+ opt?.origins ?? new Set(["https://drfed.org", "http://localhost:5173"]),
});
const getAccessToken = (headers: Headers) =>
From ab45e493fe8a0c8f974036d3456a6869b81ac3a2 Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Thu, 23 Jul 2026 17:48:53 +0900
Subject: [PATCH 04/13] Turn off eslint/max-lines-per-function rule
---
.oxlintrc.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/.oxlintrc.json b/.oxlintrc.json
index bb2d393..9126f90 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -29,6 +29,7 @@
"oxc/no-async-await": "off",
"eslint/eqeqeq": ["off", "smart"],
"eslint/func-style": ["off"],
+ "eslint/max-lines-per-function": "off",
"eslint/id-length": ["warn", { "exceptionPatterns": ["^_", "^[Tertv]$"] }],
"eslint/init-declarations": "off",
"eslint/max-params": ["warn", { "max": 4 }],
From 118ee83790c739a3d5394e9d7f11269b0159e451 Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Sat, 25 Jul 2026 14:04:49 +0900
Subject: [PATCH 05/13] Solve CORS Problem
---
packages/graphql/src/index.ts | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts
index 87320c9..9e991b4 100644
--- a/packages/graphql/src/index.ts
+++ b/packages/graphql/src/index.ts
@@ -62,6 +62,10 @@ export function createYogaServer(
): YogaServerInstance {
const options = fillOptions(_options);
return createYoga({
+ cors: {
+ origin: [...options.origins],
+ credentials: true,
+ },
async context(ctx) {
const anonymous = { db, request: ctx.request, ...options };
const accessToken = getAccessToken(ctx.request.headers);
@@ -94,7 +98,12 @@ const fillOptions = (opt: YogaServerOptions): Required => ({
emailFrom: opt?.emailFrom ?? "noreply@drfed.org",
// FIXME: Properly parametrize the following allowlist:
origins:
- opt?.origins ?? new Set(["https://drfed.org", "http://localhost:5173"]),
+ opt?.origins ??
+ new Set([
+ "https://drfed.org",
+ "http://localhost:5173",
+ "http://0.0.0.0:5173",
+ ]),
});
const getAccessToken = (headers: Headers) =>
From d05b1528d1fac6cb7f0d1025008181ce883559bc Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Thu, 23 Jul 2026 17:49:53 +0900
Subject: [PATCH 06/13] Add Sign-in Page
---
packages/web/src/routes/sign-in.tsx | 62 ++++++++++++++++-------------
1 file changed, 35 insertions(+), 27 deletions(-)
diff --git a/packages/web/src/routes/sign-in.tsx b/packages/web/src/routes/sign-in.tsx
index 562c92b..a5c82f6 100644
--- a/packages/web/src/routes/sign-in.tsx
+++ b/packages/web/src/routes/sign-in.tsx
@@ -14,42 +14,50 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import { action, useSubmission } from "@solidjs/router";
-import { Show } from "solid-js";
+import { graphql } from "relay-runtime";
+import { type JSX, Show, createSignal } from "solid-js";
+import { createMutation } from "solid-relay";
-const signin = action(async (formData: FormData) => {
- "use server";
+import type { SignInMutation } from "./__generated__/SignInMutation.graphql";
- const email = formData.get("email");
- if (typeof email !== "string") {
- return { ok: false, message: "이메일이 올바르지 않습니다." };
+const signInMutation = graphql`
+ mutation SignInMutation($email: Email!, $verifyUrl: URITemplate) {
+ loginByEmail(email: $email, verifyUrl: $verifyUrl) {
+ token
+ }
}
+`;
- await fetch("http://0.0.0.0:8888/graphql", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: `
- mutation Login($email: Email!, $verifyUrl: URITemplate!) { loginByEmail(email: $email, verifyUrl: $verifyUrl) { token } }
- `,
+export default function SignInPage() {
+ const [signIn] = createMutation(signInMutation);
+ const [message, setMessage] = createSignal("");
+
+ const handleSubmit: JSX.EventHandler = (e) => {
+ e.preventDefault();
+ const email = new FormData(e.currentTarget).get("email");
+ if (typeof email !== "string" || email === "") {
+ return;
+ }
+
+ signIn({
variables: {
email,
- verifyUrl: "http://localhost:5173/confirm/{token}?code={code}",
+ verifyUrl: `${globalThis.location.origin}/confirm/{token}?code={code}`,
},
- }),
- });
-
- return { ok: true, message: "인증 메일을 확인해 주세요." };
-}, "signin");
+ onCompleted: (_response, errors) => {
+ const [error] = errors ?? [];
-export default function SignInPage() {
- const submission = useSubmission(signin);
+ setMessage(error?.message ?? "인증 메일을 확인해 주세요.");
+ },
+ onError: (error) => {
+ setMessage(error.message);
+ },
+ });
+ };
return (
<>
-
-
+
}>
+ You're not signed in.}>
{(viewer) => (
{viewer().name}
diff --git a/packages/web/src/routes/session.ts b/packages/web/src/routes/session.ts
index 5b0dad8..8e1c11a 100644
--- a/packages/web/src/routes/session.ts
+++ b/packages/web/src/routes/session.ts
@@ -16,6 +16,9 @@
import type { APIEvent } from "@solidjs/start/server";
+const SESSION_COOKIE = "session";
+const ACCESS_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
+
// Note: setCookie(nativeEvent, ...) from @solidjs/start/http is intentionally
// NOT used here. In @solidjs/start 2.0.0-alpha.2, that function produces a
// malformed Set-Cookie header in both "use server" RPCs and POST API route
@@ -41,11 +44,14 @@ export async function POST({ request }: APIEvent) {
? body.expires
: undefined;
- // FIXME: Accesstoken validation
if (accessToken == undefined || expiresValue == undefined) {
return new Response(undefined, { status: 400 });
}
+ if (!ACCESS_TOKEN_PATTERN.test(accessToken)) {
+ return new Response(undefined, { status: 400 });
+ }
+
let expires: Temporal.Instant;
try {
@@ -59,7 +65,7 @@ export async function POST({ request }: APIEvent) {
}
const cookie = [
- `session=${encodeURIComponent(accessToken)}`,
+ `${SESSION_COOKIE}=${encodeURIComponent(accessToken)}`,
"HttpOnly",
"Path=/",
"SameSite=Lax",
@@ -72,3 +78,34 @@ export async function POST({ request }: APIEvent) {
headers: { "Set-Cookie": cookie },
});
}
+
+export function readSessionCookie(
+ request: Request | undefined,
+): string | undefined {
+ const cookieHeader = request?.headers.get("cookie");
+
+ if (cookieHeader == undefined || cookieHeader == "") {
+ return undefined;
+ }
+ for (const part of cookieHeader.split(";")) {
+ const eq = part.indexOf("=");
+ if (eq === -1) {
+ continue;
+ }
+ if (part.slice(0, eq).trim() !== SESSION_COOKIE) {
+ continue;
+ }
+ const raw = part.slice(eq + 1).trim();
+ if (raw === "") {
+ return undefined;
+ }
+ let decoded: string;
+ try {
+ decoded = decodeURIComponent(raw);
+ } catch {
+ return undefined;
+ }
+ return ACCESS_TOKEN_PATTERN.test(decoded) ? decoded : undefined;
+ }
+ return undefined;
+}
From 591fe173da758eb3ef8ec79cb4cf7f587d8b9717 Mon Sep 17 00:00:00 2001
From: "Kim, Hyeonseo"
Date: Tue, 28 Jul 2026 14:47:09 +0900
Subject: [PATCH 13/13] Change placeholder texts to DrFed
Co-authored-by: Jiwon Kwon
---
packages/web/src/RelayEnviroment.ts | 2 +-
packages/web/src/routes/[...404].tsx | 8 ++++----
packages/web/src/routes/about.tsx | 8 +++++++-
packages/web/src/routes/index.tsx | 18 ++++++------------
4 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/packages/web/src/RelayEnviroment.ts b/packages/web/src/RelayEnviroment.ts
index 0f6bfb9..66e8f94 100644
--- a/packages/web/src/RelayEnviroment.ts
+++ b/packages/web/src/RelayEnviroment.ts
@@ -42,7 +42,7 @@ const fetchFn: FetchFunction = async (params, variables) => {
query: params.text,
variables,
}),
- credentials: "include"
+ credentials: "include",
});
// oxlint-disable return-await no-unsafe-return
diff --git a/packages/web/src/routes/[...404].tsx b/packages/web/src/routes/[...404].tsx
index 25708e8..b5e0971 100644
--- a/packages/web/src/routes/[...404].tsx
+++ b/packages/web/src/routes/[...404].tsx
@@ -25,10 +25,10 @@ export default function NotFound() {
Page Not Found
Visit{" "}
-
- start.solidjs.com
- {" "}
- to learn how to build SolidStart apps.
+
+ DrFed
+
+ : A web-based platform for developing and debugging ActivityPub apps
);
diff --git a/packages/web/src/routes/about.tsx b/packages/web/src/routes/about.tsx
index 34dbe99..84ec70c 100644
--- a/packages/web/src/routes/about.tsx
+++ b/packages/web/src/routes/about.tsx
@@ -20,7 +20,13 @@ export default function About() {
return (
About
- About
+
+ Visit{" "}
+
+ DrFed
+
+ : A web-based platform for developing and debugging ActivityPub apps
+
);
}
diff --git a/packages/web/src/routes/index.tsx b/packages/web/src/routes/index.tsx
index 35f3258..a08e0ee 100644
--- a/packages/web/src/routes/index.tsx
+++ b/packages/web/src/routes/index.tsx
@@ -35,23 +35,17 @@ export default function Home() {
return (
- Hello World
- Hello world!
+ Home
You're not signed in.
}>
- {(viewer) => (
-
- {viewer().name}
- {viewer().admin ? " (관리자)" : ""}
-
- )}
+ {(viewer) => {viewer().name}
}
Visit{" "}
-
- start.solidjs.com
- {" "}
- to learn how to build SolidStart apps.
+
+ DrFed
+
+ : A web-based platform for developing and debugging ActivityPub apps
);