the statusphere demo reworked into a vite/react app in a monorepo

setup to work as either loopback or internet-exposed client

+14 -38
+3 -5
.env.template
··· 2 NODE_ENV="development" # Options: 'development', 'production' 3 PORT="8080" # The port your server will listen on 4 HOST="localhost" # Hostname for the server 5 - PUBLIC_URL="https://localhost:8080" 6 7 # CORS Settings 8 CORS_ORIGIN="http://localhost:*" # Allowed CORS origin, adjust as necessary ··· 12 COMMON_RATE_LIMIT_MAX_REQUESTS="20" # Max number of requests per window per IP 13 14 # Secrets 15 - # openssl rand -base64 33 16 - COOKIE_SECRET="" 17 - # openssl ecparam -name prime256v1 -genkey | openssl pkcs8 -topk8 -nocrypt | openssl base64 -A 18 - PRIVATE_KEY_ES256_B64=""
··· 2 NODE_ENV="development" # Options: 'development', 'production' 3 PORT="8080" # The port your server will listen on 4 HOST="localhost" # Hostname for the server 5 + PUBLIC_URL="" 6 7 # CORS Settings 8 CORS_ORIGIN="http://localhost:*" # Allowed CORS origin, adjust as necessary ··· 12 COMMON_RATE_LIMIT_MAX_REQUESTS="20" # Max number of requests per window per IP 13 14 # Secrets 15 + # Must this in production. May be generated with `openssl rand -base64 33` 16 + # COOKIE_SECRET=""
+8 -20
src/auth/client.ts
··· 5 import { SessionStore, StateStore } from './storage' 6 7 export const createClient = async (db: Database) => { 8 - const url = env.PUBLIC_URL 9 - const privateKeyPKCS8 = Buffer.from(env.PRIVATE_KEY_ES256_B64, 'base64').toString() 10 - const privateKey = await JoseKey.fromImportable(privateKeyPKCS8, 'key1') 11 return new NodeOAuthClient({ 12 - // This object will be used to build the payload of the /client-metadata.json 13 - // endpoint metadata, exposing the client metadata to the OAuth server. 14 clientMetadata: { 15 - // Must be a URL that will be exposing this metadata 16 - client_id: `${url}/client-metadata.json`, 17 client_uri: url, 18 - client_name: 'ATProto Express App', 19 - jwks_uri: `${url}/jwks.json`, 20 logo_uri: `${url}/logo.png`, 21 tos_uri: `${url}/tos`, 22 policy_uri: `${url}/policy`, 23 redirect_uris: [`${url}/oauth/callback`], 24 - token_endpoint_auth_signing_alg: 'ES256', 25 - scope: 'profile email offline_access', 26 grant_types: ['authorization_code', 'refresh_token'], 27 response_types: ['code'], 28 application_type: 'web', 29 - token_endpoint_auth_method: 'private_key_jwt', 30 dpop_bound_access_tokens: true, 31 }, 32 - 33 - // Used to authenticate the client to the token endpoint. Will be used to 34 - // build the jwks object to be exposed on the "jwks_uri" endpoint. 35 - keyset: [privateKey], 36 - 37 - // Interface to store authorization state data (during authorization flows) 38 stateStore: new StateStore(db), 39 - 40 - // Interface to store authenticated session data 41 sessionStore: new SessionStore(db), 42 }) 43 }
··· 5 import { SessionStore, StateStore } from './storage' 6 7 export const createClient = async (db: Database) => { 8 + const publicUrl = env.PUBLIC_URL 9 + const url = publicUrl || `http://127.0.0.1:${env.PORT}` 10 return new NodeOAuthClient({ 11 clientMetadata: { 12 + client_name: 'AT Protocol Express App', 13 + client_id: publicUrl 14 + ? `${url}/client-metadata.json` 15 + : `http://localhost?redirect_uri=${encodeURIComponent(`${url}/oauth/callback`)}`, 16 client_uri: url, 17 logo_uri: `${url}/logo.png`, 18 tos_uri: `${url}/tos`, 19 policy_uri: `${url}/policy`, 20 redirect_uris: [`${url}/oauth/callback`], 21 + scope: 'profile offline_access', 22 grant_types: ['authorization_code', 'refresh_token'], 23 response_types: ['code'], 24 application_type: 'web', 25 + token_endpoint_auth_method: 'none', 26 dpop_bound_access_tokens: true, 27 }, 28 stateStore: new StateStore(db), 29 sessionStore: new SessionStore(db), 30 }) 31 }
+2 -3
src/env.ts
··· 7 NODE_ENV: str({ devDefault: testOnly('test'), choices: ['development', 'production', 'test'] }), 8 HOST: host({ devDefault: testOnly('localhost') }), 9 PORT: port({ devDefault: testOnly(3000) }), 10 - PUBLIC_URL: str({ devDefault: testOnly('http://localhost:3000') }), 11 - COOKIE_SECRET: str(), 12 - PRIVATE_KEY_ES256_B64: str(), 13 CORS_ORIGIN: str({ devDefault: testOnly('http://localhost:3000') }), 14 COMMON_RATE_LIMIT_MAX_REQUESTS: num({ devDefault: testOnly(1000) }), 15 COMMON_RATE_LIMIT_WINDOW_MS: num({ devDefault: testOnly(1000) }),
··· 7 NODE_ENV: str({ devDefault: testOnly('test'), choices: ['development', 'production', 'test'] }), 8 HOST: host({ devDefault: testOnly('localhost') }), 9 PORT: port({ devDefault: testOnly(3000) }), 10 + PUBLIC_URL: str({}), 11 + COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }), 12 CORS_ORIGIN: str({ devDefault: testOnly('http://localhost:3000') }), 13 COMMON_RATE_LIMIT_MAX_REQUESTS: num({ devDefault: testOnly(1000) }), 14 COMMON_RATE_LIMIT_WINDOW_MS: num({ devDefault: testOnly(1000) }),
+1 -1
src/firehose/ingester.ts
··· 1 - import type { Database } from '#/db/index' 2 import { Firehose } from '#/firehose/firehose' 3 4 export class Ingester {
··· 1 + import type { Database } from '#/db' 2 import { Firehose } from '#/firehose/firehose' 3 4 export class Ingester {
-2
src/pages/login.ts
··· 1 - import { AtUri } from '@atproto/syntax' 2 - import type { Post } from '#/db/schema' 3 import { html } from '../view' 4 import { shell } from './shell' 5
··· 1 import { html } from '../view' 2 import { shell } from './shell' 3
-7
src/routes/index.ts
··· 12 const router = express.Router() 13 14 router.get( 15 - '/jwks.json', 16 - handler((_req, res) => { 17 - return res.json(ctx.oauthClient.jwks) 18 - }), 19 - ) 20 - 21 - router.get( 22 '/client-metadata.json', 23 handler((_req, res) => { 24 return res.json(ctx.oauthClient.clientMetadata)
··· 12 const router = express.Router() 13 14 router.get( 15 '/client-metadata.json', 16 handler((_req, res) => { 17 return res.json(ctx.oauthClient.clientMetadata)