@stwrd-auth/core 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented in this file.
4
+
5
+ The format is based on Keep a Changelog 1.1.0, and this package adheres to Semantic Versioning (SemVer).
6
+
7
+ ## [0.1.0] - Unreleased
8
+
9
+ Initial release.
10
+
11
+ ### Added
12
+
13
+ - Configuration resolution (`resolveConfig`, `configFromEnv`, `stwrdOptionsFromEnv`) with fail-closed validation and `ConfigError`.
14
+ - `StwrdCore`: session resolution with expiry and token renewal, HMAC-signed cookie sealing, CSRF tokens and webhook verification, independent of any web framework or runtime.
15
+ - OpenID Connect relying-party client (`OidcClient`): discovery, JWKS caching, authorization-code flow with PKCE, ID token and logout token validation, refresh-token exchange, userinfo. Redirects are never followed.
16
+ - Distinct errors for a rejection by the identity provider (`OidcError`) and for no answer (`IdpUnavailable`, `RefreshUncertain`).
17
+ - Session store contract (`SessionStore`) with a refresh lease and fencing, so a refresh token is exchanged at most once across processes, plus the in-memory `MemoryStore`.
18
+ - `Keyring` for encrypting stored sessions (JWE `dir` + `A256GCM`) with key rotation.
19
+ - `authHandler`: the `/auth/*` routes (sign-in, callback, sign-out, session, organizations, back-channel logout, webhook) as a `Request` to `Response` function.
20
+ - Access guards on `Request` and `Response` (`checkAccess`, `resolveAccess`, `safeTarget`, `isPublicPath`).
21
+ - Back-channel logout bookkeeping (`BackChannelSink`, `MemoryBackChannelSink`).
22
+ - Standard Webhooks signature verification and signing (`verifyWebhook`, `signWebhook`) with replay window and `SeenEventIds` deduplication.
23
+ - Typed Management API client (`createManagement`) with discovery validation, token caching and cursor iteration.
24
+ - Cookie, bounded request-body and response helpers.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stwrd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,173 @@
1
- # Temporary Holding Version
1
+ # @stwrd-auth/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The runtime-neutral core shared by the stwrd JavaScript SDKs: configuration, an OpenID Connect relying-party client, server-side sessions with a refresh lease, signed cookies, webhook verification and the Management API transport.
4
+
5
+ ## What it is
6
+
7
+ [stwrd](https://stwrd.dev) is a multi-tenant identity provider. This package holds the logic that does not depend on a web framework or a runtime. It has no HTTP server and no framework integration of its own: applications normally install an adapter, and the adapter builds on this package.
8
+
9
+ | Package | Use it for |
10
+ |---|---|
11
+ | [`@stwrd-auth/node`](https://www.npmjs.com/package/@stwrd-auth/node) | Express apps |
12
+ | [`@stwrd-auth/workers`](https://www.npmjs.com/package/@stwrd-auth/workers) | Cloudflare Workers |
13
+ | [`@stwrd-auth/react`](https://www.npmjs.com/package/@stwrd-auth/react) | React front ends that talk to one of the two above |
14
+
15
+ Install this package directly to write an adapter for another runtime, to verify stwrd webhooks on their own, or to use the typed Management client without an adapter.
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ npm install @stwrd-auth/core
21
+ ```
22
+
23
+ Requires Node.js 18 or newer, or any runtime that provides the Fetch API (`Request`, `Response`, `Headers`) and `node:crypto` (Cloudflare Workers with the `nodejs_compat` compatibility flag). The only runtime dependency is [`jose`](https://www.npmjs.com/package/jose) (`^6`). The package is ESM only and ships its own TypeScript declarations.
24
+
25
+ Each module is a subpath export; there is no root entry point (`import ... from "@stwrd-auth/core/client"`). The adapters pin this package to an exact version, so install both from the same release.
26
+
27
+ ## Minimal example
28
+
29
+ A server for any runtime that speaks `Request` and `Response` (Bun, Deno, Workers). The handler serves the `/auth/*` routes and returns `null` for any other path:
30
+
31
+ ```ts
32
+ import { StwrdCore, stwrdOptionsFromEnv } from "@stwrd-auth/core/client";
33
+ import { authHandler } from "@stwrd-auth/core/web/authHandler";
34
+
35
+ const stwrd = new StwrdCore(stwrdOptionsFromEnv(process.env));
36
+ const handle = authHandler(stwrd);
37
+
38
+ export default {
39
+ async fetch(request: Request): Promise<Response> {
40
+ return (await handle(request)) ?? new Response("Not found", { status: 404 });
41
+ },
42
+ };
43
+ ```
44
+
45
+ Without a `sessions` option the sessions live in process memory (`MemoryStore`), which suits development and a single process. For several processes, pass a shared store that implements `SessionStore`; `@stwrd-auth/node/postgres` and `@stwrd-auth/workers` ship two.
46
+
47
+ Verifying a webhook delivery, with the raw request body and the request headers:
48
+
49
+ ```ts
50
+ import { SeenEventIds, verifyWebhook } from "@stwrd-auth/core/webhooks";
51
+
52
+ const seen = new SeenEventIds();
53
+
54
+ export function receive(rawBody: string | Buffer, headers: Record<string, string>) {
55
+ // Throws InvalidSignatureError or DuplicateEventError.
56
+ return verifyWebhook(rawBody, headers, process.env.STWRD_WEBHOOK_SECRET!, { seen });
57
+ }
58
+ ```
59
+
60
+ ## Configuration
61
+
62
+ `new StwrdCore(options)` and `resolveConfig(options)` validate the options and throw `ConfigError` when one is missing or invalid. `stwrdOptionsFromEnv(env, overrides?)` reads them from an environment object; the core never reads a global environment of its own.
63
+
64
+ | Option | Environment variable | Required | Default | Description |
65
+ |---|---|---|---|---|
66
+ | `issuer` | `STWRD_ISSUER` | yes | | Issuer URL of the tenant. |
67
+ | `clientId` | `STWRD_CLIENT_ID` | yes | | OIDC client ID of the application. |
68
+ | `clientSecret` | `STWRD_CLIENT_SECRET` | yes | | Client secret of the application. |
69
+ | `baseUrl` | `STWRD_BASE_URL` | yes | | Public URL of the application. `{baseUrl}{prefix}/callback` is the redirect URI. |
70
+ | `cookieSecret` | `STWRD_COOKIE_SECRET` | yes | | HMAC key of the session cookie, at least 32 characters. |
71
+ | `scope` | `STWRD_SCOPE` | no | `openid profile email offline_access` | Requested scopes. Add `org` to enable organizations. |
72
+ | `webhookSecret` | `STWRD_WEBHOOK_SECRET` | no | none | Without it `/auth/webhook` answers 503. |
73
+ | `prefix` | `STWRD_PREFIX` | no | `/auth` | Path prefix of the routes. |
74
+ | `sessionTtlS` | `STWRD_SESSION_TTL_S` | no | `28800` | Sliding session lifetime in seconds. |
75
+ | `cookieSecure` | `STWRD_COOKIE_SECURE` | no | `true` | `Secure` attribute of the cookies. |
76
+ | `postLoginRedirect` | `STWRD_POST_LOGIN_REDIRECT` | no | `/` | Destination when a sign-in has no `return_to`. |
77
+ | `postLogoutRedirect` | `STWRD_POST_LOGOUT_REDIRECT` | no | `/` | Destination after sign-out. |
78
+ | `sessionCookie` | | no | `__Host-stwrd_session` | Name of the session cookie. |
79
+ | `txCookie` | | no | `__Host-stwrd_tx` | Name of the sign-in transaction cookie. |
80
+ | `transactionTtlS` | | no | `600` | Lifetime of a pending sign-in, in seconds. |
81
+ | `sessions` | | no | `MemoryStore` | A `SessionStore`. |
82
+ | `fetch` | | no | `globalThis.fetch` | The `fetch` used for every request to the identity provider. |
83
+
84
+ ## API reference
85
+
86
+ | Subpath | Exports |
87
+ |---|---|
88
+ | `@stwrd-auth/core/client` | `StwrdCore` (sessions, cookie sealing, CSRF, webhook verification, organization listing), `stwrdOptionsFromEnv`, `RefreshTuning`, `KeepAlive` |
89
+ | `@stwrd-auth/core/config` | `resolveConfig`, `configFromEnv`, `stwrdConfigFromEnv`, `ConfigError`, defaults and option types |
90
+ | `@stwrd-auth/core/oidc` | `OidcClient`, `OidcError`, `IdpUnavailable`, `RefreshUncertain`, PKCE and `state` helpers |
91
+ | `@stwrd-auth/core/sessions` | `SessionStore`, `MemoryStore`, `StwrdSession`, `StwrdUser`, `StwrdOrganization`, `sessionContext`, `sessionResponse`, `hasRole`, `hasPermission` |
92
+ | `@stwrd-auth/core/keyring` | `Keyring`: named 32-byte keys that encrypt stored sessions (JWE `dir` + `A256GCM`) |
93
+ | `@stwrd-auth/core/backchannel` | `BackChannelSink`, `MemoryBackChannelSink` |
94
+ | `@stwrd-auth/core/webhooks` | `verifyWebhook`, `verifyWebhookSignature`, `signWebhook`, `SeenEventIds`, `InvalidSignatureError`, `DuplicateEventError` |
95
+ | `@stwrd-auth/core/management` | `createManagement`, `ManagementClient`, `ManagementError`: typed Management API client |
96
+ | `@stwrd-auth/core/crypto` | `sign`, `unsign`, `csrfToken` |
97
+ | `@stwrd-auth/core/web/authHandler` | `authHandler(stwrd, options?)`: the `/auth/*` routes as `(Request) => Promise<Response \| null>` |
98
+ | `@stwrd-auth/core/web/access` | `checkAccess`, `resolveAccess`, `safeTarget`, `isPublicPath`: the access guards on `Request` and `Response` |
99
+ | `@stwrd-auth/core/web/cookies`, `body`, `response` | Cookie, bounded body-reading and response helpers |
100
+
101
+ ### Routes served by `authHandler`
102
+
103
+ | Route | Behavior |
104
+ |---|---|
105
+ | `GET {prefix}/sign-in` | Starts the authorization-code flow with PKCE; accepts `return_to`; other query parameters are ignored. |
106
+ | `GET {prefix}/callback` | Completes the flow, creates the session and sets the session cookie. |
107
+ | `POST {prefix}/sign-out` | Ends the session. Requires the CSRF token (`x-csrf-token` header or `csrf_token` form field). |
108
+ | `GET {prefix}/session` | JSON: `authenticated`, `user`, `organization`, `consents`, `csrf_token`, `account_url`. |
109
+ | `GET {prefix}/organizations`, `POST {prefix}/organization` | List the person's organizations and switch the active one (requires the `org` scope). |
110
+ | `POST {prefix}/back-channel` | Receives OIDC back-channel logout notices. |
111
+ | `POST {prefix}/webhook` | Receives and verifies webhook deliveries. |
112
+
113
+ ### Management client
114
+
115
+ ```ts
116
+ import { createManagement } from "@stwrd-auth/core/management";
117
+
118
+ const api = createManagement({
119
+ issuer: process.env.STWRD_ISSUER!,
120
+ clientId: process.env.STWRD_MANAGEMENT_CLIENT_ID!,
121
+ clientSecret: process.env.STWRD_MANAGEMENT_CLIENT_SECRET!,
122
+ scopes: ["mgmt:users:read"],
123
+ });
124
+
125
+ for await (const user of api.users.iterate()) {
126
+ console.log(user.id);
127
+ }
128
+ ```
129
+
130
+ Use it on a server only; never ship Management credentials to a browser. The client validates the discovery document before it sends any credential, refuses redirects, caches access tokens per validated destination and never retries a write on its own. Errors are `ManagementError` instances with `status`, `body` and `requestId`.
131
+
132
+ ## Security
133
+
134
+ - Session cookies are HMAC-signed (tamper-evident, not encrypted) and carry only an opaque session identifier. Access, ID and refresh tokens stay in the server-side store and are never sent to the browser.
135
+ - By default the cookies use the `__Host-` prefix and are `HttpOnly`, `Secure` and `SameSite=Lax`.
136
+ - Authorization-code flows use PKCE (`S256`), `state` and `nonce`. ID tokens are verified for signature (`RS256` or `EdDSA`), issuer, audience, expiry, `nonce` and `at_hash`. The algorithm list is fixed and never read from the token.
137
+ - Requests to the identity provider never follow redirects, so a redirect cannot carry a secret to another host. The redirect URI is built from `baseUrl`, never from the `Host` header.
138
+ - State-changing routes (`sign-out`, organization switch) require a CSRF token derived from the session. Redirect targets taken from requests go through `safeTarget`: only paths of the same application are allowed.
139
+ - Webhook signatures are compared in constant time, with a 300-second replay window. Deduplication by event `id` is process-local; a receiver that needs a hard guarantee must also deduplicate in its own storage.
140
+ - Request bodies are read with a size ceiling measured on the stream (100 KiB for forms and JSON, 5 MiB for webhooks).
141
+ - Across the processes that share a session store, a refresh token is exchanged at most once (a lease with fencing). When a refresh may have been processed but its result was not stored, the session becomes `uncertain` and the person signs in again; the consumed token is never replayed.
142
+
143
+ Keep `cookieSecret`, `clientSecret`, `webhookSecret` and any session keyring out of source control and out of logs.
144
+
145
+ Supported versions and how to report a vulnerability are described in the [security policy](https://github.com/stwrd-dev/sdk-js/security/policy).
146
+
147
+ ## Error handling and troubleshooting
148
+
149
+ | Error | Meaning | What to do |
150
+ |---|---|---|
151
+ | `ConfigError` | A required option is missing or invalid, or an option has the wrong type. | Fix the configuration; the process should not start. |
152
+ | `OidcError` | The identity provider answered and rejected the request (bad code, invalid token). | Treat the sign-in or the session as invalid. |
153
+ | `IdpUnavailable` | The identity provider, or the session store, did not answer. `requestSent` says whether a request may have been processed. | Answer 503 and keep the session; retry later. |
154
+ | `RefreshUncertain` | A refresh may have been processed without its result being stored. | Send the person through `/auth/sign-in`. |
155
+ | `InvalidSignatureError` | The webhook headers, timestamp or signature are not valid. | Answer 400. |
156
+ | `DuplicateEventError` | The webhook event `id` was already processed. | Answer 200 without reprocessing. |
157
+ | `ManagementError` | The Management API answered with an error status. | Read `status`, `body` and `requestId`. |
158
+
159
+ When the identity provider does not answer, the routes and guards answer 503 and never 401, so a person with a live session is not told that they signed out.
160
+
161
+ ## Compatibility
162
+
163
+ - Node.js 18 or newer; Cloudflare Workers with `nodejs_compat`. The test suite runs on both Node and workerd.
164
+ - ESM only. TypeScript declarations are included; they resolve under `node10`, `nodenext` and `bundler` module resolution.
165
+ - Browsers are not supported: this package holds server-side secrets.
166
+
167
+ ## Versioning
168
+
169
+ This package follows Semantic Versioning (SemVer). While the version is below 1.0, minor releases may contain breaking changes. Changes are listed in `CHANGELOG.md`.
170
+
171
+ ## License
172
+
173
+ MIT © 2026 stwrd. See `LICENSE`.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Back-channel logout bookkeeping: ending a local session and remembering the
3
+ * notice's `jti`, so the same notice is never accepted twice.
4
+ *
5
+ * The order is the contract: the session is deleted FIRST and the `jti` is
6
+ * marked SECOND. The IdP retries a 5xx and does not retry a 4xx; if the `jti`
7
+ * were marked first and the deletion then failed, the retry would find a seen
8
+ * `jti` and answer 400 "replay", and the local session would stay alive for
9
+ * good. A sink that cannot do both (a store that is down) throws and marks
10
+ * nothing.
11
+ */
12
+ import type { SessionStore } from "./sessions.js";
13
+ export interface BackChannelSink {
14
+ /** Ends `sessionId` (when the notice names a session) and marks `jti` as
15
+ * seen until `untilS` (epoch seconds, the notice's own `exp`: afterwards it
16
+ * cannot pass validation anyway). `"replay"` when `jti` was already seen; in
17
+ * that case nothing is deleted. Throws when the store cannot be reached. */
18
+ terminate(sessionId: string | null, jti: string, untilS: number): Promise<"terminated" | "replay">;
19
+ }
20
+ /** The default sink: process-local `jti` memory in front of a `SessionStore`.
21
+ * With several processes each one remembers its own; a sink that spans them
22
+ * (one transaction in the store) is the adapter's to provide. */
23
+ export declare class MemoryBackChannelSink implements BackChannelSink {
24
+ private readonly sessions;
25
+ private readonly seen;
26
+ constructor(sessions: SessionStore);
27
+ terminate(sessionId: string | null, jti: string, untilS: number): Promise<"terminated" | "replay">;
28
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Back-channel logout bookkeeping: ending a local session and remembering the
3
+ * notice's `jti`, so the same notice is never accepted twice.
4
+ *
5
+ * The order is the contract: the session is deleted FIRST and the `jti` is
6
+ * marked SECOND. The IdP retries a 5xx and does not retry a 4xx; if the `jti`
7
+ * were marked first and the deletion then failed, the retry would find a seen
8
+ * `jti` and answer 400 "replay", and the local session would stay alive for
9
+ * good. A sink that cannot do both (a store that is down) throws and marks
10
+ * nothing.
11
+ */
12
+ /** The default sink: process-local `jti` memory in front of a `SessionStore`.
13
+ * With several processes each one remembers its own; a sink that spans them
14
+ * (one transaction in the store) is the adapter's to provide. */
15
+ export class MemoryBackChannelSink {
16
+ sessions;
17
+ seen = new Map();
18
+ constructor(sessions) {
19
+ this.sessions = sessions;
20
+ }
21
+ async terminate(sessionId, jti, untilS) {
22
+ const now = Date.now() / 1000;
23
+ for (const [seenJti, until] of this.seen) {
24
+ if (until <= now) {
25
+ this.seen.delete(seenJti);
26
+ }
27
+ }
28
+ if (this.seen.has(jti)) {
29
+ return "replay";
30
+ }
31
+ // The store is keyed by the BFF's own id and the notice only carries
32
+ // `sid`, so the id was derived (`sessionIdForSid`) and the deletion is the
33
+ // plain `delete(id)` of the store contract: every store, third-party ones
34
+ // included, terminates sessions correctly with no extra method.
35
+ if (sessionId) {
36
+ await this.sessions.delete(sessionId);
37
+ }
38
+ this.seen.set(jti, untilS);
39
+ return "terminated";
40
+ }
41
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `StwrdCore` — the runtime-neutral object behind every JavaScript adapter:
3
+ * configuration, OIDC client, session store, cookie sealing, session
4
+ * resolution with the refresh lease, organization listing and webhook
5
+ * verification. It knows nothing about Express, Node's `http` or a Worker's
6
+ * `env`: `@stwrd-auth/node` extends it with the Express wiring. The Python SDK
7
+ * implements the same session logic.
8
+ */
9
+ import { type EnvLike, type ResolvedConfig, type StwrdConfigOptions } from "./config.js";
10
+ import { type FetchLike, OidcClient } from "./oidc.js";
11
+ import { type OwnOrganization } from "./organizations.js";
12
+ import { type SessionStore, type StwrdSession } from "./sessions.js";
13
+ import { SeenEventIds, type WebhookEvent } from "./webhooks.js";
14
+ /** How long the IdP gets to answer the discovery probe (`probeBeforeExchange`). */
15
+ export declare const DISCOVERY_PROBE_TIMEOUT_MS = 5000;
16
+ /** How a runtime waits for, and checks the IdP before, a refresh. The defaults
17
+ * suit Node; an adapter for a runtime where each store call is expensive (Workers: one Durable Object
18
+ * call per poll) or where a failed exchange cannot be told apart from a lost
19
+ * one passes its own. */
20
+ export interface RefreshTuning {
21
+ /** Seconds a request waits for another owner's refresh of the same session
22
+ * to finish, before answering "the IdP is unavailable". */
23
+ waitS: number;
24
+ /** Milliseconds to wait before polling the store again, given how many polls
25
+ * (0 for the first) already found the lease busy. */
26
+ pollDelayMs(attempt: number): number;
27
+ /** Ask the IdP for its discovery document again, with a
28
+ * `DISCOVERY_PROBE_TIMEOUT_MS` timeout, just BEFORE the exchange is marked as
29
+ * sent. If the IdP does not answer, nothing was marked and nothing sent: the
30
+ * lease is released and the next request tries again. Where a failure of the
31
+ * exchange itself can never be proven "not sent" (the session would go
32
+ * `uncertain`), this is what keeps an IdP that is down from costing a session. */
33
+ probeBeforeExchange: boolean;
34
+ }
35
+ export declare const DEFAULT_REFRESH_TUNING: RefreshTuning;
36
+ /** How a runtime keeps a renewal alive past the request that started it: given
37
+ * the work (a promise that never rejects), it must not let the runtime cut it
38
+ * short. On Workers this is `ctx.waitUntil`; where nothing cuts a request off
39
+ * (Node) there is none, and a renewal runs inline. The caller still awaits the
40
+ * renewal itself; this only stops "the person closed the tab" from killing it
41
+ * between "marked sent" and "saved". */
42
+ export type KeepAlive = (work: Promise<void>) => void;
43
+ export interface StwrdOptions extends StwrdConfigOptions {
44
+ sessions?: SessionStore;
45
+ fetch?: FetchLike;
46
+ }
47
+ export declare class StwrdCore {
48
+ readonly config: ResolvedConfig;
49
+ readonly oidc: OidcClient;
50
+ readonly sessions: SessionStore;
51
+ readonly seenWebhookIds: SeenEventIds;
52
+ private readonly fetchImpl;
53
+ private readonly tuning;
54
+ constructor(options: StwrdOptions, tuning?: RefreshTuning);
55
+ /** HMAC-signed, base64url cookie value. Not encryption — nothing sealed
56
+ * carries a secret the browser must not read; what this buys is
57
+ * tamper-evidence, so a forged cookie unseals to nothing. */
58
+ seal(value: unknown): string;
59
+ /** `null` on anything wrong — missing cookie, bad signature, bad JSON. */
60
+ unseal(cookie: string | null | undefined): unknown;
61
+ csrf(sessionId: string): string;
62
+ /** The local session id for an IdP session — derived, never random. It is
63
+ * what lets back-channel logout reach a session with only the IdP's `sid`,
64
+ * through `SessionStore.delete(id)` and no secondary index. */
65
+ sessionIdForSid(sid: string): string;
66
+ /** Organization selection needs the `org` scope to be requested
67
+ * explicitly; it is never added automatically. */
68
+ organizationSelectionEnabled(): boolean;
69
+ /** The person's own usable organizations, read with their access token. */
70
+ listOrganizations(session: StwrdSession): Promise<OwnOrganization[]>;
71
+ /** The session ready to authorize with — renews the tokens if the session
72
+ * holds a refresh token and the access token has expired. */
73
+ resolveSession(cookie: string | null | undefined, keepAlive?: KeepAlive): Promise<StwrdSession | null>;
74
+ /** Raw store read: unseals the cookie and looks the session up, with no
75
+ * renewal and no expiry side effects. */
76
+ sessionFromCookie(cookie: string | null | undefined): Promise<StwrdSession | null>;
77
+ /** Refresh `session` under the store's lease: across every worker the
78
+ * refresh token is exchanged at most once. `null` when the IdP REJECTED the
79
+ * renewal or the session was deleted meanwhile (`resolveSession` then ends
80
+ * the local session). `IdpUnavailable` propagates when nothing can be
81
+ * vouched for right now: the IdP is silent, another worker holds the lease
82
+ * too long, or an earlier exchange may have rotated the token without its
83
+ * result stored (`uncertain`: permanent for that session, the consumed token
84
+ * is never replayed). With `keepAlive`, the owned renewal (marked sent →
85
+ * exchange → saved) is handed to the runtime to finish even if the request
86
+ * ends first. */
87
+ private renew;
88
+ /** A lost lease is not a rejection: only a session that no longer exists
89
+ * ends (logout). One that is still there, possibly re-created by a new
90
+ * sign-in, must not be deleted by a stale owner. */
91
+ private leaseLost;
92
+ private renewOwned;
93
+ /** Verifies a webhook delivery and deduplicates it against
94
+ * `this.seenWebhookIds`. Throws `ConfigError` if no `webhookSecret` is
95
+ * configured — the `/auth/webhook` route turns that into a 503. */
96
+ verifyWebhook(body: string | Buffer, headers: Record<string, string>): WebhookEvent;
97
+ }
98
+ /** Reads `STWRD_*` variables from `env`, with `overrides` winning over them,
99
+ * and fails closed (`ConfigError`) when a required one is missing from both.
100
+ * There is no default `env` here: `@stwrd-auth/node` passes `process.env`. */
101
+ export declare function stwrdOptionsFromEnv(env: EnvLike, overrides?: Partial<StwrdOptions>): StwrdOptions;