@o3co/create-auth-provider 0.13.0 → 0.15.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/package.json +2 -2
- package/templates/standalone/.env.example +27 -0
- package/templates/standalone/Dockerfile +1 -1
- package/templates/standalone/README.ja.md +80 -2
- package/templates/standalone/README.md +160 -4
- package/templates/standalone/config/application.conf +56 -1
- package/templates/standalone/docker-compose.production.yml +7 -0
- package/templates/standalone/docker-compose.yml +7 -0
- package/templates/standalone/package.json +4 -3
- package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +101 -0
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +62 -3
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +384 -0
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +431 -0
- package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +57 -0
- package/templates/standalone/src/__tests__/metrics.test.mts +14 -2
- package/templates/standalone/src/__tests__/replica-safety.test.mts +58 -0
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +37 -0
- package/templates/standalone/src/__tests__/shutdown.test.mts +96 -2
- package/templates/standalone/src/__tests__/smoke.test.mts +1 -0
- package/templates/standalone/src/__tests__/supertest-loopback.test.mts +125 -0
- package/templates/standalone/src/app.mts +9 -1
- package/templates/standalone/src/buildModules.mts +110 -6
- package/templates/standalone/src/modules.mts +96 -6
- package/templates/standalone/src/shutdown.mts +70 -4
- package/templates/standalone/vitest.config.mts +6 -0
- package/templates/standalone/vitest.supertest-loopback.mts +250 -0
- package/templates/versions.json +15 -14
|
@@ -61,10 +61,11 @@ import type { Logger } from "@o3co/auth-provider-core";
|
|
|
61
61
|
* reports through its callback, and treating that as success would tell an
|
|
62
62
|
* orchestrator the listener came down when it did not.
|
|
63
63
|
*
|
|
64
|
-
* Size `drainTimeoutMs` **below** the orchestrator's
|
|
65
|
-
* (Kubernetes `terminationGracePeriodSeconds
|
|
66
|
-
*
|
|
67
|
-
*
|
|
64
|
+
* Size `drainTimeoutMs` plus `cleanupTimeoutMs` **below** the orchestrator's
|
|
65
|
+
* own kill grace period (Kubernetes `terminationGracePeriodSeconds` is 30s by
|
|
66
|
+
* default, compose `stop_grace_period` 10s; with federation grants on the sum
|
|
67
|
+
* is 55s, so the grace is 60s or more) — the point is to close on our terms
|
|
68
|
+
* before SIGKILL arrives on someone else's.
|
|
68
69
|
*/
|
|
69
70
|
export interface GracefulShutdownOptions {
|
|
70
71
|
readonly logger: Logger;
|
|
@@ -102,6 +103,71 @@ export function deferExit(code: number, exitProcess: (code: number) => void = pr
|
|
|
102
103
|
const DEFAULT_DRAIN_TIMEOUT_MS = 10_000;
|
|
103
104
|
const SIGNALS: readonly NodeJS.Signals[] = ["SIGTERM", "SIGINT"];
|
|
104
105
|
|
|
106
|
+
/**
|
|
107
|
+
* The least cleanup allowance federation grants get (#593 slice 7): 45
|
|
108
|
+
* seconds. The package's background registry drains on dispose — it waits for
|
|
109
|
+
* a rotated upstream credential's write — and the longest tail one refresh
|
|
110
|
+
* can have is the upstream hard timeout, the persist budget and the wait for
|
|
111
|
+
* the grant's lock, back to back. With the shipped budgets (25 s + 3 s + 5 s)
|
|
112
|
+
* and {@link FEDERATION_GRANTS_CLEANUP_MARGIN_MS} that is exactly this floor;
|
|
113
|
+
* a deployment that raises a budget raises the allowance with it, below.
|
|
114
|
+
* The ten-second drain that cleanup would otherwise inherit is shorter than
|
|
115
|
+
* the tail, and a shutdown under it would abandon exactly the write the drain
|
|
116
|
+
* exists to wait for, after the IdP had moved on to the new refresh token.
|
|
117
|
+
*
|
|
118
|
+
* A host policy, not a grant setting: what it bounds is `handle.dispose()`,
|
|
119
|
+
* and the orchestrator's grace period has to cover the drain, this and an exit
|
|
120
|
+
* margin — the compose files ship 60 seconds for the shipped budgets, and an
|
|
121
|
+
* operator who raises a budget raises the grace to match.
|
|
122
|
+
*/
|
|
123
|
+
export const FEDERATION_GRANTS_CLEANUP_ALLOWANCE_MS = 45_000;
|
|
124
|
+
|
|
125
|
+
/** What the allowance adds to the longest refresh tail: an exit margin. */
|
|
126
|
+
export const FEDERATION_GRANTS_CLEANUP_MARGIN_MS = 12_000;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The most a timer can be asked for: Node's `setTimeout` takes a 32-bit signed
|
|
130
|
+
* delay, and a larger one fires after ~1 ms instead. A sum of budgets an
|
|
131
|
+
* operator set high enough to reach it would otherwise turn the allowance
|
|
132
|
+
* into no allowance at all (Copilot, #614).
|
|
133
|
+
*/
|
|
134
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The `cleanupTimeoutMs` to hand {@link installGracefulShutdown}, from the
|
|
138
|
+
* config: while the feature is on, the longer of the floor above and the
|
|
139
|
+
* configured refresh tail plus the margin; while it is off, nothing — the
|
|
140
|
+
* drain's own budget, as before. A fragment to spread, so an absent allowance
|
|
141
|
+
* is absent rather than `undefined`. A config that carries no budgets (one
|
|
142
|
+
* built by hand, without `reference.conf`) gets the floor.
|
|
143
|
+
*/
|
|
144
|
+
export function cleanupAllowanceFor(config: {
|
|
145
|
+
readonly federationGrants?:
|
|
146
|
+
| {
|
|
147
|
+
readonly enabled?: boolean | undefined;
|
|
148
|
+
readonly upstreamHardTimeoutMs?: number | undefined;
|
|
149
|
+
readonly persistRetryBudgetMs?: number | undefined;
|
|
150
|
+
readonly lockWaitMs?: number | undefined;
|
|
151
|
+
}
|
|
152
|
+
| undefined;
|
|
153
|
+
}): { readonly cleanupTimeoutMs: number } | Record<string, never> {
|
|
154
|
+
const grants = config.federationGrants;
|
|
155
|
+
if (grants?.enabled !== true) return {};
|
|
156
|
+
const budgets = [grants.upstreamHardTimeoutMs, grants.persistRetryBudgetMs, grants.lockWaitMs];
|
|
157
|
+
const tail = budgets.every((budget) => typeof budget === "number" && Number.isFinite(budget))
|
|
158
|
+
? (budgets as number[]).reduce(
|
|
159
|
+
(sum, budget) => sum + budget,
|
|
160
|
+
FEDERATION_GRANTS_CLEANUP_MARGIN_MS,
|
|
161
|
+
)
|
|
162
|
+
: 0;
|
|
163
|
+
return {
|
|
164
|
+
cleanupTimeoutMs: Math.min(
|
|
165
|
+
Math.max(FEDERATION_GRANTS_CLEANUP_ALLOWANCE_MS, tail),
|
|
166
|
+
MAX_TIMER_MS,
|
|
167
|
+
),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
105
171
|
export function installGracefulShutdown(server: Server, options: GracefulShutdownOptions): void {
|
|
106
172
|
const {
|
|
107
173
|
logger,
|
|
@@ -20,5 +20,11 @@ export default defineConfig({
|
|
|
20
20
|
// monorepo the same packages are symlinks to source and are inlined
|
|
21
21
|
// anyway; this makes the two layouts behave the same.
|
|
22
22
|
server: { deps: { inline: [/@o3co\/auth-provider-/] } },
|
|
23
|
+
// #556: the server `request(app)` starts listens on 127.0.0.1, the
|
|
24
|
+
// address supertest dials. Unpatched, macOS can give it a port another
|
|
25
|
+
// process holds on 127.0.0.1, and the request hangs on that process until
|
|
26
|
+
// the test timeout. Rationale and mechanics in the file itself. The
|
|
27
|
+
// Dockerfile `test` stage copies it; scaffold-assets.test.mts checks that.
|
|
28
|
+
setupFiles: ["./vitest.supertest-loopback.mts"],
|
|
23
29
|
},
|
|
24
30
|
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 1o1 Co. Ltd.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* vitest setup file (#556): the server supertest starts listens on the
|
|
19
|
+
* address supertest dials.
|
|
20
|
+
*
|
|
21
|
+
* `request(app)` / `request.agent(app)` start a server with `app.listen(0)`
|
|
22
|
+
* and send the request to `127.0.0.1:<port>`. A hostless `listen` binds the
|
|
23
|
+
* dual-stack wildcard `[::]:P`, and on macOS (BSD `SO_REUSEADDR` semantics —
|
|
24
|
+
* libuv sets it on every TCP listener) the kernel will choose a `P` that
|
|
25
|
+
* another process already holds as `127.0.0.1:P`. The more specific socket
|
|
26
|
+
* wins, so the test's request is delivered to that process. When it accepts
|
|
27
|
+
* and never answers — an editor helper, in the case that was caught — the
|
|
28
|
+
* request hangs until the test timeout, in whichever supertest test drew the
|
|
29
|
+
* port, and passes on the next run. Linux refuses the conflicting bind
|
|
30
|
+
* (EADDRINUSE), so CI does not show it.
|
|
31
|
+
*
|
|
32
|
+
* Binding `127.0.0.1` explicitly removes both halves: the kernel will not
|
|
33
|
+
* give out a `127.0.0.1:P` someone else holds, and against a wildcard holder
|
|
34
|
+
* our specific socket is the one that receives. A specific-host `listen` is
|
|
35
|
+
* asynchronous (it goes through `dns.lookup`), and supertest reads the port
|
|
36
|
+
* synchronously in its constructor, so the URL is completed when the request
|
|
37
|
+
* is sent (`end`, which `then` and `expect(..., fn)` go through). A server the
|
|
38
|
+
* test started itself is never touched.
|
|
39
|
+
*
|
|
40
|
+
* Which version of supertest closes the server it started, and when, has
|
|
41
|
+
* changed under this file once (7.3.0): 7.2 closed `_server` before asserting;
|
|
42
|
+
* 7.3 tracks the servers it started itself in a private registry and closes
|
|
43
|
+
* only those, so a server this file bound is left listening. The patched
|
|
44
|
+
* `end` therefore closes the server this request started, before the test's
|
|
45
|
+
* callback, whenever supertest has not — the same order either way.
|
|
46
|
+
*
|
|
47
|
+
* supertest is loaded from the package that owns the running test file (the
|
|
48
|
+
* nearest `package.json` above it), so the patched class is the one that test
|
|
49
|
+
* imports, whatever directory vitest was started from. A package that does
|
|
50
|
+
* not declare supertest is left alone. Everything else this file cannot do —
|
|
51
|
+
* no test path, a declared supertest that does not load, a supertest whose
|
|
52
|
+
* internals no longer look like this — throws, so the guard is never silently
|
|
53
|
+
* off. That load-time check sees only the two methods' presence; a supertest
|
|
54
|
+
* that moved the `listen` out of `serverAddress` would pass it and leave the
|
|
55
|
+
* guard off. The behavioural tests catch that shape of change: this
|
|
56
|
+
* template's `supertest-loopback.test.mts` and its twin in the session
|
|
57
|
+
* package assert the address the server actually bound.
|
|
58
|
+
*
|
|
59
|
+
* This file is part of the project template and ships with every scaffold.
|
|
60
|
+
* The auth.provider workspace loads this same file for its packages (see
|
|
61
|
+
* `WORKSPACE_TEST_SETUP` in its `vitest.shared.mts`).
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
import { once } from "node:events";
|
|
65
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
66
|
+
import { createRequire } from "node:module";
|
|
67
|
+
import type { Server } from "node:net";
|
|
68
|
+
import { dirname, join } from "node:path";
|
|
69
|
+
import { Server as TlsServer } from "node:tls";
|
|
70
|
+
import { expect } from "vitest";
|
|
71
|
+
|
|
72
|
+
const LOOPBACK = "127.0.0.1";
|
|
73
|
+
const PATCHED = Symbol.for("o3co.auth.provider.supertest-loopback");
|
|
74
|
+
const PENDING = Symbol("o3co.auth.provider.supertest-loopback.pending");
|
|
75
|
+
|
|
76
|
+
type Pending = {
|
|
77
|
+
readonly server: Server;
|
|
78
|
+
readonly protocol: string;
|
|
79
|
+
readonly path: string;
|
|
80
|
+
/** Settles on `listening`, rejects on a listen `error`. */
|
|
81
|
+
readonly ready: Promise<unknown>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type TestInstance = {
|
|
85
|
+
url: string;
|
|
86
|
+
_server?: Server;
|
|
87
|
+
[PENDING]?: Pending;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type TestPrototype = {
|
|
91
|
+
serverAddress(this: TestInstance, app: Server, path: string): string;
|
|
92
|
+
end(this: TestInstance, fn?: (err: unknown, res?: unknown) => void): TestInstance;
|
|
93
|
+
[PATCHED]?: true;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const DEPENDENCY_FIELDS = [
|
|
97
|
+
"dependencies",
|
|
98
|
+
"devDependencies",
|
|
99
|
+
"peerDependencies",
|
|
100
|
+
"optionalDependencies",
|
|
101
|
+
] as const;
|
|
102
|
+
|
|
103
|
+
/** The nearest `package.json` at or above `dir`. */
|
|
104
|
+
function owningManifest(dir: string): string | undefined {
|
|
105
|
+
for (let current = dir; ; current = dirname(current)) {
|
|
106
|
+
const candidate = join(current, "package.json");
|
|
107
|
+
if (existsSync(candidate)) return candidate;
|
|
108
|
+
if (dirname(current) === current) return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** supertest's `Test.prototype` as the running test file sees it, or `undefined` when its package has no supertest. */
|
|
113
|
+
function supertestFor(testPath: string): TestPrototype | undefined {
|
|
114
|
+
const manifestPath = owningManifest(dirname(testPath));
|
|
115
|
+
if (!manifestPath) {
|
|
116
|
+
throw new Error(`vitest.supertest-loopback.mts: no package.json above ${testPath}.`);
|
|
117
|
+
}
|
|
118
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as Record<string, unknown>;
|
|
119
|
+
const declared = DEPENDENCY_FIELDS.some(
|
|
120
|
+
(field) => (manifest[field] as Record<string, unknown> | undefined)?.supertest !== undefined,
|
|
121
|
+
);
|
|
122
|
+
if (!declared) return undefined;
|
|
123
|
+
|
|
124
|
+
let supertest: { Test?: { prototype: TestPrototype } };
|
|
125
|
+
try {
|
|
126
|
+
supertest = createRequire(manifestPath)("supertest");
|
|
127
|
+
} catch (cause) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`vitest.supertest-loopback.mts: ${manifestPath} declares supertest, but it cannot be loaded from there; the #556 loopback guard would be off.`,
|
|
130
|
+
{ cause },
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const proto = supertest.Test?.prototype;
|
|
134
|
+
if (typeof proto?.serverAddress !== "function" || typeof proto.end !== "function") {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"vitest.supertest-loopback.mts: supertest no longer exposes Test.prototype.serverAddress/end; " +
|
|
137
|
+
"re-check the #556 loopback guard against the installed supertest before removing it.",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return proto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The bind in progress per server. A second request on a server whose bind has
|
|
145
|
+
* not finished (an agent's requests sent together share one server) waits on
|
|
146
|
+
* the same bind instead of calling `listen` twice — and, like unpatched
|
|
147
|
+
* supertest when it finds a server already listening, does not own it.
|
|
148
|
+
*/
|
|
149
|
+
const binding = new WeakMap<Server, Promise<unknown>>();
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A throw from the test's own callback, raised where it cannot propagate to
|
|
153
|
+
* the test: reported as an uncaught exception, which fails the run by name,
|
|
154
|
+
* instead of an unhandled rejection of a promise nobody holds.
|
|
155
|
+
*/
|
|
156
|
+
function rethrowOutsideThisChain(err: unknown): void {
|
|
157
|
+
process.nextTick(() => {
|
|
158
|
+
throw err;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function patch(proto: TestPrototype): void {
|
|
163
|
+
if (proto[PATCHED]) return;
|
|
164
|
+
const originalServerAddress = proto.serverAddress;
|
|
165
|
+
const originalEnd = proto.end;
|
|
166
|
+
|
|
167
|
+
proto.serverAddress = function serverAddress(app, path) {
|
|
168
|
+
// Already listening: the test owns the server and chose its address.
|
|
169
|
+
if (app.address()) return originalServerAddress.call(this, app, path);
|
|
170
|
+
|
|
171
|
+
const protocol = app instanceof TlsServer ? "https" : "http";
|
|
172
|
+
let ready = binding.get(app);
|
|
173
|
+
if (!ready) {
|
|
174
|
+
// supertest 7.2 closes `_server` after the response; 7.3 does not,
|
|
175
|
+
// and the callback in the patched `end` below closes it instead.
|
|
176
|
+
this._server = app.listen(0, LOOPBACK);
|
|
177
|
+
// Subscribed now, not in `end`: a listen error emitted before the test
|
|
178
|
+
// sends the request must still reach it, not crash the worker as an
|
|
179
|
+
// unhandled 'error' event.
|
|
180
|
+
const bound = once(app, "listening");
|
|
181
|
+
binding.set(app, bound);
|
|
182
|
+
bound.then(
|
|
183
|
+
() => binding.delete(app),
|
|
184
|
+
() => binding.delete(app),
|
|
185
|
+
);
|
|
186
|
+
ready = bound;
|
|
187
|
+
}
|
|
188
|
+
this[PENDING] = { server: app, protocol, path, ready };
|
|
189
|
+
// No port yet; `end` fills it in before anything is sent.
|
|
190
|
+
return `${protocol}://${LOOPBACK}${path}`;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
proto.end = function end(fn) {
|
|
194
|
+
const pending = this[PENDING];
|
|
195
|
+
if (!pending) return originalEnd.call(this, fn);
|
|
196
|
+
this[PENDING] = undefined;
|
|
197
|
+
|
|
198
|
+
const { server, protocol, path, ready } = pending;
|
|
199
|
+
let called = false;
|
|
200
|
+
const callback = (err: unknown, res?: unknown) => {
|
|
201
|
+
called = true;
|
|
202
|
+
// supertest 7.2 has closed the server it started by now, before the
|
|
203
|
+
// callback; 7.3 closes only servers in its own private registry, which
|
|
204
|
+
// this bind is not in. Close what this request started, if it is
|
|
205
|
+
// still open, before the callback — the order 7.2 kept.
|
|
206
|
+
if (this._server === server && server.listening) {
|
|
207
|
+
server.close(() => fn?.(err, res));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
fn?.(err, res);
|
|
211
|
+
};
|
|
212
|
+
// Unpatched, whatever `end` throws while building the request (a header
|
|
213
|
+
// value Node refuses, a body that will not serialize) reaches the caller
|
|
214
|
+
// at once — and `then` runs `end` inside a promise executor, so the
|
|
215
|
+
// awaited request rejects with it. Deferred, there is no caller left: the
|
|
216
|
+
// throw, like a failed bind, is handed to the request's callback, which
|
|
217
|
+
// is what rejects that promise. Nothing here may reject: a rejection of
|
|
218
|
+
// this chain is one nobody awaits.
|
|
219
|
+
const fail = (err: unknown) => {
|
|
220
|
+
if (called) return rethrowOutsideThisChain(err);
|
|
221
|
+
// The request never went out, so supertest will not close the server
|
|
222
|
+
// this request started.
|
|
223
|
+
if (this._server?.listening) this._server.close();
|
|
224
|
+
try {
|
|
225
|
+
callback(err);
|
|
226
|
+
} catch (thrown) {
|
|
227
|
+
rethrowOutsideThisChain(thrown);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
ready
|
|
231
|
+
.then(() => {
|
|
232
|
+
const { port } = server.address() as { port: number };
|
|
233
|
+
this.url = `${protocol}://${LOOPBACK}:${port}${path}`;
|
|
234
|
+
originalEnd.call(this, callback);
|
|
235
|
+
})
|
|
236
|
+
.catch(fail);
|
|
237
|
+
return this;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
proto[PATCHED] = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { testPath } = expect.getState();
|
|
244
|
+
if (!testPath) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
"vitest.supertest-loopback.mts: vitest did not say which test file this setup runs for, so the #556 loopback guard cannot find supertest.",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const proto = supertestFor(testPath);
|
|
250
|
+
if (proto) patch(proto);
|
package/templates/versions.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@o3co/auth-provider-core": "0.
|
|
3
|
-
"@o3co/auth-provider-device-grant": "0.
|
|
4
|
-
"@o3co/auth-provider-dpop": "0.
|
|
5
|
-
"@o3co/auth-provider-mtls": "0.
|
|
6
|
-
"@o3co/auth-provider-federation-apple": "0.
|
|
7
|
-
"@o3co/auth-provider-federation-google": "0.
|
|
8
|
-
"@o3co/auth-provider-federation-github": "0.
|
|
9
|
-
"@o3co/auth-provider-federation-
|
|
10
|
-
"@o3co/auth-provider-
|
|
11
|
-
"@o3co/auth-provider-oauth
|
|
12
|
-
"@o3co/auth-provider-
|
|
13
|
-
"@o3co/auth-provider-
|
|
14
|
-
"@o3co/auth-provider-
|
|
15
|
-
"@o3co/auth-provider-
|
|
2
|
+
"@o3co/auth-provider-core": "0.15.0",
|
|
3
|
+
"@o3co/auth-provider-device-grant": "0.15.0",
|
|
4
|
+
"@o3co/auth-provider-dpop": "0.15.0",
|
|
5
|
+
"@o3co/auth-provider-mtls": "0.15.0",
|
|
6
|
+
"@o3co/auth-provider-federation-apple": "0.15.0",
|
|
7
|
+
"@o3co/auth-provider-federation-google": "0.15.0",
|
|
8
|
+
"@o3co/auth-provider-federation-github": "0.15.0",
|
|
9
|
+
"@o3co/auth-provider-federation-grants": "0.15.0",
|
|
10
|
+
"@o3co/auth-provider-federation-oidc": "0.15.0",
|
|
11
|
+
"@o3co/auth-provider-oauth": "0.15.0",
|
|
12
|
+
"@o3co/auth-provider-oauth-token-exchange": "0.15.0",
|
|
13
|
+
"@o3co/auth-provider-session": "0.15.0",
|
|
14
|
+
"@o3co/auth-provider-foundation": "0.15.0",
|
|
15
|
+
"@o3co/auth-provider-redis": "0.15.0",
|
|
16
|
+
"@o3co/auth-provider-webauthn": "0.15.0"
|
|
16
17
|
}
|