(client: Client, getOrganizationId: () => string, gate?: SignupGate)
| 64 | // --------------------------------------------------------------------------- |
| 65 | |
| 66 | const makeAuthOptions = (client: Client, getOrganizationId: () => string, gate?: SignupGate) => { |
| 67 | const config = loadConfig(); |
| 68 | // A `Secure` session cookie is never sent back over plain HTTP, so an HTTP |
| 69 | // alias can sign in and then look signed out on every later request. Drop the |
| 70 | // attribute when ANY trusted origin is HTTP. This is not a new relaxation for |
| 71 | // the common cases: Better Auth already infers `useSecureCookies` from the |
| 72 | // baseURL scheme, so an all-HTTPS instance still gets `true` and the plain |
| 73 | // `http://localhost` default still gets `false`. It only changes the mixed |
| 74 | // case an operator opts into with EXECUTOR_TRUSTED_ORIGINS. |
| 75 | const hasInsecureTrustedOrigin = config.trustedOrigins.some((origin) => |
| 76 | origin.startsWith("http://"), |
| 77 | ); |
| 78 | // Warn only for that mixed case. An HTTP-only instance (local dev, a LAN |
| 79 | // deploy) never had Secure cookies to lose, and warning there would fire on |
| 80 | // every default boot. |
| 81 | const downgradesCanonicalCookies = |
| 82 | hasInsecureTrustedOrigin && config.webBaseUrl.startsWith("https://"); |
| 83 | if (downgradesCanonicalCookies && !warnedInsecureTrustedOrigin) { |
| 84 | warnedInsecureTrustedOrigin = true; |
| 85 | console.warn( |
| 86 | "[executor] EXECUTOR_TRUSTED_ORIGINS contains an http:// origin, so session cookies drop the Secure attribute for every origin — including the https:// canonical URL. Use https:// aliases to keep session cookies transport-secure.", |
| 87 | ); |
| 88 | } |
| 89 | // Always resolved (generated + persisted when no env is set); this guards only |
| 90 | // an explicitly-set env secret that is too weak. |
| 91 | const secret = config.authSecret; |
| 92 | if (secret.length < 32) { |
| 93 | // 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 |
| 94 | throw new Error("BETTER_AUTH_SECRET (or AUTH_SECRET), if set, must be at least 32 characters"); |
| 95 | } |
| 96 | return { |
| 97 | // Hand Better Auth the SAME libSQL client SelfHostDb already opened — NOT a |
| 98 | // fresh `{ url }` connection. `{ client }` makes LibsqlDialect adopt the |
| 99 | // existing handle (closeClient=false, so SelfHostDb keeps ownership). One |
| 100 | // connection means one WAL: see the header comment for why a second |
| 101 | // connection is the self-host data-loss bug. |
| 102 | // |
| 103 | // The cast bridges a dependency skew: @libsql/kysely-libsql pins an older |
| 104 | // @libsql/core (0.8) than @libsql/client (0.17), so the two `Client` types |
| 105 | // differ — only in `.sync()` (embedded-replica replication, unused here). |
| 106 | // The dialect calls execute/batch/transaction/close, which are identical |
| 107 | // across both versions, so sharing the 0.17 client is sound at runtime. |
| 108 | database: { |
| 109 | // 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. |
| 110 | dialect: new LibsqlDialect({ client } as unknown as LibsqlDialectConfig), |
| 111 | type: "sqlite" as const, |
| 112 | }, |
| 113 | secret, |
| 114 | // The canonical browser Origin is config.webBaseUrl; explicitly configured |
| 115 | // aliases may also send cookie-authenticated requests. CLI/MCP bearer |
| 116 | // requests carry no Origin and are unaffected. We deliberately do NOT derive |
| 117 | // either value from the request `Host`: matching the ecosystem (Windmill |
| 118 | // `BASE_URL`, n8n `WEBHOOK_URL`), a pinned origin keeps host-header injection |
| 119 | // out of OAuth redirects and links. Additional trusted origins affect only |
| 120 | // Better Auth's request validation; generated links and OAuth callbacks stay |
| 121 | // pinned to config.webBaseUrl. |
| 122 | baseURL: config.webBaseUrl, |
| 123 | trustedOrigins: [...config.trustedOrigins], |
no test coverage detected