personal web client for Bluesky
typescript solidjs bluesky atcute

refactor: public client on dev

mary.my.id ff3b8448 f5d87790

verified
Changed files
+68 -23
src
+33 -22
src/main.tsx
··· 4 4 5 5 import { Client, ok, simpleFetchHandler } from '@atcute/client'; 6 6 import type { Did } from '@atcute/lexicons'; 7 - import { configureOAuth } from '@atcute/oauth-browser-client'; 7 + import { type ClientAssertionFetcher, configureOAuth } from '@atcute/oauth-browser-client'; 8 8 9 9 import * as navigation from '~/globals/navigation'; 10 10 import * as preferences from '~/globals/preferences'; ··· 36 36 37 37 // Configure OAuth 38 38 { 39 + // Development mode uses public client with http://localhost client ID 40 + // Production mode uses confidential client with server-side JWT assertions 41 + const isPublicClient = !!import.meta.env.VITE_OAUTH_CLIENT_ID; 42 + 39 43 const host = new Client({ 40 44 handler: simpleFetchHandler({ service: location.origin }), 41 45 }); 42 46 47 + const fetchClientAssertion: ClientAssertionFetcher = async ({ aud, jkt, createDpopProof }) => { 48 + const dpop = await createDpopProof(`${location.origin}/xrpc/x.aglais.requestAssertion`); 49 + 50 + const data = await ok( 51 + host.post('x.aglais.requestAssertion', { 52 + input: { 53 + aud: aud, 54 + jkt: jkt, 55 + }, 56 + headers: { 57 + dpop: dpop, 58 + }, 59 + }), 60 + ); 61 + 62 + return { 63 + client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 64 + client_assertion: data.assertion, 65 + }; 66 + }; 67 + 43 68 configureOAuth({ 44 69 metadata: { 45 - client_id: `${location.origin}/oauth-client-metadata.json`, 46 - redirect_uri: `${location.origin}/oauth/callback`, 70 + client_id: isPublicClient 71 + ? import.meta.env.VITE_OAUTH_CLIENT_ID 72 + : `${location.origin}/oauth-client-metadata.json`, 73 + redirect_uri: isPublicClient 74 + ? import.meta.env.VITE_OAUTH_REDIRECT_URL 75 + : `${location.origin}/oauth/callback`, 47 76 }, 48 77 49 78 identityResolver: { ··· 59 88 return data; 60 89 }, 61 90 }, 62 - async fetchClientAssertion({ aud, jkt, createDpopProof }) { 63 - const dpop = await createDpopProof(`${location.origin}/xrpc/x.aglais.requestAssertion`); 64 91 65 - const data = await ok( 66 - host.post('x.aglais.requestAssertion', { 67 - input: { 68 - aud: aud, 69 - jkt: jkt, 70 - }, 71 - headers: { 72 - dpop: dpop, 73 - }, 74 - }), 75 - ); 76 - 77 - return { 78 - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 79 - client_assertion: data.assertion, 80 - }; 81 - }, 92 + fetchClientAssertion: isPublicClient ? undefined : fetchClientAssertion, 82 93 }); 83 94 } 84 95
+3
src/vite-env.d.ts
··· 6 6 7 7 interface ImportMetaEnv { 8 8 readonly VITE_APP_NAME: string; 9 + readonly VITE_OAUTH_CLIENT_ID: string; 10 + readonly VITE_OAUTH_REDIRECT_URL: string; 11 + readonly VITE_OAUTH_SCOPE: string; 9 12 } 10 13 11 14 interface ImportMeta {
+32 -1
vite.config.ts
··· 5 5 import { VitePWA } from 'vite-plugin-pwa'; 6 6 import solid from 'vite-plugin-solid'; 7 7 8 + const SERVER_HOST = '127.0.0.1'; 9 + const SERVER_PORT = 52222; 10 + 11 + const OAUTH_SCOPE = 'atproto transition:generic transition:chat.bsky'; 12 + 8 13 export default defineConfig({ 9 14 build: { 10 15 target: 'esnext', ··· 54 59 }, 55 60 }, 56 61 server: { 57 - allowedHosts: ['.trycloudflare.com'], 62 + host: SERVER_HOST, 63 + port: SERVER_PORT, 58 64 }, 59 65 optimizeDeps: { 60 66 esbuildOptions: { ··· 110 116 ); 111 117 112 118 return { code: transformed, map: null }; 119 + }, 120 + }, 121 + 122 + // Injects OAuth-related variables for development mode 123 + { 124 + name: 'aglais-oauth-inject', 125 + config(_conf, { command }) { 126 + if (command === 'build') { 127 + // Production uses confidential client 128 + process.env.VITE_OAUTH_CLIENT_ID = ''; 129 + process.env.VITE_OAUTH_REDIRECT_URL = ''; 130 + } else { 131 + // Development uses public client with http://localhost format 132 + const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}/oauth/callback`; 133 + 134 + const clientId = 135 + `http://localhost` + 136 + `?redirect_uri=${encodeURIComponent(redirectUri)}` + 137 + `&scope=${encodeURIComponent(OAUTH_SCOPE)}`; 138 + 139 + process.env.VITE_OAUTH_CLIENT_ID = clientId; 140 + process.env.VITE_OAUTH_REDIRECT_URL = redirectUri; 141 + } 142 + 143 + process.env.VITE_OAUTH_SCOPE = OAUTH_SCOPE; 113 144 }, 114 145 }, 115 146 ],