@qauth-labs/plugin-sdk 0.0.0 → 0.2.0-rc.1
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 +201 -0
- package/README.md +148 -0
- package/dist/host.d.ts +24 -0
- package/dist/host.js +24 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +31 -0
- package/dist/lib/auth-method.d.ts +424 -0
- package/dist/lib/auth-method.js +1 -0
- package/dist/lib/context.d.ts +163 -0
- package/dist/lib/context.js +390 -0
- package/dist/lib/contract.d.ts +274 -0
- package/dist/lib/contract.js +285 -0
- package/dist/lib/credential-provider.d.ts +86 -0
- package/dist/lib/credential-provider.js +15 -0
- package/dist/lib/engine.d.ts +125 -0
- package/dist/lib/engine.js +367 -0
- package/dist/lib/errors.d.ts +49 -0
- package/dist/lib/errors.js +63 -0
- package/dist/lib/parameters.d.ts +21 -0
- package/dist/lib/parameters.js +62 -0
- package/dist/lib/validate.d.ts +55 -0
- package/dist/lib/validate.js +265 -0
- package/dist/testing/contract-kit.d.ts +116 -0
- package/dist/testing/contract-kit.js +526 -0
- package/dist/testing/fetch-guard.d.ts +38 -0
- package/dist/testing/fetch-guard.js +49 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.js +1 -0
- package/package.json +61 -11
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { AUTH_METHOD_BINDING_PARAMETER_PATTERN, AUTH_METHOD_CONTRACT, AUTH_METHOD_CONTRACT_NAME, AUTH_METHOD_DISPLAY_NAME_PATTERN, AUTH_METHOD_ICON_MEDIA_TYPES, AUTH_METHOD_ID_PATTERN, AUTH_METHOD_ROUTE_PATH_PATTERN, AUTH_METHOD_STEP_URNS, AUTH_METHOD_TYPE_PATTERN, MAX_AUTH_METHOD_DISPLAY_NAME_LENGTH, MAX_AUTH_METHOD_ICON_BYTES, RESERVED_AUTH_METHOD_TYPES, } from './contract.js';
|
|
2
|
+
import { AuthMethodContractError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The static checks of the AuthMethod contract (#599): what a method must be
|
|
5
|
+
* before the registry accepts it. The registry runs them at registration, so a
|
|
6
|
+
* method that fails one fails the boot. The contract test kit runs them too,
|
|
7
|
+
* then exercises the method.
|
|
8
|
+
*
|
|
9
|
+
* @experimental
|
|
10
|
+
*/
|
|
11
|
+
/** The eight bytes every PNG file starts with. */
|
|
12
|
+
const PNG_SIGNATURE = Uint8Array.of(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a);
|
|
13
|
+
/**
|
|
14
|
+
* Whether `provider` presents itself as an AuthMethod: it has a `contract`
|
|
15
|
+
* member. A plain `CredentialProvider` has none. The registry then holds it to
|
|
16
|
+
* the whole contract ({@link assertAuthMethod}), so a provider cannot carry a
|
|
17
|
+
* `contract` member and skip the checks.
|
|
18
|
+
*
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
export function declaresAuthMethodContract(provider) {
|
|
22
|
+
return 'contract' in provider;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A frozen snapshot of a method: each member read once, functions bound to
|
|
26
|
+
* the method, routes and steps copied. The registry validates and keeps the
|
|
27
|
+
* snapshot, so the `type` it checked, the routes it mounts and the members the
|
|
28
|
+
* engine reads cannot change afterwards, whatever getters the method has.
|
|
29
|
+
* Members that are not what the contract asks are copied as they are, for
|
|
30
|
+
* the validation to report.
|
|
31
|
+
*
|
|
32
|
+
* @experimental
|
|
33
|
+
*/
|
|
34
|
+
export function authMethodSnapshot(method) {
|
|
35
|
+
const member = method;
|
|
36
|
+
const bound = (value, self) => typeof value === 'function' ? value.bind(self) : value;
|
|
37
|
+
const display = member['display'];
|
|
38
|
+
const steps = member['steps'];
|
|
39
|
+
const routes = member['routes'];
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
contract: member['contract'],
|
|
42
|
+
type: member['type'],
|
|
43
|
+
id: member['id'],
|
|
44
|
+
display: typeof display === 'object' && display !== null ? displaySnapshot(display) : display,
|
|
45
|
+
configSchema: member['configSchema'],
|
|
46
|
+
steps: Array.isArray(steps) ? Object.freeze([...steps]) : steps,
|
|
47
|
+
routes: Array.isArray(routes)
|
|
48
|
+
? Object.freeze(routes.map((route) => {
|
|
49
|
+
if (typeof route !== 'object' || route === null)
|
|
50
|
+
return route;
|
|
51
|
+
const { kind, method: verb, path, handle, bindingParameter, } = route;
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
kind,
|
|
54
|
+
method: verb,
|
|
55
|
+
path,
|
|
56
|
+
handle: bound(handle, route),
|
|
57
|
+
...(bindingParameter === undefined ? {} : { bindingParameter }),
|
|
58
|
+
});
|
|
59
|
+
}))
|
|
60
|
+
: routes,
|
|
61
|
+
start: bound(member['start'], method),
|
|
62
|
+
verify: bound(member['verify'], method),
|
|
63
|
+
extractAttributes: bound(member['extractAttributes'], method),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function displaySnapshot(display) {
|
|
67
|
+
const { name, icon } = display;
|
|
68
|
+
if (typeof icon !== 'object' || icon === null) {
|
|
69
|
+
return Object.freeze(icon === undefined ? { name } : { name, icon });
|
|
70
|
+
}
|
|
71
|
+
const { mediaType, bytes } = icon;
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
name,
|
|
74
|
+
icon: Object.freeze({
|
|
75
|
+
mediaType,
|
|
76
|
+
bytes: bytes instanceof Uint8Array ? new Uint8Array(bytes) : bytes,
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether `value` meets the static checks of the contract.
|
|
82
|
+
*
|
|
83
|
+
* @experimental
|
|
84
|
+
*/
|
|
85
|
+
export function isAuthMethod(value) {
|
|
86
|
+
return authMethodProblems(value).length === 0;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Throw an {@link AuthMethodContractError} listing every problem when `value`
|
|
90
|
+
* does not meet the static checks of the contract.
|
|
91
|
+
*
|
|
92
|
+
* @experimental
|
|
93
|
+
*/
|
|
94
|
+
export function assertAuthMethod(value) {
|
|
95
|
+
const problems = authMethodProblems(value);
|
|
96
|
+
if (problems.length > 0) {
|
|
97
|
+
throw new AuthMethodContractError(describeMethod(value), problems);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Every static problem of `value` as an AuthMethod, one sentence each; empty
|
|
102
|
+
* when there is none.
|
|
103
|
+
*
|
|
104
|
+
* @experimental
|
|
105
|
+
*/
|
|
106
|
+
export function authMethodProblems(value) {
|
|
107
|
+
if (typeof value !== 'object' || value === null)
|
|
108
|
+
return ['it is not an object'];
|
|
109
|
+
const method = value;
|
|
110
|
+
const problems = [];
|
|
111
|
+
problems.push(...contractProblems(method['contract']));
|
|
112
|
+
problems.push(...authMethodTypeProblems(method['type']));
|
|
113
|
+
if (typeof method['id'] !== 'string' || !AUTH_METHOD_ID_PATTERN.test(method['id'])) {
|
|
114
|
+
problems.push(`id must match ${AUTH_METHOD_ID_PATTERN.source}`);
|
|
115
|
+
}
|
|
116
|
+
for (const member of ['verify', 'extractAttributes', 'start']) {
|
|
117
|
+
if (typeof method[member] !== 'function')
|
|
118
|
+
problems.push(`${member} must be a function`);
|
|
119
|
+
}
|
|
120
|
+
problems.push(...displayProblems(method['display']));
|
|
121
|
+
const schema = method['configSchema'];
|
|
122
|
+
if (typeof schema !== 'object' ||
|
|
123
|
+
schema === null ||
|
|
124
|
+
typeof schema['safeParse'] !== 'function') {
|
|
125
|
+
problems.push('configSchema must be a Zod schema');
|
|
126
|
+
}
|
|
127
|
+
problems.push(...stepProblems(method['steps']));
|
|
128
|
+
problems.push(...routeProblems(method['routes']));
|
|
129
|
+
return problems;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The problems of a route list; exported for the mount, which checks again.
|
|
133
|
+
*
|
|
134
|
+
* @experimental
|
|
135
|
+
*/
|
|
136
|
+
export function routeProblems(routes) {
|
|
137
|
+
if (!Array.isArray(routes))
|
|
138
|
+
return ['routes must be a list'];
|
|
139
|
+
const problems = [];
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
let callbacks = 0;
|
|
142
|
+
routes.forEach((route, index) => {
|
|
143
|
+
const at = `routes[${index}]`;
|
|
144
|
+
if (typeof route !== 'object' || route === null) {
|
|
145
|
+
problems.push(`${at} must be an object`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const entry = route;
|
|
149
|
+
if (entry['kind'] !== 'callback' && entry['kind'] !== 'bounce') {
|
|
150
|
+
problems.push(`${at}.kind must be callback or bounce`);
|
|
151
|
+
}
|
|
152
|
+
if (entry['kind'] === 'callback')
|
|
153
|
+
callbacks += 1;
|
|
154
|
+
if (entry['method'] !== 'GET' && entry['method'] !== 'POST') {
|
|
155
|
+
problems.push(`${at}.method must be GET or POST`);
|
|
156
|
+
}
|
|
157
|
+
const path = entry['path'];
|
|
158
|
+
if (typeof path !== 'string' || !AUTH_METHOD_ROUTE_PATH_PATTERN.test(path)) {
|
|
159
|
+
problems.push(`${at}.path must be one to four lower-case segments, such as /callback: a route cannot leave the method's prefix`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const key = `${String(entry['method'])} ${path}`;
|
|
163
|
+
if (seen.has(key))
|
|
164
|
+
problems.push(`${at} repeats ${key}`);
|
|
165
|
+
seen.add(key);
|
|
166
|
+
}
|
|
167
|
+
if (typeof entry['handle'] !== 'function')
|
|
168
|
+
problems.push(`${at}.handle must be a function`);
|
|
169
|
+
const bindingParameter = entry['bindingParameter'];
|
|
170
|
+
if (bindingParameter !== undefined &&
|
|
171
|
+
(entry['kind'] !== 'callback' ||
|
|
172
|
+
typeof bindingParameter !== 'string' ||
|
|
173
|
+
!AUTH_METHOD_BINDING_PARAMETER_PATTERN.test(bindingParameter))) {
|
|
174
|
+
problems.push(`${at}.bindingParameter must be the name of a parameter (${AUTH_METHOD_BINDING_PARAMETER_PATTERN.source}), on a callback route only`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (callbacks === 0) {
|
|
178
|
+
problems.push('routes must hold at least one callback route: the upstream step returns there');
|
|
179
|
+
}
|
|
180
|
+
return problems;
|
|
181
|
+
}
|
|
182
|
+
function contractProblems(contract) {
|
|
183
|
+
if (contract === AUTH_METHOD_CONTRACT)
|
|
184
|
+
return [];
|
|
185
|
+
if (typeof contract === 'string' && contract.startsWith(`${AUTH_METHOD_CONTRACT_NAME}/`)) {
|
|
186
|
+
return [
|
|
187
|
+
`it is written against the contract ${contract}, and this server implements ${AUTH_METHOD_CONTRACT}`,
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
return [`contract must be ${AUTH_METHOD_CONTRACT}`];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The problems of `type` as an AuthMethod's type: its pattern, and not one of
|
|
194
|
+
* the reserved types.
|
|
195
|
+
*
|
|
196
|
+
* @experimental
|
|
197
|
+
*/
|
|
198
|
+
export function authMethodTypeProblems(type) {
|
|
199
|
+
if (typeof type !== 'string' || !AUTH_METHOD_TYPE_PATTERN.test(type)) {
|
|
200
|
+
return [`type must match ${AUTH_METHOD_TYPE_PATTERN.source}`];
|
|
201
|
+
}
|
|
202
|
+
if (RESERVED_AUTH_METHOD_TYPES.includes(type)) {
|
|
203
|
+
return [`type ${type} is reserved for QAuth's own credentials`];
|
|
204
|
+
}
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
function displayProblems(display) {
|
|
208
|
+
if (typeof display !== 'object' || display === null)
|
|
209
|
+
return ['display must be an object'];
|
|
210
|
+
const { name, icon } = display;
|
|
211
|
+
const problems = [];
|
|
212
|
+
if (typeof name !== 'string' ||
|
|
213
|
+
name.length > MAX_AUTH_METHOD_DISPLAY_NAME_LENGTH ||
|
|
214
|
+
!AUTH_METHOD_DISPLAY_NAME_PATTERN.test(name)) {
|
|
215
|
+
problems.push(`display.name must be at most ${MAX_AUTH_METHOD_DISPLAY_NAME_LENGTH} characters under the display rule: no markup bracket, no control or invisible character, no space at either end`);
|
|
216
|
+
}
|
|
217
|
+
if (icon !== undefined)
|
|
218
|
+
problems.push(...iconProblems(icon));
|
|
219
|
+
return problems;
|
|
220
|
+
}
|
|
221
|
+
function iconProblems(icon) {
|
|
222
|
+
if (typeof icon !== 'object' || icon === null)
|
|
223
|
+
return ['display.icon must be an object'];
|
|
224
|
+
const { mediaType, bytes } = icon;
|
|
225
|
+
const problems = [];
|
|
226
|
+
if (!AUTH_METHOD_ICON_MEDIA_TYPES.includes(mediaType)) {
|
|
227
|
+
problems.push(`display.icon.mediaType must be ${AUTH_METHOD_ICON_MEDIA_TYPES.join(' or ')}`);
|
|
228
|
+
}
|
|
229
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
230
|
+
problems.push('display.icon.bytes must be a Uint8Array');
|
|
231
|
+
}
|
|
232
|
+
else if (bytes.byteLength > MAX_AUTH_METHOD_ICON_BYTES) {
|
|
233
|
+
problems.push(`display.icon.bytes must be at most ${MAX_AUTH_METHOD_ICON_BYTES} bytes`);
|
|
234
|
+
}
|
|
235
|
+
else if (!PNG_SIGNATURE.every((byte, index) => bytes[index] === byte)) {
|
|
236
|
+
problems.push('display.icon.bytes must be a PNG file');
|
|
237
|
+
}
|
|
238
|
+
return problems;
|
|
239
|
+
}
|
|
240
|
+
function stepProblems(steps) {
|
|
241
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
242
|
+
return ['steps must list at least one step'];
|
|
243
|
+
}
|
|
244
|
+
const problems = [];
|
|
245
|
+
const seen = new Set();
|
|
246
|
+
for (const step of steps) {
|
|
247
|
+
if (seen.has(step))
|
|
248
|
+
problems.push(`steps repeats ${String(step)}`);
|
|
249
|
+
seen.add(step);
|
|
250
|
+
if (AUTH_METHOD_STEP_URNS.includes(step))
|
|
251
|
+
continue;
|
|
252
|
+
problems.push(`steps holds ${String(step)}, which is not a step a method may contribute here (${AUTH_METHOD_STEP_URNS.join(', ')})`);
|
|
253
|
+
}
|
|
254
|
+
return problems;
|
|
255
|
+
}
|
|
256
|
+
/** How a candidate is named in an error: its type, when it has a usable one. */
|
|
257
|
+
function describeMethod(value) {
|
|
258
|
+
if (typeof value === 'object' && value !== null) {
|
|
259
|
+
const type = value['type'];
|
|
260
|
+
if (typeof type === 'string' && AUTH_METHOD_TYPE_PATTERN.test(type)) {
|
|
261
|
+
return `The AuthMethod ${type}`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return 'An AuthMethod';
|
|
265
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { AuthMethod, AuthMethodEgressRequest, AuthMethodEgressResponse, AuthMethodParameters } from '../lib/auth-method.js';
|
|
2
|
+
/**
|
|
3
|
+
* The contract test kit (#599): one function a method's author calls, in any
|
|
4
|
+
* test runner, to check the method against the AuthMethod contract. It is
|
|
5
|
+
* test support, served from the `@qauth-labs/plugin-sdk/testing` entry point,
|
|
6
|
+
* never from the main one, and it refuses to run under `NODE_ENV=production`: while it runs, it
|
|
7
|
+
* replaces the global `fetch`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { checkAuthMethod } from '@qauth-labs/plugin-sdk/testing';
|
|
11
|
+
*
|
|
12
|
+
* it('meets the AuthMethod contract', async () => {
|
|
13
|
+
* await checkAuthMethod(myMethod, {
|
|
14
|
+
* config: { clientId: 'kit', clientSecret: 'kit-secret' },
|
|
15
|
+
* egress: (request) => myFakeProvider.answer(request),
|
|
16
|
+
* callback: (authorizationRequest) => myFakeProvider.signIn(authorizationRequest),
|
|
17
|
+
* });
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* It resolves with the checks it ran, and rejects with an
|
|
22
|
+
* {@link AuthMethodContractError} that lists every check the method failed.
|
|
23
|
+
*
|
|
24
|
+
* EXPERIMENTAL, like the contract it checks.
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* A request the provider sends the browser back with, to one of the method's
|
|
30
|
+
* routes.
|
|
31
|
+
*
|
|
32
|
+
* @experimental
|
|
33
|
+
*/
|
|
34
|
+
export interface AuthMethodKitRequest {
|
|
35
|
+
/** The route's path below the method's prefix: `/callback`. */
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly method: 'GET' | 'POST';
|
|
38
|
+
readonly query?: AuthMethodParameters;
|
|
39
|
+
readonly form?: AuthMethodParameters;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* How the provider behaves in the kit's run: the method's author plays it.
|
|
43
|
+
*
|
|
44
|
+
* @experimental
|
|
45
|
+
*/
|
|
46
|
+
export interface AuthMethodKitScenario {
|
|
47
|
+
/**
|
|
48
|
+
* A config for one realm, which the method's `configSchema` accepts. Give
|
|
49
|
+
* its `writeOnly` members realistic secrets (a dozen characters or more):
|
|
50
|
+
* the kit looks for every one of them, however short, in every log line,
|
|
51
|
+
* and a secret such as `abc` matches text that is no leak.
|
|
52
|
+
*/
|
|
53
|
+
readonly config: unknown;
|
|
54
|
+
/**
|
|
55
|
+
* Answer the method's outbound calls as the provider would. Without it,
|
|
56
|
+
* every call the method makes is refused.
|
|
57
|
+
*/
|
|
58
|
+
egress?(request: AuthMethodEgressRequest): AuthMethodEgressResponse | Promise<AuthMethodEgressResponse>;
|
|
59
|
+
/**
|
|
60
|
+
* The request the browser comes back with once the person signed in at the
|
|
61
|
+
* provider, for the authorization request `start()` sent it to. Each call
|
|
62
|
+
* signs the same person in.
|
|
63
|
+
*/
|
|
64
|
+
callback(authorizationRequest: URL): AuthMethodKitRequest | Promise<AuthMethodKitRequest>;
|
|
65
|
+
/**
|
|
66
|
+
* The request the browser comes back with when the person declines at the
|
|
67
|
+
* provider (`error=access_denied`). Checked when given.
|
|
68
|
+
*/
|
|
69
|
+
declined?(authorizationRequest: URL): AuthMethodKitRequest | Promise<AuthMethodKitRequest>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* What a passing run checked, in order.
|
|
73
|
+
*
|
|
74
|
+
* @experimental
|
|
75
|
+
*/
|
|
76
|
+
export interface AuthMethodKitReport {
|
|
77
|
+
readonly checks: readonly string[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check `method` against the AuthMethod contract.
|
|
81
|
+
*
|
|
82
|
+
* Static checks first: the contract version, `type`, `id`, the display data,
|
|
83
|
+
* the config schema (a Zod object schema that renders as JSON Schema and
|
|
84
|
+
* accepts `scenario.config`), the steps and the routes (each inside the
|
|
85
|
+
* method's prefix). Then a run against a fake realm, with the global `fetch`
|
|
86
|
+
* disabled:
|
|
87
|
+
*
|
|
88
|
+
* 1. `start()` answers an `https` redirect with no credentials and no fragment,
|
|
89
|
+
* a transaction of JSON data within its cap and a binding of 22 to 512
|
|
90
|
+
* base64url characters; two starts answer different transactions.
|
|
91
|
+
* 2. Two starts answer different bindings, and each sends its binding to the
|
|
92
|
+
* provider, in the redirect or in a body it posted.
|
|
93
|
+
* 3. The provider's callback carries the binding back in the callback
|
|
94
|
+
* route's `bindingParameter` (`state` by default): the kit, like the host,
|
|
95
|
+
* hands the handler the transaction only when it does. It ends in an
|
|
96
|
+
* identity whose `rawClaims.iss` names its issuer and whose attributes
|
|
97
|
+
* carry the method's `type`; a second sign-in of the same person resolves
|
|
98
|
+
* the same `externalSub`.
|
|
99
|
+
* 4. When the authorization request asks for a code (`response_type=code`,
|
|
100
|
+
* in the redirect or a body the method posted), it carries
|
|
101
|
+
* `code_challenge_method=S256` and a `code_challenge` (RFC 7636; QAuth
|
|
102
|
+
* requires PKCE with S256), and every token request the callback makes
|
|
103
|
+
* (`grant_type=authorization_code`) carries the `code_verifier` whose S256
|
|
104
|
+
* is that challenge.
|
|
105
|
+
* 5. `verify()` rejects inputs that no callback produced, a well-formed
|
|
106
|
+
* identity, a copy of a real result and a result already used among them.
|
|
107
|
+
* 6. When the scenario gives one, the declined callback ends as `declined`.
|
|
108
|
+
* 7. The method called the global `fetch` nowhere, and no log line carries
|
|
109
|
+
* the value of a config member marked `writeOnly` (a secret), in a field,
|
|
110
|
+
* a message or an error.
|
|
111
|
+
*
|
|
112
|
+
* @throws AuthMethodContractError listing every failed check
|
|
113
|
+
* @throws Error when `NODE_ENV` is `production`
|
|
114
|
+
* @experimental
|
|
115
|
+
*/
|
|
116
|
+
export declare function checkAuthMethod(method: AuthMethod, scenario: AuthMethodKitScenario): Promise<AuthMethodKitReport>;
|