From 9e063d735cdc5f6dba61cc937744a3eae5a075a7 Mon Sep 17 00:00:00 2001 From: ewsgit Date: Tue, 12 May 2026 01:15:26 +0100 Subject: [PATCH] get login working --- frontend/package.json | 4 ++-- frontend/src/components/Counter.css | 21 ------------------ frontend/src/components/Counter.tsx | 11 ---------- frontend/src/lib/authentication.ts | 20 +++++++++++++++-- frontend/src/routes/(default).tsx | 21 +++++++++++++++--- frontend/src/routes/app.module.scss | 5 +++++ frontend/src/routes/app.tsx | 8 +++++++ frontend/src/routes/app/index.tsx | 7 ++++++ frontend/src/routes/login.module.scss | 6 +++++- frontend/src/routes/login.tsx | 4 ++-- frontend/src/routes/oauth/callback.tsx | 30 ++++++++++++++++++++------ frontend/src/routes/oauth/login.tsx | 1 - frontend/src/routes/oauth/logout.tsx | 9 +++++++- frontend/uikit-solid | 2 +- 14 files changed, 97 insertions(+), 52 deletions(-) delete mode 100644 frontend/src/components/Counter.css delete mode 100644 frontend/src/components/Counter.tsx create mode 100644 frontend/src/routes/app.module.scss create mode 100644 frontend/src/routes/app.tsx create mode 100644 frontend/src/routes/app/index.tsx diff --git a/frontend/package.json b/frontend/package.json index 4aef0e3..0e63fd8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "build": "vite build", "start": "vite start", "preview": "vite preview", - "preinstall": "git pull --recurse-submodules" + "preinstall": "git pull --recurse-submodules && git submodule update --recursive --remote" }, "dependencies": { "@atproto/jwk-jose": "^0.1.11", @@ -29,4 +29,4 @@ "@parcel/watcher", "core-js" ] -} \ No newline at end of file +} diff --git a/frontend/src/components/Counter.css b/frontend/src/components/Counter.css deleted file mode 100644 index 220e179..0000000 --- a/frontend/src/components/Counter.css +++ /dev/null @@ -1,21 +0,0 @@ -.increment { - font-family: inherit; - font-size: inherit; - padding: 1em 2em; - color: #335d92; - background-color: rgba(68, 107, 158, 0.1); - border-radius: 2em; - border: 2px solid rgba(68, 107, 158, 0); - outline: none; - width: 200px; - font-variant-numeric: tabular-nums; - cursor: pointer; -} - -.increment:focus { - border: 2px solid #335d92; -} - -.increment:active { - background-color: rgba(68, 107, 158, 0.2); -} \ No newline at end of file diff --git a/frontend/src/components/Counter.tsx b/frontend/src/components/Counter.tsx deleted file mode 100644 index 091fc5d..0000000 --- a/frontend/src/components/Counter.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { createSignal } from "solid-js"; -import "./Counter.css"; - -export default function Counter() { - const [count, setCount] = createSignal(0); - return ( - - ); -} diff --git a/frontend/src/lib/authentication.ts b/frontend/src/lib/authentication.ts index 0a0f6ff..fc2cbac 100644 --- a/frontend/src/lib/authentication.ts +++ b/frontend/src/lib/authentication.ts @@ -6,25 +6,41 @@ import type { NodeSavedSession, NodeSavedState, } from "@atproto/oauth-client-node"; +import { getCookie } from "@solidjs/start/http"; export const SCOPE = "atproto"; -const globalAuth = globalThis as unknown as { +export const globalAuth = globalThis as unknown as { stateStore: Map; sessionStore: Map; + // sessionToken -> DID + sessionTokenStore: Map }; globalAuth.stateStore ??= new Map(); globalAuth.sessionStore ??= new Map(); +globalAuth.sessionTokenStore ??= new Map() let client: NodeOAuthClient | null = null; +export function isValidSession(): boolean { + const did = getCookie("did"); + const sessionToken = getCookie("session_token") + + if (!did || !sessionToken) return false; + + if (globalAuth.sessionTokenStore.get(sessionToken) === did) + return true; + + return false +} + export async function getOAuthClient(): Promise { if (client) return client; client = new NodeOAuthClient({ clientMetadata: buildAtprotoLoopbackClientMetadata({ scope: SCOPE, - redirect_uris: ["http://127.0.0.1:5173/oauth/callback"], + redirect_uris: [`${process.env.PUBLIC_URL || "http://127.0.0.1:5173"}/oauth/callback`], }), stateStore: { diff --git a/frontend/src/routes/(default).tsx b/frontend/src/routes/(default).tsx index baa20f4..ae4dd31 100644 --- a/frontend/src/routes/(default).tsx +++ b/frontend/src/routes/(default).tsx @@ -1,17 +1,32 @@ import { Component, ParentProps } from "solid-js"; import styles from "./(default).module.scss" -import UKText from "@ewsgit/uikit-solid/src/components/text/UKText.jsx"; import UKButton from "@ewsgit/uikit-solid/src/components/button/UKButton.jsx"; -import { useNavigate } from "@solidjs/router"; +import { createAsync, query, useNavigate } from "@solidjs/router"; import ACCOUNT_CIRCLE_ICON from "@material-symbols/svg-700/outlined/account_circle.svg" +import OPEN_IN_NEW_ICON from "@material-symbols/svg-700/outlined/open_in_new.svg"; +import { isValidSession } from "~/lib/authentication"; + +const getIsLoggedIn = query(async () => { + "use server"; + + return isValidSession(); +}, "loggedIn") const DefaultLayout: Component = (props) => { const navigate = useNavigate(); + const isLoggedIn = createAsync(() => getIsLoggedIn()) return
navigate("/")} src="/harmonychat_logo.svg" alt="harmony logo" class={styles.logo} /> - { navigate("/login") }} leadingIcon={ACCOUNT_CIRCLE_ICON}>Login + { + isLoggedIn() + ? <> + { navigate("/app") }} leadingIcon={OPEN_IN_NEW_ICON}>Open App + { navigate("/oauth/logout") }}>Logout + + : { navigate("/login") }} leadingIcon={ACCOUNT_CIRCLE_ICON}>Login + }
{props.children}
diff --git a/frontend/src/routes/app.module.scss b/frontend/src/routes/app.module.scss new file mode 100644 index 0000000..b582ba5 --- /dev/null +++ b/frontend/src/routes/app.module.scss @@ -0,0 +1,5 @@ +.layout { + display: grid; + grid-template-columns: auto 1fr auto; + grid-template-rows: auto 1fr auto; +} diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx new file mode 100644 index 0000000..d740b80 --- /dev/null +++ b/frontend/src/routes/app.tsx @@ -0,0 +1,8 @@ +import { Component, ParentProps } from "solid-js"; +import styles from "./app.module.scss" + +const App: Component = (props) => { + return
{props.children}
+} + +export default App; diff --git a/frontend/src/routes/app/index.tsx b/frontend/src/routes/app/index.tsx new file mode 100644 index 0000000..c0a1709 --- /dev/null +++ b/frontend/src/routes/app/index.tsx @@ -0,0 +1,7 @@ +import { Component } from "solid-js"; + +const AppIndexPage: Component = () => { + return <>App Index Page +} + +export default AppIndexPage diff --git a/frontend/src/routes/login.module.scss b/frontend/src/routes/login.module.scss index ab4a1b0..1bd3510 100644 --- a/frontend/src/routes/login.module.scss +++ b/frontend/src/routes/login.module.scss @@ -19,6 +19,10 @@ margin-bottom: 1rem; } +.errorMessage { + margin-bottom: 0.75rem; +} + .content { display: flex; align-items: center; @@ -41,4 +45,4 @@ .dontHaveAHandle { padding-top: 2rem; -} \ No newline at end of file +} diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index cdf3552..1478af0 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -43,7 +43,7 @@ const LoginPage: Component = () => { <> harmony logo - {error() && {error()}} + {error() && {error()}}
Continue @@ -55,7 +55,7 @@ const LoginPage: Component = () => { ) }
- + } export default LoginPage diff --git a/frontend/src/routes/oauth/callback.tsx b/frontend/src/routes/oauth/callback.tsx index a65762b..a1ab140 100644 --- a/frontend/src/routes/oauth/callback.tsx +++ b/frontend/src/routes/oauth/callback.tsx @@ -1,5 +1,5 @@ import type { APIEvent } from "@solidjs/start/server"; -import { getOAuthClient } from "~/lib/authentication"; +import { getOAuthClient, globalAuth } from "~/lib/authentication"; export async function GET({ request }: APIEvent) { try { @@ -9,12 +9,28 @@ export async function GET({ request }: APIEvent) { const { session } = await client.callback(params); const headers = new Headers(); - headers.append("Location", new URL("/", PUBLIC_URL).toString()); - - const cookieString = `did=${session.did}; HttpOnly; ${process.env.NODE_ENV === "production" ? "Secure; " : "" - }SameSite=Lax; Max-Age=${60 * 60 * 24 * 7}; Path=/`; - - headers.append("Set-Cookie", cookieString); + headers.append("Location", new URL("/app", PUBLIC_URL).toString()); + + function randomString(length: number) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*_-+=.~'; + const arr = new Uint8Array(length); + crypto.getRandomValues(arr); + return Array.from(arr).map(n => chars[n % chars.length]).join(''); + } + + const sessionToken = randomString(128) + globalAuth.sessionTokenStore.set(sessionToken, session.did) + + const cookieAttributes = [ + "HttpOnly", + "Path=/", + "SameSite=Lax", + `Max-Age=${60 * 60 * 24 * 7}`, // 1 week + process.env.NODE_ENV === "production" ? "Secure" : "" + ].filter(Boolean).join("; "); + + headers.append("Set-Cookie", `did=${session.did}; ${cookieAttributes}`); + headers.append("Set-Cookie", `session_token=${sessionToken}; ${cookieAttributes}`); const response = new Response(null, { status: 302, diff --git a/frontend/src/routes/oauth/login.tsx b/frontend/src/routes/oauth/login.tsx index a90970a..3a02153 100644 --- a/frontend/src/routes/oauth/login.tsx +++ b/frontend/src/routes/oauth/login.tsx @@ -21,7 +21,6 @@ export async function POST({ request }: APIEvent) { return Response.json({ redirectUrl: authUrl.toString() }); } catch (error) { - console.error("OAuth login error:", error); return Response.json( { error: error instanceof Error ? error.message : "Login failed" }, { status: 500 } diff --git a/frontend/src/routes/oauth/logout.tsx b/frontend/src/routes/oauth/logout.tsx index 49ad6e3..65d27cc 100644 --- a/frontend/src/routes/oauth/logout.tsx +++ b/frontend/src/routes/oauth/logout.tsx @@ -1,4 +1,4 @@ -import { getOAuthClient } from "~/lib/authentication"; +import { getOAuthClient, globalAuth } from "~/lib/authentication"; import { deleteCookie, getCookie } from "@solidjs/start/http"; export async function POST() { @@ -8,13 +8,20 @@ export async function POST() { if (did) { const client = await getOAuthClient(); await client.revoke(did); + + const sessionToken = getCookie("session_token") + + if (sessionToken) + globalAuth.sessionTokenStore.delete(sessionToken) } deleteCookie("did") + deleteCookie("session_token") return Response.json({ success: true }); } catch (error) { console.error("Logout error:", error); deleteCookie("did") + deleteCookie("session_token") return Response.json({ success: true }); } } diff --git a/frontend/uikit-solid b/frontend/uikit-solid index 9496b1c..e692075 160000 --- a/frontend/uikit-solid +++ b/frontend/uikit-solid @@ -1 +1 @@ -Subproject commit 9496b1c69fdbf2bfe086c9ab6067fa242b1c3b1c +Subproject commit e692075ea2c66d33c30076c3443619275e87e284 -- 2.51.2