Create your Link in Bio for Bluesky

top-level awaitでOAuthクライアントを初期化 (#98)

authored by mkizka.dev and committed by GitHub fb9fbcf3 50870935

+2 -3
app/routes/client-metadata[.json].tsx
··· 1 - import { createOAuthClient } from "~/server/oauth/client"; 1 + import { oauthClient } from "~/server/oauth/client"; 2 2 3 - export async function loader() { 4 - const oauthClient = await createOAuthClient(); 3 + export function loader() { 5 4 return Response.json(oauthClient.clientMetadata); 6 5 }
+2 -3
app/routes/jwks[.json].tsx
··· 1 - import { createOAuthClient } from "~/server/oauth/client"; 1 + import { oauthClient } from "~/server/oauth/client"; 2 2 3 - export async function loader() { 4 - const oauthClient = await createOAuthClient(); 3 + export function loader() { 5 4 return Response.json(oauthClient.jwks); 6 5 }
+1 -2
app/routes/login.tsx
··· 5 5 import { LoginForm } from "~/features/login/login-form"; 6 6 import { RouteToaster } from "~/features/toast/route"; 7 7 import { i18nServer } from "~/i18n/i18n"; 8 - import { createOAuthClient } from "~/server/oauth/client"; 8 + import { oauthClient } from "~/server/oauth/client"; 9 9 import { getSessionUserDid } from "~/server/oauth/session"; 10 10 import { createLogger } from "~/utils/logger"; 11 11 ··· 21 21 return { error: t("login.unknown-error") }; 22 22 } 23 23 try { 24 - const oauthClient = await createOAuthClient(); 25 24 const url = await oauthClient.authorize(handle, { 26 25 scope: "atproto transition:generic", 27 26 });
+1 -2
app/routes/oauth.callback.tsx
··· 1 1 import { redirect } from "react-router"; 2 2 3 - import { createOAuthClient } from "~/server/oauth/client"; 3 + import { oauthClient } from "~/server/oauth/client"; 4 4 import { commitSession, getSession } from "~/server/oauth/session"; 5 5 import { createLogger } from "~/utils/logger"; 6 6 ··· 11 11 export async function loader({ request }: Route.LoaderArgs) { 12 12 const remixSession = await getSession(request); 13 13 try { 14 - const oauthClient = await createOAuthClient(); 15 14 const { session: oauthSession } = await oauthClient.callback( 16 15 new URL(request.url).searchParams, 17 16 );
+31 -39
app/server/oauth/client.ts
··· 6 6 7 7 import { SessionStore, StateStore } from "./storage"; 8 8 9 - let oauthClient: NodeOAuthClient | null = null; 9 + const baseUrl = isProduction ? env.PUBLIC_URL : "http://127.0.0.1:3000"; 10 10 11 - export const createOAuthClient = async () => { 12 - if (oauthClient) { 13 - return oauthClient; 14 - } 15 - const baseUrl = isProduction ? env.PUBLIC_URL : "http://127.0.0.1:3000"; 16 - const privateKeyPKCS8 = Buffer.from( 17 - env.PRIVATE_KEY_ES256_B64, 18 - "base64", 19 - ).toString(); 20 - const privateKey = await JoseKey.fromImportable(privateKeyPKCS8, "key1"); 21 - const oauthClientOptions: NodeOAuthClientOptions = { 22 - clientMetadata: { 23 - client_name: "Linkat", 24 - client_id: isProduction 25 - ? `${env.PUBLIC_URL}/client-metadata.json` 26 - : `http://localhost?redirect_uri=${encodeURIComponent(`${baseUrl}/oauth/callback`)}`, 27 - client_uri: baseUrl, 28 - jwks_uri: `${baseUrl}/jwks.json`, 29 - redirect_uris: [`${baseUrl}/oauth/callback`], 30 - scope: "atproto transition:generic", 31 - grant_types: ["authorization_code", "refresh_token"], 32 - response_types: ["code"], 33 - application_type: "web", 34 - token_endpoint_auth_method: "private_key_jwt", 35 - token_endpoint_auth_signing_alg: "ES256", 36 - dpop_bound_access_tokens: true, 37 - }, 38 - keyset: [privateKey], 39 - plcDirectoryUrl: env.ATPROTO_PLC_URL, 40 - stateStore: new StateStore(), 41 - sessionStore: new SessionStore(), 42 - }; 43 - if (!isProduction) { 44 - oauthClientOptions.handleResolver = env.BSKY_PUBLIC_API_URL; 45 - oauthClientOptions.allowHttp = true; // httpを許可しないとOAuthProtectedResourceMetadataResolverがエラーを投げる 46 - } 47 - oauthClient = new NodeOAuthClient(oauthClientOptions); 48 - return oauthClient; 11 + const privateKey = Buffer.from(env.PRIVATE_KEY_ES256_B64, "base64").toString(); 12 + 13 + const oauthClientOptions: NodeOAuthClientOptions = { 14 + clientMetadata: { 15 + client_name: "Linkat", 16 + client_id: isProduction 17 + ? `${env.PUBLIC_URL}/client-metadata.json` 18 + : `http://localhost?redirect_uri=${encodeURIComponent(`${baseUrl}/oauth/callback`)}`, 19 + client_uri: baseUrl, 20 + jwks_uri: `${baseUrl}/jwks.json`, 21 + redirect_uris: [`${baseUrl}/oauth/callback`], 22 + scope: "atproto transition:generic", 23 + grant_types: ["authorization_code", "refresh_token"], 24 + response_types: ["code"], 25 + application_type: "web", 26 + token_endpoint_auth_method: "private_key_jwt", 27 + token_endpoint_auth_signing_alg: "ES256", 28 + dpop_bound_access_tokens: true, 29 + }, 30 + keyset: [await JoseKey.fromImportable(privateKey, "key1")], 31 + plcDirectoryUrl: env.ATPROTO_PLC_URL, 32 + stateStore: new StateStore(), 33 + sessionStore: new SessionStore(), 49 34 }; 35 + 36 + if (!isProduction) { 37 + oauthClientOptions.handleResolver = env.BSKY_PUBLIC_API_URL; 38 + oauthClientOptions.allowHttp = true; // httpを許可しないとOAuthProtectedResourceMetadataResolverがエラーを投げる 39 + } 40 + 41 + export const oauthClient = new NodeOAuthClient(oauthClientOptions);
+1 -2
app/server/oauth/session.ts
··· 4 4 import { userService } from "~/server/service/userService"; 5 5 import { env } from "~/utils/env"; 6 6 7 - import { createOAuthClient } from "./client"; 7 + import { oauthClient } from "./client"; 8 8 9 9 type SessionData = { 10 10 did: string; ··· 56 56 if (!userDid) { 57 57 return null; 58 58 } 59 - const oauthClient = await createOAuthClient(); 60 59 const oauthSession = await oauthClient.restore(userDid); 61 60 return new LinkatAgent(oauthSession); 62 61 };
+3
vite.config.ts
··· 5 5 export default defineConfig({ 6 6 base: process.env.VITE_CONFIG_BASE ?? "/", 7 7 plugins: [reactRouter(), tsconfigPaths()], 8 + build: { 9 + target: "es2022", 10 + }, 8 11 test: { 9 12 include: ["app/**/*.spec.ts"], 10 13 coverage: {