Scratch space for learning atproto app development

setup to work as either loopback or internet-exposed client

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