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/dist/vite.js ADDED
@@ -0,0 +1,114 @@
1
+ import { resolve } from 'node:path';
2
+ import { attachToRunning, startServer } from './server.js';
3
+ /**
4
+ * One mock per config and port for the whole process. Vite re-creates plugins on every restart
5
+ * (config change, `server.restart()`), but the port stays taken until the old server is closed –
6
+ * reusing it is simpler and keeps the app's session valid across restarts.
7
+ */
8
+ const running = (globalThis.__oidcMock ??=
9
+ new Map());
10
+ function acquire(configFile, port) {
11
+ const id = `${configFile}#${port ?? ''}`;
12
+ let server = running.get(id);
13
+ if (!server) {
14
+ server = startServer({ config: configFile, port }).catch(async (error) => {
15
+ if (error.code !== 'EADDRINUSE')
16
+ throw error;
17
+ const attached = await attachToRunning({ config: configFile, port });
18
+ if (!attached) {
19
+ throw new Error(`Port ${error.port} is taken by something that is not this oidc-mock. ` +
20
+ 'Pick another port in the config or with oidcMock({ port }).');
21
+ }
22
+ return attached;
23
+ });
24
+ server.catch(() => running.delete(id));
25
+ running.set(id, server);
26
+ }
27
+ return server;
28
+ }
29
+ /**
30
+ * Runs the mock provider next to the Vite dev server.
31
+ *
32
+ * The provider listens on its own loopback port (the back channel: discovery, token, JWKS –
33
+ * called by the app's server), and the Vite server serves the same endpoints under `base_path`
34
+ * (the front channel: the login page, called by the browser). Redirects the app sends to the
35
+ * back channel are rewritten to relative URLs, so the browser stays on whatever host it used to
36
+ * reach Vite – `localhost`, a LAN address with `--host`, or a forwarded port.
37
+ */
38
+ export function oidcMock(options = {}) {
39
+ let root = process.cwd();
40
+ let mock;
41
+ function install(server) {
42
+ const configFile = resolve(root, options.config ?? 'oidc-mock.yaml');
43
+ const pending = acquire(configFile, options.port);
44
+ mock = pending;
45
+ pending.then((started) => {
46
+ const log = server.config.logger;
47
+ log.info(` ➜ oidc-mock: ${started.discoveryUrl}\n` +
48
+ ` ${started.config().users.length} users from ${configFile}` +
49
+ (started.server
50
+ ? ''
51
+ : '\n port taken by another oidc-mock with this issuer – sharing its back channel'));
52
+ }, (error) => server.config.logger.error(`[oidc-mock] ${error.message}`));
53
+ server.middlewares.use(middleware);
54
+ }
55
+ const middleware = (req, res, next) => {
56
+ if (!mock)
57
+ return next();
58
+ mock.then(async (started) => {
59
+ if (options.rewriteRedirects !== false)
60
+ rewriteLocation(res, started);
61
+ if (await started.provider.handle(req, res))
62
+ return;
63
+ next();
64
+ }, () => next());
65
+ };
66
+ return {
67
+ name: 'oidc-mock',
68
+ configResolved(config) {
69
+ root = config.root;
70
+ },
71
+ configureServer(server) {
72
+ install(server);
73
+ },
74
+ configurePreviewServer(server) {
75
+ if (options.preview !== false)
76
+ install(server);
77
+ }
78
+ };
79
+ }
80
+ /**
81
+ * Turns `Location: <issuer origin>/<base_path>/…` into `/<base_path>/…`, both for
82
+ * `setHeader` and for `writeHead`, which is what SvelteKit, Express and friends use.
83
+ */
84
+ function rewriteLocation(res, mock) {
85
+ const issuer = new URL(mock.issuer);
86
+ const prefix = issuer.origin + mock.config().base_path;
87
+ const rewrite = (value) => typeof value === 'string' && (value === prefix || value.startsWith(prefix + '/') || value.startsWith(prefix + '?'))
88
+ ? value.slice(issuer.origin.length)
89
+ : value;
90
+ const setHeader = res.setHeader.bind(res);
91
+ res.setHeader = (name, value) => setHeader(name, name.toLowerCase() === 'location' ? rewrite(value) : value);
92
+ const writeHead = res.writeHead.bind(res);
93
+ res.writeHead = ((...args) => {
94
+ const headersIndex = typeof args[1] === 'string' ? 2 : 1;
95
+ const headers = args[headersIndex];
96
+ if (Array.isArray(headers)) {
97
+ // Either [[name, value], …] or a flat [name, value, name, value, …].
98
+ if (Array.isArray(headers[0])) {
99
+ args[headersIndex] = headers.map(([name, value]) => [name, name.toLowerCase() === 'location' ? rewrite(value) : value]);
100
+ }
101
+ else {
102
+ args[headersIndex] = headers.map((value, i) => i % 2 === 1 && String(headers[i - 1]).toLowerCase() === 'location' ? rewrite(value) : value);
103
+ }
104
+ }
105
+ else if (headers && typeof headers === 'object') {
106
+ args[headersIndex] = Object.fromEntries(Object.entries(headers).map(([name, value]) => [
107
+ name,
108
+ name.toLowerCase() === 'location' ? rewrite(value) : value
109
+ ]));
110
+ }
111
+ return writeHead(...args);
112
+ });
113
+ }
114
+ export default oidcMock;
@@ -0,0 +1,58 @@
1
+ # oidc-mock configuration
2
+ #
3
+ # Edits to `users`, `clients`, `tokens` and `custom_login` apply without a restart.
4
+ # `host`, `port`, `base_path`, `issuer` and `key_file` need one.
5
+
6
+ # Back channel: where the app's server reaches the provider. The issuer is
7
+ # http://<host>:<port><base_path>, the discovery URL that plus /.well-known/openid-configuration.
8
+ host: 127.0.0.1
9
+ port: 8090
10
+ base_path: /oidc
11
+
12
+ # Leave `clients` out to accept any client_id without a secret.
13
+ # clients:
14
+ # - client_id: my-app
15
+ # client_secret: dev-secret # omit for public clients (PKCE)
16
+ # redirect_uris: ['http://*:5173/*', 'https://*:5173/*']
17
+
18
+ tokens:
19
+ access_token_ttl: 1h
20
+ id_token_ttl: 1h
21
+ refresh_token_ttl: 30d
22
+
23
+ # Show the “custom claims” box on the login page.
24
+ custom_login: true
25
+
26
+ # One button per user on the login page. `claims` go into the id token, the access token and
27
+ # userinfo; `sub` is fixed per user, so the app sees the same account every time.
28
+ users:
29
+ - sub: admin
30
+ label: Admin
31
+ description: Every permission
32
+ claims:
33
+ email: admin@example.org
34
+ email_verified: true
35
+ given_name: Ada
36
+ family_name: Admin
37
+ preferred_username: ada
38
+ locale: en
39
+ roles: [admin]
40
+
41
+ - sub: member
42
+ label: Member
43
+ description: Plain account without roles
44
+ claims:
45
+ email: member@example.org
46
+ email_verified: true
47
+ given_name: Max
48
+ family_name: Member
49
+ preferred_username: max
50
+ locale: en
51
+ roles: []
52
+
53
+ - sub: no-email
54
+ label: Without email
55
+ description: Edge case – the email claim is missing
56
+ claims:
57
+ given_name: Nora
58
+ family_name: Noemail
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "oidc-mock",
3
+ "version": "0.1.2",
4
+ "description": "OpenID Connect provider for local development: users and claims in one YAML file, a login page to pick them, and a Vite plugin that makes it work behind --host.",
5
+ "license": "MIT",
6
+ "author": "Tade Strehk <tade@strehk.eu>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/strehk/oidc-mock.git"
10
+ },
11
+ "homepage": "https://github.com/strehk/oidc-mock#readme",
12
+ "bugs": "https://github.com/strehk/oidc-mock/issues",
13
+ "type": "module",
14
+ "keywords": [
15
+ "oidc",
16
+ "openid-connect",
17
+ "oauth2",
18
+ "mock",
19
+ "dev-server",
20
+ "vite-plugin"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "bin": {
26
+ "oidc-mock": "dist/cli.js"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./vite": {
34
+ "types": "./dist/vite.d.ts",
35
+ "default": "./dist/vite.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "examples/oidc-mock.yaml"
41
+ ],
42
+ "scripts": {
43
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
44
+ "dev": "bun --watch src/cli.ts --config examples/oidc-mock.yaml",
45
+ "test": "bun test",
46
+ "typecheck": "tsc --noEmit",
47
+ "prepublishOnly": "bun run typecheck && bun test && bun run build"
48
+ },
49
+ "dependencies": {
50
+ "jose": "^6.2.12",
51
+ "yaml": "^2.9.1",
52
+ "zod": "^4.6.5"
53
+ },
54
+ "peerDependencies": {
55
+ "vite": ">=5"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "vite": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@types/bun": "latest",
64
+ "@types/node": "^26.6.3",
65
+ "openid-client": "^6.8.8",
66
+ "typescript": "^7.0.2",
67
+ "vite": "^8.3.1"
68
+ },
69
+ "publishConfig": {
70
+ "access": "public"
71
+ }
72
+ }