oidc-mock 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tade Strehk
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 ADDED
@@ -0,0 +1,382 @@
1
+ # oidc-mock
2
+
3
+ [![npm](https://img.shields.io/npm/v/oidc-mock)](https://www.npmjs.com/package/oidc-mock)
4
+ [![Test](https://github.com/strehk/oidc-mock/actions/workflows/test.yml/badge.svg)](https://github.com/strehk/oidc-mock/actions/workflows/test.yml)
5
+
6
+ **A fake OpenID Connect provider for local development – users in a YAML file, one click to sign
7
+ in, and a Vite plugin that keeps login working on your phone.**
8
+
9
+ <p align="center">
10
+ <picture>
11
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/strehk/oidc-mock/main/docs/login-dark.png">
12
+ <img src="https://raw.githubusercontent.com/strehk/oidc-mock/main/docs/login-light.png" alt="The oidc-mock login page: one button per configured user, and a box for custom claims" width="420">
13
+ </picture>
14
+ </p>
15
+
16
+ Developing against a real identity provider means test accounts, passwords and a network
17
+ connection. Most mocks fix that but bring their own friction: they run in Docker, you paste claims
18
+ into a text field on every login, and they break the moment you open your app on another device.
19
+
20
+ oidc-mock is a small Node package instead:
21
+
22
+ - **Users are a YAML file.** Every user has a stable `sub` and whatever claims your app expects –
23
+ roles, groups, tenant IDs, any JSON shape. Check the file in, and the whole team signs in as the
24
+ same “Admin” or “Member without email”.
25
+ - **One click to sign in.** The login page shows a button per user. Need a variation? The ✎ next
26
+ to a user opens their claims as JSON, ready to edit, without touching the file.
27
+ - **Edits apply live.** Change a claim in the YAML and the next login – or even the next token
28
+ refresh – carries it. No restart.
29
+ - **Runs inside Vite.** `vite --host`, a phone on the LAN, a forwarded port, a dev container: the
30
+ login page is served by your dev server, so it works wherever your app works.
31
+ - **Real OIDC.** Discovery, signed RS256 JWTs, JWKS, authorization code flow with PKCE, refresh
32
+ tokens, userinfo, introspection, logout. Your app uses its normal OIDC client and the same code
33
+ path as in production.
34
+
35
+ > [!WARNING]
36
+ > This is a development tool. Anyone who can reach it can sign in as anyone, with any claims.
37
+ > Never expose it to the internet or ship it to production.
38
+
39
+ ## Contents
40
+
41
+ - [Quick start](#quick-start)
42
+ - [Configuration](#configuration)
43
+ - [Why a Vite plugin: `--host`, phones and HTTPS](#why-a-vite-plugin---host-phones-and-https)
44
+ - [Recipes](#recipes)
45
+ - [Endpoints](#endpoints)
46
+ - [Programmatic use and tests](#programmatic-use-and-tests)
47
+ - [Compared to other mocks](#compared-to-other-mocks)
48
+ - [Development](#development)
49
+
50
+ ## Quick start
51
+
52
+ ```sh
53
+ npm i -D oidc-mock # or: bun add -d oidc-mock / pnpm add -D oidc-mock
54
+ npx oidc-mock init # writes an example oidc-mock.yaml
55
+ ```
56
+
57
+ ### With Vite (SvelteKit, Nuxt, Astro, Remix, plain Vite, …)
58
+
59
+ ```ts
60
+ // vite.config.ts
61
+ import { defineConfig } from 'vite';
62
+ import { sveltekit } from '@sveltejs/kit/vite';
63
+ import { oidcMock } from 'oidc-mock/vite';
64
+
65
+ export default defineConfig({
66
+ plugins: [oidcMock(), sveltekit()]
67
+ });
68
+ ```
69
+
70
+ On `vite dev` the plugin prints the discovery URL. Give it to your OIDC client as issuer or
71
+ authority, with any client ID:
72
+
73
+ ```sh
74
+ # .env
75
+ OIDC_AUTHORITY=http://127.0.0.1:8090/oidc/.well-known/openid-configuration
76
+ OIDC_CLIENT_ID=my-app
77
+ ```
78
+
79
+ The plugin only runs in `vite dev` and `vite preview`, never in `vite build`.
80
+
81
+ | Option | Default | Description |
82
+ | ------------------ | ------------------ | ---------------------------------------------------------------------------------------------------- |
83
+ | `config` | `'oidc-mock.yaml'` | Path to the YAML file, relative to the Vite root |
84
+ | `port` | from the YAML | Overrides `port` |
85
+ | `rewriteRedirects` | `true` | Rewrite redirects to the mock into relative ones ([why](#why-a-vite-plugin---host-phones-and-https)) |
86
+ | `preview` | `true` | Also run in `vite preview` |
87
+
88
+ ### Standalone
89
+
90
+ For anything that is not Vite – Next.js, Express, a Python backend, a mobile app:
91
+
92
+ ```sh
93
+ npx oidc-mock # reads ./oidc-mock.yaml
94
+ npx oidc-mock -c dev/oidc.yaml -p 9000 -H 0.0.0.0
95
+ ```
96
+
97
+ ```
98
+ oidc-mock listening – 3 users from /path/to/oidc-mock.yaml
99
+ issuer: http://127.0.0.1:8090/oidc
100
+ discovery: http://127.0.0.1:8090/oidc/.well-known/openid-configuration
101
+ ```
102
+
103
+ In this mode the browser talks to the mock directly, so it has to reach `host:port`.
104
+
105
+ ## Configuration
106
+
107
+ A complete file with every option and its default:
108
+
109
+ ```yaml
110
+ # Where the provider listens (the “back channel”, see below).
111
+ # The issuer is http://<host>:<port><base_path>.
112
+ host: 127.0.0.1
113
+ port: 8090
114
+ base_path: /oidc
115
+
116
+ # Set this only when the mock sits behind a proxy under another URL.
117
+ # issuer: https://auth.dev.example.org/oidc
118
+
119
+ # Signing key, generated on first start. Relative to this file.
120
+ key_file: node_modules/.cache/oidc-mock/signing-key.json
121
+
122
+ # Without `clients`, every client_id is accepted without a secret.
123
+ clients:
124
+ - client_id: my-app # public client, PKCE
125
+ redirect_uris: ['http://*:5173/*', 'https://*:5173/*']
126
+ - client_id: backend
127
+ client_secret: dev-secret # confidential client
128
+ # no redirect_uris: anything goes
129
+
130
+ tokens: # seconds, or 30s / 15m / 1h / 30d
131
+ access_token_ttl: 1h
132
+ id_token_ttl: 1h
133
+ refresh_token_ttl: 30d
134
+
135
+ # The “custom claims” box on the login page.
136
+ custom_login: true
137
+
138
+ users:
139
+ - sub: admin # required, unique, becomes the `sub` claim
140
+ label: Admin # button text; default: name, then email, then sub
141
+ description: Every permission # second line on the button
142
+ claims: # anything; goes into id token, access token and userinfo
143
+ email: admin@example.org
144
+ email_verified: true
145
+ given_name: Ada
146
+ family_name: Admin
147
+ roles: [admin]
148
+ ```
149
+
150
+ What to know about claims:
151
+
152
+ - **Anything goes.** Claims are copied verbatim, so an array of strings, an array of objects or a
153
+ nested map all work. The [recipes](#recipes) show the shapes of common providers.
154
+ - **The same claims everywhere.** The id token, the access token and `/userinfo` all carry the
155
+ full set, so it does not matter where your app reads a claim from.
156
+ - **Protected claims.** `iss`, `sub`, `aud`, `exp`, `iat`, `nbf` and `jti` are set by the mock. If
157
+ a user's claims contain them, they are ignored.
158
+
159
+ What reloads without a restart:
160
+
161
+ - **Live:** `users`, `clients`, `tokens` and `custom_login`. The file is checked on every request.
162
+ A broken file logs an error and keeps the last good config.
163
+ - **Needs a restart:** `host`, `port`, `base_path`, `issuer` and `key_file`. A warning tells you
164
+ when one of them changed.
165
+
166
+ Refresh tokens remember which user they belong to. For a user from the file, a refresh re-reads
167
+ their claims, so you can take away a role and watch the app react without logging out.
168
+
169
+ ## Why a Vite plugin: `--host`, phones and HTTPS
170
+
171
+ An OIDC client discovers its provider once, on the server, and from then on sends the browser to
172
+ the `authorization_endpoint` from that discovery. With a mock at `http://127.0.0.1:8090` that
173
+ breaks in several everyday situations:
174
+
175
+ - **`vite --host` and a phone on the LAN.** The phone opens `http://192.168.1.23:5173` and gets
176
+ redirected to `http://127.0.0.1:8090/…` – which is the phone itself.
177
+ - **Mock in Docker, dev server somewhere else.** Remote dev boxes, Codespaces, SSH port forwards:
178
+ you forwarded the app's port, but not the mock's.
179
+ - **HTTPS dev server.** A self-signed or mkcert certificate is fine for the browser, but a
180
+ server-side fetch from Node to `https://localhost:5173` fails certificate validation.
181
+
182
+ The Vite plugin splits the provider into two channels:
183
+
184
+ ```mermaid
185
+ flowchart LR
186
+ B["Browser<br/>(laptop, phone, …)"] -- "app pages and<br/>/oidc/authorize (login page)" --> V["Vite dev server<br/>any host, http or https"]
187
+ A["App server code<br/>(OIDC client)"] -- "discovery, token, JWKS<br/>plain HTTP on loopback" --> M["oidc-mock back channel<br/>127.0.0.1:8090"]
188
+ V -. "same process" .- A
189
+ V -. "same process" .- M
190
+ ```
191
+
192
+ - **Back channel.** Discovery, token, JWKS, userinfo and introspection run on a loopback port over
193
+ plain HTTP. Only your app's server calls them, so HTTPS certificates and host names never
194
+ matter here.
195
+ - **Front channel.** The Vite server serves the same endpoints under `base_path`. The browser only
196
+ ever needs the login page and the logout endpoint, and those come from wherever it loaded your
197
+ app from.
198
+ - **Redirect rewriting.** When your app redirects to `http://127.0.0.1:8090/oidc/authorize?…`,
199
+ the plugin rewrites the `Location` header to `/oidc/authorize?…`. The browser stays on
200
+ `192.168.1.23:5173`, and since your OIDC client builds `redirect_uri` from the request host, it
201
+ comes back there as well.
202
+
203
+ Your app code does not change. Only its authority URL points at the mock.
204
+
205
+ **Several dev servers at once.** Start a second `vite dev` in the same project, e.g. in a Git
206
+ worktree or next to a test run, and it finds the back channel port taken. If an oidc-mock with
207
+ the same issuer answers there, the second server shares it and serves only the login page. That
208
+ works because codes and refresh tokens are self-contained signed JWTs and both servers use the
209
+ same key file. If something else holds the port, the plugin says so and leaves the dev server
210
+ running.
211
+
212
+ > [!NOTE]
213
+ > Vite refuses unknown host names by default. If you open the dev server by host name instead of
214
+ > by IP (e.g. `my-laptop.local`), add it to
215
+ > [`server.allowedHosts`](https://vite.dev/config/server-options#server-allowedhosts).
216
+
217
+ ## Recipes
218
+
219
+ ### Role claims in the shape of your production provider
220
+
221
+ Keep the claims shaped like production, so the same parsing code runs in dev:
222
+
223
+ ```yaml
224
+ users:
225
+ # Logto: an array of role objects
226
+ - sub: logto-admin
227
+ claims:
228
+ email: admin@example.org
229
+ roles: [{ name: admin }]
230
+
231
+ # Zitadel: a map keyed by role, then by organisation
232
+ - sub: zitadel-admin
233
+ claims:
234
+ email: admin@example.org
235
+ 'urn:zitadel:iam:org:project:roles':
236
+ admin: { '123456789': example.org }
237
+
238
+ # Keycloak: realm roles
239
+ - sub: keycloak-admin
240
+ claims:
241
+ email: admin@example.org
242
+ realm_access: { roles: [admin] }
243
+
244
+ # Auth0 / Entra style: namespaced string array
245
+ - sub: groups-admin
246
+ claims:
247
+ email: admin@example.org
248
+ 'https://example.org/groups': [admins, staff]
249
+ ```
250
+
251
+ ### Edge cases as permanent presets
252
+
253
+ The users you only test once a year are the ones that break. Make them one click away:
254
+
255
+ ```yaml
256
+ users:
257
+ - sub: no-email
258
+ label: Without email
259
+ description: Provider did not release the email scope
260
+ claims: { given_name: Nora }
261
+
262
+ - sub: unverified
263
+ label: Unverified email
264
+ claims: { email: new@example.org, email_verified: false }
265
+
266
+ - sub: unicode
267
+ label: Ünïcödé name
268
+ claims: { email: zoe@example.org, given_name: Zoë, family_name: Ó Súilleabháin-Müller }
269
+
270
+ - sub: no-roles
271
+ label: Roles claim missing
272
+ claims: { email: plain@example.org }
273
+ ```
274
+
275
+ ### Logging in from a script or `curl`
276
+
277
+ The login page is a plain HTML form, so a script can sign in without a browser. Send the hidden
278
+ `params` field back unchanged and name a user:
279
+
280
+ ```sh
281
+ # 1. Start a login in your app and grab the redirect to the mock
282
+ AUTH=$(curl -s -c jar -o /dev/null -w '%{redirect_url}' http://127.0.0.1:5173/login)
283
+ # 2. Read the hidden params field from the login page
284
+ PARAMS=$(curl -s "$AUTH" | sed -n 's/.*name="params" value="\([^"]*\)".*/\1/p' | head -1 \
285
+ | sed 's/&quot;/"/g; s/&#39;/'"'"'/g; s/&lt;/</g; s/&gt;/>/g; s/&amp;/\&/g')
286
+ # 3. Pick a user; the response redirects back to your app with a code
287
+ curl -s -o /dev/null -w '%{redirect_url}' "${AUTH%%\?*}" \
288
+ --data-urlencode "params=$PARAMS" --data-urlencode "sub=admin"
289
+ ```
290
+
291
+ Then open the returned URL with the same cookie jar (`curl -b jar -c jar …`) to finish the login
292
+ in your app. For custom claims, send `custom=1`, `custom_sub=…` and `custom_claims=<json>` instead
293
+ of `sub`.
294
+
295
+ ## Endpoints
296
+
297
+ All below the issuer, e.g. `http://127.0.0.1:8090/oidc`:
298
+
299
+ | Path | Supports |
300
+ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
301
+ | `/.well-known/openid-configuration` | discovery; `/.well-known/oauth-authorization-server` too |
302
+ | `/jwks` | the RS256 public key |
303
+ | `/authorize` | `response_type=code`; PKCE `S256` and `plain`; `state`, `nonce`; `prompt=none` answers `login_required`; RFC 9207 `iss` in the response |
304
+ | `/token` | `authorization_code`, `refresh_token`; client auth `none`, `client_secret_basic`, `client_secret_post` |
305
+ | `/userinfo` | `GET` and `POST`, bearer token |
306
+ | `/introspect` | access, refresh and id tokens |
307
+ | `/revoke` | accepts and ignores |
308
+ | `/end_session` | redirects to `post_logout_redirect_uri` with `state`, or shows a “signed out” page |
309
+
310
+ What the tokens contain:
311
+
312
+ - **`id_token`:** issued when the scope contains `openid`.
313
+ - **`refresh_token`:** issued when the scope contains `offline_access`.
314
+ - **Access token:** a JWT (`typ: at+jwt`) with the client ID as audience, so clients that verify
315
+ it locally against the JWKS work as well.
316
+ - **Codes:** single-use, valid for two minutes.
317
+ - **CORS:** open on all JSON endpoints, so browser-only SPAs can call the token endpoint directly.
318
+
319
+ What is deliberately missing: implicit and hybrid flows, client credentials, device flow, dynamic
320
+ client registration, `request` objects, encrypted tokens and consent screens. If you need one of
321
+ them, open an issue.
322
+
323
+ ## Programmatic use and tests
324
+
325
+ ```ts
326
+ import { startServer } from 'oidc-mock';
327
+
328
+ const mock = await startServer({
329
+ inline: {
330
+ port: 0, // pick a free port
331
+ users: [{ sub: 'alice', claims: { email: 'alice@example.org', roles: ['admin'] } }]
332
+ }
333
+ });
334
+
335
+ mock.issuer; // http://127.0.0.1:54321/oidc
336
+ mock.discoveryUrl; // …/.well-known/openid-configuration
337
+ await mock.close();
338
+ ```
339
+
340
+ `startServer({ config: 'path/to/oidc-mock.yaml' })` loads a file instead. To mount the provider
341
+ in an existing server, `createProvider()` returns an object whose
342
+ `handle(req, res): Promise<boolean>` works with `node:http`, Express and Connect.
343
+
344
+ ## Compared to other mocks
345
+
346
+ | Mock | Login page | Config | Runs in |
347
+ | ------------------------------------------------------------------------- | --------------------------------- | ---------------------- | ---------------------- |
348
+ | **oidc-mock** | one click per user, editable | YAML, live reload | Node / Vite dev server |
349
+ | [navikt/mock-oauth2-server](https://github.com/navikt/mock-oauth2-server) | text field for JSON claims | JSON env var | JVM / Docker |
350
+ | [oauth2-mock-server](https://github.com/axa-group/oauth2-mock-server) | none, approves every request | code, event hooks | Node |
351
+ | [oidc-provider](https://github.com/panva/node-oidc-provider) | username only | code, many options | Node |
352
+ | [Soluto/oidc-server-mock](https://github.com/Soluto/oidc-server-mock) | username and password | JSON / YAML | .NET / Docker |
353
+ | [Dex](https://dexidp.io) | username and password | YAML, no custom claims | Go / Docker |
354
+
355
+ For automated tests without a browser, `oauth2-mock-server` is a fine choice. oidc-mock is meant
356
+ for the times a human clicks through the app.
357
+
358
+ ## Development
359
+
360
+ ```sh
361
+ bun install
362
+ bun test # end-to-end with openid-client, plus the Vite plugin behind a foreign host
363
+ bun run dev # CLI with examples/oidc-mock.yaml, restarts on change
364
+ bun run typecheck
365
+ bun run build # → dist/
366
+ ```
367
+
368
+ Releases: bump `version` in `package.json`, then push a tag `v<version>`. The workflow tests,
369
+ builds, publishes to npm with provenance (trusted publishing, no token) and creates a GitHub
370
+ release with the tarball attached.
371
+
372
+ ```sh
373
+ git tag v0.1.1 && git push --tags
374
+ ```
375
+
376
+ The runtime needs Node 20 or later. Bun is only used for development. The dependencies are
377
+ [`jose`](https://github.com/panva/jose), [`yaml`](https://github.com/eemeli/yaml) and
378
+ [`zod`](https://zod.dev).
379
+
380
+ ## License
381
+
382
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { copyFileSync, existsSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { parseArgs } from 'node:util';
6
+ import { ConfigError } from './config.js';
7
+ import { startServer } from './server.js';
8
+ const usage = `Usage:
9
+ oidc-mock [--config oidc-mock.yaml] [--port 8090] [--host 127.0.0.1]
10
+ oidc-mock init [file] write an example config
11
+
12
+ Options:
13
+ -c, --config YAML file (default: oidc-mock.yaml)
14
+ -p, --port overrides "port" from the config
15
+ -H, --host overrides "host" from the config
16
+ -h, --help show this help`;
17
+ const { values, positionals } = parseArgs({
18
+ allowPositionals: true,
19
+ options: {
20
+ config: { type: 'string', short: 'c', default: 'oidc-mock.yaml' },
21
+ port: { type: 'string', short: 'p' },
22
+ host: { type: 'string', short: 'H' },
23
+ help: { type: 'boolean', short: 'h' }
24
+ }
25
+ });
26
+ if (values.help) {
27
+ console.log(usage);
28
+ process.exit(0);
29
+ }
30
+ if (positionals[0] === 'init') {
31
+ const target = resolve(positionals[1] ?? values.config);
32
+ if (existsSync(target)) {
33
+ console.error(`${target} already exists.`);
34
+ process.exit(1);
35
+ }
36
+ const example = resolve(dirname(fileURLToPath(import.meta.url)), '../examples/oidc-mock.yaml');
37
+ copyFileSync(example, target);
38
+ console.log(`Wrote ${target}`);
39
+ process.exit(0);
40
+ }
41
+ if (positionals.length) {
42
+ console.error(`Unknown command "${positionals[0]}".\n\n${usage}`);
43
+ process.exit(1);
44
+ }
45
+ if (!existsSync(values.config)) {
46
+ console.error(`${resolve(values.config)} not found. Create one with: oidc-mock init`);
47
+ process.exit(1);
48
+ }
49
+ try {
50
+ const mock = await startServer({
51
+ config: values.config,
52
+ port: values.port === undefined ? undefined : Number(values.port),
53
+ host: values.host
54
+ });
55
+ const users = mock.config().users.length;
56
+ console.log(`oidc-mock listening – ${users} user${users === 1 ? '' : 's'} from ${resolve(values.config)}`);
57
+ console.log(` issuer: ${mock.issuer}`);
58
+ console.log(` discovery: ${mock.discoveryUrl}`);
59
+ const stop = () => mock.close().then(() => process.exit(0));
60
+ process.on('SIGINT', stop);
61
+ process.on('SIGTERM', stop);
62
+ }
63
+ catch (error) {
64
+ const code = error.code;
65
+ if (error instanceof ConfigError)
66
+ console.error(error.message);
67
+ else if (code === 'EADDRINUSE' || code === 'EACCES') {
68
+ const { address, port } = error;
69
+ console.error(`Cannot listen on ${address}:${port} (${code}). Pick another port with --port or in the config.`);
70
+ }
71
+ else
72
+ console.error(error);
73
+ process.exit(1);
74
+ }
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ declare const userSchema: z.ZodObject<{
3
+ sub: z.ZodString;
4
+ label: z.ZodOptional<z.ZodString>;
5
+ description: z.ZodOptional<z.ZodString>;
6
+ claims: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7
+ }, z.core.$strip>;
8
+ declare const clientSchema: z.ZodObject<{
9
+ client_id: z.ZodString;
10
+ client_secret: z.ZodOptional<z.ZodString>;
11
+ redirect_uris: z.ZodDefault<z.ZodArray<z.ZodString>>;
12
+ }, z.core.$strip>;
13
+ declare const configSchema: z.ZodObject<{
14
+ host: z.ZodDefault<z.ZodString>;
15
+ port: z.ZodDefault<z.ZodNumber>;
16
+ base_path: z.ZodPipe<z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string, string>>, z.ZodTransform<string, string>>;
17
+ issuer: z.ZodOptional<z.ZodURL>;
18
+ key_file: z.ZodDefault<z.ZodString>;
19
+ clients: z.ZodDefault<z.ZodArray<z.ZodObject<{
20
+ client_id: z.ZodString;
21
+ client_secret: z.ZodOptional<z.ZodString>;
22
+ redirect_uris: z.ZodDefault<z.ZodArray<z.ZodString>>;
23
+ }, z.core.$strip>>>;
24
+ tokens: z.ZodPrefault<z.ZodObject<{
25
+ access_token_ttl: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
26
+ id_token_ttl: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
27
+ refresh_token_ttl: z.ZodDefault<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>;
28
+ }, z.core.$strip>>;
29
+ custom_login: z.ZodDefault<z.ZodBoolean>;
30
+ users: z.ZodDefault<z.ZodArray<z.ZodObject<{
31
+ sub: z.ZodString;
32
+ label: z.ZodOptional<z.ZodString>;
33
+ description: z.ZodOptional<z.ZodString>;
34
+ claims: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
35
+ }, z.core.$strip>>>;
36
+ }, z.core.$strip>;
37
+ export type MockUser = z.infer<typeof userSchema>;
38
+ export type MockClient = z.infer<typeof clientSchema>;
39
+ export type MockConfig = z.infer<typeof configSchema> & {
40
+ /** Absolute path of the YAML file, if the config came from one. */
41
+ file?: string;
42
+ /** `issuer` with the default filled in. */
43
+ issuer: string;
44
+ };
45
+ export type MockConfigInput = z.input<typeof configSchema>;
46
+ export declare class ConfigError extends Error {
47
+ }
48
+ export declare function resolveConfig(input: unknown, file?: string): MockConfig;
49
+ export declare function loadConfigFile(path: string): MockConfig;
50
+ /**
51
+ * Returns the current config and re-reads the file whenever it changed, so edits to users and
52
+ * claims apply to the next login without a restart. `host`, `port`, `base_path`, `issuer` and
53
+ * `key_file` stay as they were at start – changing them needs a restart, and a warning says so.
54
+ */
55
+ export declare function watchConfig(initial: MockConfig): () => MockConfig;
56
+ export declare function findClient(config: MockConfig, clientId: string): MockClient | undefined;
57
+ export declare function redirectUriAllowed(client: MockClient, redirectUri: string): boolean;
58
+ export declare function userLabel(user: MockUser): string;
59
+ export {};