(client: Client, getOrganizationId: () => string, gate?: SignupGate)
| 62 | // --------------------------------------------------------------------------- |
| 63 | |
| 64 | const makeAuthOptions = (client: Client, getOrganizationId: () => string, gate?: SignupGate) => { |
| 65 | const config = loadConfig(); |
| 66 | // Always resolved (generated + persisted when no env is set); this guards only |
| 67 | // an explicitly-set env secret that is too weak. |
| 68 | const secret = config.authSecret; |
| 69 | if (secret.length < 32) { |
| 70 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: a multi-user auth server must not boot with a weak session secret |
| 71 | throw new Error("BETTER_AUTH_SECRET (or AUTH_SECRET), if set, must be at least 32 characters"); |
| 72 | } |
| 73 | return { |
| 74 | // Hand Better Auth the SAME libSQL client SelfHostDb already opened — NOT a |
| 75 | // fresh `{ url }` connection. `{ client }` makes LibsqlDialect adopt the |
| 76 | // existing handle (closeClient=false, so SelfHostDb keeps ownership). One |
| 77 | // connection means one WAL: see the header comment for why a second |
| 78 | // connection is the self-host data-loss bug. |
| 79 | // |
| 80 | // The cast bridges a dependency skew: @libsql/kysely-libsql pins an older |
| 81 | // @libsql/core (0.8) than @libsql/client (0.17), so the two `Client` types |
| 82 | // differ — only in `.sync()` (embedded-replica replication, unused here). |
| 83 | // The dialect calls execute/batch/transaction/close, which are identical |
| 84 | // across both versions, so sharing the 0.17 client is sound at runtime. |
| 85 | database: { |
| 86 | // oxlint-disable-next-line executor/no-double-cast -- boundary: the two @libsql/core versions' Client types are structurally identical for the calls the dialect makes (see above); no schema/decode applies to a native client handle. |
| 87 | dialect: new LibsqlDialect({ client } as unknown as LibsqlDialectConfig), |
| 88 | type: "sqlite" as const, |
| 89 | }, |
| 90 | secret, |
| 91 | // The browser Origin must match this exactly; CLI/MCP bearer requests carry |
| 92 | // no Origin and are unaffected. `config.webBaseUrl` resolves from an explicit |
| 93 | // EXECUTOR_WEB_BASE_URL, else a platform-injected origin (Railway/Render/Fly/ |
| 94 | // …), else localhost — so a PaaS deploy is zero-config and any other host |
| 95 | // sets the one variable (a loud warning fires on the localhost fallback). |
| 96 | // See config.ts. We deliberately do NOT derive this from the request `Host`: |
| 97 | // matching the ecosystem (Windmill `BASE_URL`, n8n `WEBHOOK_URL`), a pinned |
| 98 | // origin keeps host-header injection out of OAuth redirects and links. |
| 99 | baseURL: config.webBaseUrl, |
| 100 | trustedOrigins: [config.webBaseUrl], |
| 101 | emailAndPassword: { enabled: true }, |
| 102 | // `apiKey` issues long-lived personal keys (the API-keys page). With |
| 103 | // `enableSessionForAPIKeys`, presenting a key resolves to its owner's |
| 104 | // session — so a key works as a Bearer token for the API + MCP endpoint. |
| 105 | // |
| 106 | // `mcp()` adds the MCP OAuth Authorization Server: dynamic client |
| 107 | // registration + authorize + token under /api/auth/mcp/*, the discovery |
| 108 | // docs, and `getMcpSession` (opaque-bearer validation). It WRAPS |
| 109 | // oidcProvider — do NOT also add oidcProvider. The two root well-known docs |
| 110 | // are re-emitted by the shared envelope (MCP clients probe the origin root, |
| 111 | // not the /api/auth basePath). |
| 112 | plugins: [ |
| 113 | organization(), |
| 114 | admin(), |
| 115 | apiKey({ enableSessionForAPIKeys: true, rateLimit: { enabled: false } }), |
| 116 | bearer(), |
| 117 | // RFC 8628 device authorization, the CLI `executor login` flow. Registers |
| 118 | // /device/code + /device/token + the approval endpoints; the issued token |
| 119 | // is an opaque session that `bearer()` (above) accepts as `Authorization: |
| 120 | // Bearer` on the /api/* plane. `validateClient` is left unset, so any |
| 121 | // client_id is accepted (the CLI presents "executor-cli"). `verificationUri` |
no test coverage detected