diff --git a/web/src/lib/auth.svelte.ts b/web/src/lib/auth.svelte.ts index eb8c9bebf..9ebd3b5e9 100644 --- a/web/src/lib/auth.svelte.ts +++ b/web/src/lib/auth.svelte.ts @@ -18,7 +18,7 @@ import { } from "@atcute/oauth-browser-client"; import { getContext } from "svelte"; import { SvelteMap, SvelteURL, SvelteURLSearchParams } from "svelte/reactivity"; -import oauthMetadata from "$lib/oauth-client-metadata"; +import oauthMetadata, { loopbackClientId } from "$lib/oauth-client-metadata"; import { type AuthAccount, clearActive, @@ -38,7 +38,6 @@ import { isDeadSessionError } from "$lib/auth/session"; export const AUTH_KEY = Symbol("auth"); const DEV_REDIRECT_URI = `http://127.0.0.1:${import.meta.env.VITE_DEV_PORT ?? "5173"}/oauth/callback`; -const DEV_CLIENT_ID = `http://localhost?redirect_uri=${encodeURIComponent(DEV_REDIRECT_URI)}&scope=${encodeURIComponent(oauthMetadata.scope)}`; // local dev (localinfra) points these at the local pds/plc; defaults are the public network. const HANDLE_RESOLVER_URL = @@ -125,6 +124,8 @@ type MiniDoc = { type OAuthSession = ConstructorParameters[0]; +const OAUTH_SCOPE = (import.meta.env.VITE_OAUTH_SCOPE as string | undefined) ?? oauthMetadata.scope; +const DEV_CLIENT_ID = loopbackClientId(DEV_REDIRECT_URI, OAUTH_SCOPE); const ENV_OAUTH_REDIRECT_URI = import.meta.env.VITE_OAUTH_REDIRECT_URI as string | undefined; const HAS_LOCALHOST_REDIRECT = ENV_OAUTH_REDIRECT_URI?.includes("://localhost") ?? false; const OAUTH_CLIENT_ID = HAS_LOCALHOST_REDIRECT @@ -133,7 +134,6 @@ const OAUTH_CLIENT_ID = HAS_LOCALHOST_REDIRECT const OAUTH_REDIRECT_URI = HAS_LOCALHOST_REDIRECT ? DEV_REDIRECT_URI : (ENV_OAUTH_REDIRECT_URI ?? DEV_REDIRECT_URI); -const OAUTH_SCOPE = (import.meta.env.VITE_OAUTH_SCOPE as string | undefined) ?? oauthMetadata.scope; const configure = () => { if (!browser || configured) return; diff --git a/web/src/lib/oauth-client-metadata.ts b/web/src/lib/oauth-client-metadata.ts index badc3a034..c1ea37290 100644 --- a/web/src/lib/oauth-client-metadata.ts +++ b/web/src/lib/oauth-client-metadata.ts @@ -104,3 +104,22 @@ export default { dpop_bound_access_tokens: true, scope: scopes.join(" ") }; + +const rpcAnyAudience = /^rpc:(.+)\?aud=\*$/; + +export const loopbackScope = (() => { + const lxm: string[] = []; + const rest: string[] = []; + for (const scope of scopes) { + const match = rpcAnyAudience.exec(scope); + if (match) lxm.push(match[1]); + else rest.push(scope); + } + return [...rest, `rpc?${lxm.map((nsid) => `lxm=${nsid}`).join("&")}&aud=*`].join(" "); +})(); + +const queryValue = (value: string) => + value.replace(/[%&=+# ]/g, (char) => (char === " " ? "+" : encodeURIComponent(char))); + +export const loopbackClientId = (redirectUri: string, scope: string) => + `http://localhost?redirect_uri=${queryValue(redirectUri)}&scope=${queryValue(scope)}`; diff --git a/web/vite.config.ts b/web/vite.config.ts index cf23bca7e..bd48a5c96 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -2,7 +2,7 @@ import tailwindcss from "@tailwindcss/vite"; import { sveltekit } from "@sveltejs/kit/vite"; import Icons from "unplugin-icons/vite"; import { defineConfig } from "vitest/config"; -import oauthMetadata from "./src/lib/oauth-client-metadata"; +import oauthMetadata, { loopbackClientId, loopbackScope } from "./src/lib/oauth-client-metadata"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; @@ -26,7 +26,10 @@ const allowedHosts = [ .filter(Boolean) ?? []) ]; const devRedirectUri = `http://127.0.0.1:${devPort}/oauth/callback`; -const devClientId = `http://localhost?redirect_uri=${encodeURIComponent(devRedirectUri)}&scope=${encodeURIComponent(process.env.VITE_OAUTH_SCOPE ?? oauthMetadata.scope)}`; +const devScope = + process.env.VITE_OAUTH_SCOPE ?? + (process.env.VITE_OAUTH_CLIENT_ID ? oauthMetadata.scope : loopbackScope); +const devClientId = loopbackClientId(devRedirectUri, devScope); export default defineConfig({ plugins: [ { @@ -44,7 +47,7 @@ export default defineConfig({ (serve ? devRedirectUri : oauthMetadata.redirect_uris[0]) ), "import.meta.env.VITE_OAUTH_SCOPE": JSON.stringify( - process.env.VITE_OAUTH_SCOPE ?? oauthMetadata.scope + serve ? devScope : (process.env.VITE_OAUTH_SCOPE ?? oauthMetadata.scope) ) } };