server/worker.ts
Rama main · réplica de lectura del repositorio original.
import { tracing } from 'cloudflare:workers'
import { createConfiguredEmailSender, isEmailDeliveryReady } from '@server/adapters/gateways/email/sender'
import { createSecretCipher } from '@server/adapters/gateways/secrets'
import { createDrizzleConfigzRepository, readBuiltInProviderSettings } from '@server/adapters/repos/configz'
import { createConnectorRepository } from '@server/adapters/repos/connectors'
import { type Auth, createAuth } from '@server/auth'
import { createDeps } from '@server/composition'
import { createDb } from '@server/db/client'
import { type Env, type RuntimeConfig, validateEnv } from '@server/env'
import { createApp, healthStatus } from '@server/http/app'
import { readCorrelationId } from '@server/http/correlation'
import { withRequestErrorBoundary } from '@server/http/request-error-boundary'
import { withWorkerRequestMetrics } from '@server/http/worker-request-metrics'
import { withinInitializationDeadline } from '@server/initialization-deadline'
import { processAccountDeletionCleanup } from '@server/usecases/account-deletion'
import {
reconcileRealmrootResourceServer,
synchronizeEnabledResourceScopeRegistries,
} from '@server/usecases/authorization'
import { defaultBuiltInProviders } from '@server/usecases/configz'
import { loadAuthConnectorConfig } from '@server/usecases/connectors'
import { publishWebhookEvent } from '@server/usecases/webhooks'
import { managementBuiltInProviderSettingsSchema } from '@shared/api/management'
const routers = new WeakMap<Auth, ReturnType<typeof createApp>>()
let cachedAuth: Auth | null = null
let cachedKey: string | null = null
let cachedStaticKey: string | null = null
let cachedValidatedAt = 0
let cachedDb: D1Database | null = null
let cachedEmail: Env['EMAIL'] | null = null
let cachedSecurityPolicy: RuntimeConfig['securityPolicy'] | null = null
let cachedSecurityPolicyDb: D1Database | null = null
let cachedSecurityPolicyAt = 0
let reconciledBaseURL: string | null = null
const dynamicConfigRefreshIntervalMs = 5_000
const publicMetadataCacheControl = 'public, max-age=15, stale-while-revalidate=15, stale-if-error=86400'
const cachedPublicMetadataPaths = new Set(['/.well-known/agent-configuration'])
const emailVerificationPolicyPaths = new Set([
'/api/auth/sign-up/email',
'/api/auth/sign-in/email',
'/api/auth/sign-in/username',
])
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
return withWorkerRequestMetrics(request, () =>
withRequestErrorBoundary(request, async () => {
// Liveness probe answers from the process alone — before any D1 read — so it
// reports the worker is up even when the database is unmigrated or down.
const path = new URL(request.url).pathname
if (path === '/api/health') return Response.json(healthStatus)
const publicMetadataCache =
request.method === 'GET' && cachedPublicMetadataPaths.has(path)
? await caches.open('realmroot-public-metadata')
: null
const cached = await publicMetadataCache?.match(request)
if (cached) return cached
return tracing.enterSpan('realmroot.request.prepare', async (span) => {
span.setAttribute('url.path', path)
const config = validateEnv(env, request.url)
const correlationId = readCorrelationId(request.headers.get('x-correlation-id')) ?? undefined
if (correlationId) span.setAttribute('realmroot.correlation_id', correlationId)
const deps = createDeps(env, config, correlationId)
const resourceTokenRequest = request.headers.get('authorization')?.startsWith('DPoP ') ?? false
const [, securityPolicy] = await Promise.all([
reconcileResourceOnce(deps, config.baseURL),
tracing.enterSpan('realmroot.security-policy.load', () =>
resourceTokenRequest
? Promise.resolve(cachedSecurityPolicy ?? config.securityPolicy)
: loadSecurityPolicy(env, deps),
),
])
const auth = await tracing.enterSpan('realmroot.auth.prepare', () =>
getAuth(
env,
{ ...config, securityPolicy },
deps,
resourceTokenRequest,
emailVerificationPolicyPaths.has(path),
),
)
let app = routers.get(auth)
if (!app) {
app = createApp(auth, (c) => c.env.realmrootRequestDeps, {
baseURL: config.baseURL,
trustedOrigins: config.trustedOrigins,
securityPolicy,
realmrootResourceReconciled: true,
})
routers.set(auth, app)
}
const response = await tracing.enterSpan('realmroot.router.dispatch', () =>
app.fetch(request, { ...env, realmrootRequestDeps: deps }, ctx),
)
if (!publicMetadataCache || !response.ok) return response
const headers = new Headers(response.headers)
headers.set('Cache-Control', publicMetadataCacheControl)
const cacheable = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
})
ctx.waitUntil(publicMetadataCache.put(request, cacheable.clone()))
return cacheable
})
}),
)
},
async scheduled(_controller: ScheduledController, env: Env, ctx: ExecutionContext) {
const config = validateEnv(env, env.BETTER_AUTH_URL ?? 'https://scheduled.realmroot.invalid')
const deps = createDeps(env, config)
ctx.waitUntil(processAccountDeletionCleanup(deps))
if (_controller.cron === '0 3 * * *') ctx.waitUntil(synchronizeEnabledResourceScopeRegistries(deps))
},
}
async function getAuth(
env: Env,
config: RuntimeConfig,
deps: ReturnType<typeof createDeps>,
resourceTokenRequest = false,
forceDynamicRefresh = false,
): Promise<Auth> {
const staticKey = [
config.authSecret,
config.baseURL,
config.emailFrom ?? '',
config.emailFromName ?? '',
config.trustedOrigins.join(','),
JSON.stringify(config.securityPolicy),
].join('\n')
if (
cachedAuth &&
cachedStaticKey === staticKey &&
cachedDb === env.DB &&
cachedEmail === env.EMAIL &&
!forceDynamicRefresh &&
(resourceTokenRequest || Date.now() - cachedValidatedAt < dynamicConfigRefreshIntervalMs)
) {
return cachedAuth
}
const db = createDb(env.DB)
const configz = createDrizzleConfigzRepository(db)
const [connectors, validAudiences, storedBuiltInProviders, emailSettings] = await withinInitializationDeadline(
Promise.all([
loadAuthConnectorConfig(createConnectorRepository(db, createSecretCipher(config.credentialEncryptionKey))),
loadValidAudiences(env.DB, config.baseURL),
readBuiltInProviderSettings(db),
configz.getEmailSettings(),
]),
'configuration',
)
const builtInProviders = managementBuiltInProviderSettingsSchema.parse(
mergeBuiltInProviders(defaultBuiltInProviders, storedBuiltInProviders),
)
const cacheKey = [
config.authSecret,
config.baseURL,
config.emailFrom ?? '',
config.emailFromName ?? '',
config.trustedOrigins.join(','),
JSON.stringify(config.securityPolicy),
JSON.stringify(builtInProviders ?? {}),
JSON.stringify(emailSettings),
connectors.cacheKey,
validAudiences.join(','),
].join('\n')
if (!cachedAuth || cachedKey !== cacheKey || cachedDb !== env.DB || cachedEmail !== env.EMAIL) {
const fallbackEmailSender = config.emailFrom
? { from: config.emailFrom, ...(config.emailFromName ? { fromName: config.emailFromName } : {}) }
: undefined
const emailSender = createConfiguredEmailSender(env.EMAIL, () => configz.getEmailSettings(), fallbackEmailSender)
const auth = createAuth(
db,
deps.ids,
config.authSecret,
config.baseURL,
config.trustedOrigins,
emailSender,
config.securityPolicy,
connectors,
{
builtInProviders,
emailDeliveryReady: isEmailDeliveryReady(env.EMAIL, emailSettings, fallbackEmailSender),
twoFactorEmailOtpEnabled: config.securityPolicy.mfa.emailOtpEnabled,
validAudiences,
externalHttp: deps.externalHttp,
publishWebhookEvent: async (event, data) => {
await publishWebhookEvent(deps, event, data)
},
},
)
// Better Auth now performs database I/O during initialization. Share only a
// ready instance: a failed/cancelled request must not own later requests' I/O.
await withinInitializationDeadline(auth.$context, 'provider')
cachedAuth = auth
cachedKey = cacheKey
cachedStaticKey = staticKey
cachedDb = env.DB
cachedEmail = env.EMAIL
}
cachedValidatedAt = Date.now()
return cachedAuth
}
async function loadSecurityPolicy(env: Env, deps: ReturnType<typeof createDeps>) {
if (
cachedSecurityPolicy &&
cachedSecurityPolicyDb === env.DB &&
Date.now() - cachedSecurityPolicyAt < dynamicConfigRefreshIntervalMs
) {
return cachedSecurityPolicy
}
cachedSecurityPolicy = await withinInitializationDeadline(deps.security.getPolicy(), 'security policy')
cachedSecurityPolicyDb = env.DB
cachedSecurityPolicyAt = Date.now()
return cachedSecurityPolicy
}
async function reconcileResourceOnce(deps: ReturnType<typeof createDeps>, baseURL: string) {
if (reconciledBaseURL === baseURL) return
await withinInitializationDeadline(
tracing.enterSpan('realmroot.resource.reconcile', () => reconcileRealmrootResourceServer(deps, baseURL)),
'resource reconciliation',
)
reconciledBaseURL = baseURL
}
async function loadValidAudiences(db: D1Database, baseURL: string) {
const result = await db
.prepare('SELECT resource_url FROM api_resource WHERE enabled = 1 ORDER BY resource_url')
.all<{ resource_url: string }>()
return [`${baseURL}/api/auth/oauth2/userinfo`, baseURL, ...result.results.map((row) => row.resource_url)]
}
function mergeBuiltInProviders(
defaults: typeof defaultBuiltInProviders,
stored: unknown,
): typeof defaultBuiltInProviders {
if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return defaults
const input = stored as Partial<Record<keyof typeof defaultBuiltInProviders, unknown>>
return {
email: mergeProvider(defaults.email, input.email),
phone: mergeProvider(defaults.phone, input.phone),
web3Wallet: mergeProvider(defaults.web3Wallet, input.web3Wallet),
passkey: mergeProvider(defaults.passkey, input.passkey),
oneTap: mergeProvider(defaults.oneTap, input.oneTap),
}
}
function mergeProvider<T extends Record<string, unknown>>(defaults: T, stored: unknown): T {
if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return defaults
return { ...defaults, ...stored }
}