@sdxc/well-known 0.0.0-pre.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.
@@ -0,0 +1,291 @@
1
+ /**
2
+ * The mapping tables and identity check the OAuth and OpenID Connect metadata
3
+ * documents share, so OpenID Connect Discovery extends the RFC 8414 members by
4
+ * spreading one table instead of repeating it.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { WellKnownParseError } from "../parse-error.js";
10
+ /** Every RFC 8414 member, by camelCase field. */
11
+ export declare const AUTHORIZATION_SERVER_FIELDS: {
12
+ issuer: {
13
+ wire: string;
14
+ kind: "text";
15
+ required: true;
16
+ };
17
+ authorizationEndpoint: {
18
+ wire: string;
19
+ kind: "url";
20
+ };
21
+ tokenEndpoint: {
22
+ wire: string;
23
+ kind: "url";
24
+ };
25
+ jwksUri: {
26
+ wire: string;
27
+ kind: "url";
28
+ };
29
+ registrationEndpoint: {
30
+ wire: string;
31
+ kind: "url";
32
+ };
33
+ revocationEndpoint: {
34
+ wire: string;
35
+ kind: "url";
36
+ };
37
+ introspectionEndpoint: {
38
+ wire: string;
39
+ kind: "url";
40
+ };
41
+ deviceAuthorizationEndpoint: {
42
+ wire: string;
43
+ kind: "url";
44
+ };
45
+ pushedAuthorizationRequestEndpoint: {
46
+ wire: string;
47
+ kind: "url";
48
+ };
49
+ scopesSupported: {
50
+ wire: string;
51
+ kind: "strings";
52
+ };
53
+ responseTypesSupported: {
54
+ wire: string;
55
+ kind: "strings";
56
+ required: true;
57
+ };
58
+ responseModesSupported: {
59
+ wire: string;
60
+ kind: "strings";
61
+ };
62
+ grantTypesSupported: {
63
+ wire: string;
64
+ kind: "strings";
65
+ };
66
+ tokenEndpointAuthMethodsSupported: {
67
+ wire: string;
68
+ kind: "strings";
69
+ };
70
+ tokenEndpointAuthSigningAlgValuesSupported: {
71
+ wire: string;
72
+ kind: "strings";
73
+ };
74
+ revocationEndpointAuthMethodsSupported: {
75
+ wire: string;
76
+ kind: "strings";
77
+ };
78
+ introspectionEndpointAuthMethodsSupported: {
79
+ wire: string;
80
+ kind: "strings";
81
+ };
82
+ codeChallengeMethodsSupported: {
83
+ wire: string;
84
+ kind: "strings";
85
+ };
86
+ uiLocalesSupported: {
87
+ wire: string;
88
+ kind: "strings";
89
+ };
90
+ serviceDocumentation: {
91
+ wire: string;
92
+ kind: "url";
93
+ };
94
+ opPolicyUri: {
95
+ wire: string;
96
+ kind: "url";
97
+ };
98
+ opTosUri: {
99
+ wire: string;
100
+ kind: "url";
101
+ };
102
+ authorizationResponseIssParameterSupported: {
103
+ wire: string;
104
+ kind: "boolean";
105
+ };
106
+ protectedResources: {
107
+ wire: string;
108
+ kind: "urls";
109
+ };
110
+ signedMetadata: {
111
+ wire: string;
112
+ kind: "text";
113
+ };
114
+ };
115
+ /**
116
+ * Adds the §3.3 check to a read: the published `issuer` must name the issuer the
117
+ * document was fetched for.
118
+ *
119
+ * @param expected - The issuer asked for, if the caller named one.
120
+ */
121
+ export declare function issuerCheck(expected: URL | string | undefined): (fields: Record<string, unknown>, issues: WellKnownParseError.Issue[]) => void;
122
+ /**
123
+ * OpenID Connect Discovery §3 members over the RFC 8414 ones. `authorization_endpoint`
124
+ * and `jwks_uri` become required, and `request_uri_parameter_supported` defaults to
125
+ * `true`, as §3 states for an absent member.
126
+ */
127
+ export declare const OPENID_PROVIDER_FIELDS: {
128
+ authorizationEndpoint: {
129
+ wire: string;
130
+ kind: "url";
131
+ required: true;
132
+ };
133
+ jwksUri: {
134
+ wire: string;
135
+ kind: "url";
136
+ required: true;
137
+ };
138
+ userinfoEndpoint: {
139
+ wire: string;
140
+ kind: "url";
141
+ };
142
+ endSessionEndpoint: {
143
+ wire: string;
144
+ kind: "url";
145
+ };
146
+ checkSessionIframe: {
147
+ wire: string;
148
+ kind: "url";
149
+ };
150
+ subjectTypesSupported: {
151
+ wire: string;
152
+ kind: "strings";
153
+ required: true;
154
+ };
155
+ idTokenSigningAlgValuesSupported: {
156
+ wire: string;
157
+ kind: "strings";
158
+ required: true;
159
+ };
160
+ claimsSupported: {
161
+ wire: string;
162
+ kind: "strings";
163
+ };
164
+ promptValuesSupported: {
165
+ wire: string;
166
+ kind: "strings";
167
+ };
168
+ acrValuesSupported: {
169
+ wire: string;
170
+ kind: "strings";
171
+ };
172
+ requestParameterSupported: {
173
+ wire: string;
174
+ kind: "boolean";
175
+ };
176
+ requestUriParameterSupported: {
177
+ wire: string;
178
+ kind: "boolean";
179
+ default: true;
180
+ };
181
+ frontchannelLogoutSupported: {
182
+ wire: string;
183
+ kind: "boolean";
184
+ };
185
+ frontchannelLogoutSessionSupported: {
186
+ wire: string;
187
+ kind: "boolean";
188
+ };
189
+ backchannelLogoutSupported: {
190
+ wire: string;
191
+ kind: "boolean";
192
+ };
193
+ backchannelLogoutSessionSupported: {
194
+ wire: string;
195
+ kind: "boolean";
196
+ };
197
+ issuer: {
198
+ wire: string;
199
+ kind: "text";
200
+ required: true;
201
+ };
202
+ tokenEndpoint: {
203
+ wire: string;
204
+ kind: "url";
205
+ };
206
+ registrationEndpoint: {
207
+ wire: string;
208
+ kind: "url";
209
+ };
210
+ revocationEndpoint: {
211
+ wire: string;
212
+ kind: "url";
213
+ };
214
+ introspectionEndpoint: {
215
+ wire: string;
216
+ kind: "url";
217
+ };
218
+ deviceAuthorizationEndpoint: {
219
+ wire: string;
220
+ kind: "url";
221
+ };
222
+ pushedAuthorizationRequestEndpoint: {
223
+ wire: string;
224
+ kind: "url";
225
+ };
226
+ scopesSupported: {
227
+ wire: string;
228
+ kind: "strings";
229
+ };
230
+ responseTypesSupported: {
231
+ wire: string;
232
+ kind: "strings";
233
+ required: true;
234
+ };
235
+ responseModesSupported: {
236
+ wire: string;
237
+ kind: "strings";
238
+ };
239
+ grantTypesSupported: {
240
+ wire: string;
241
+ kind: "strings";
242
+ };
243
+ tokenEndpointAuthMethodsSupported: {
244
+ wire: string;
245
+ kind: "strings";
246
+ };
247
+ tokenEndpointAuthSigningAlgValuesSupported: {
248
+ wire: string;
249
+ kind: "strings";
250
+ };
251
+ revocationEndpointAuthMethodsSupported: {
252
+ wire: string;
253
+ kind: "strings";
254
+ };
255
+ introspectionEndpointAuthMethodsSupported: {
256
+ wire: string;
257
+ kind: "strings";
258
+ };
259
+ codeChallengeMethodsSupported: {
260
+ wire: string;
261
+ kind: "strings";
262
+ };
263
+ uiLocalesSupported: {
264
+ wire: string;
265
+ kind: "strings";
266
+ };
267
+ serviceDocumentation: {
268
+ wire: string;
269
+ kind: "url";
270
+ };
271
+ opPolicyUri: {
272
+ wire: string;
273
+ kind: "url";
274
+ };
275
+ opTosUri: {
276
+ wire: string;
277
+ kind: "url";
278
+ };
279
+ authorizationResponseIssParameterSupported: {
280
+ wire: string;
281
+ kind: "boolean";
282
+ };
283
+ protectedResources: {
284
+ wire: string;
285
+ kind: "urls";
286
+ };
287
+ signedMetadata: {
288
+ wire: string;
289
+ kind: "text";
290
+ };
291
+ };
@@ -0,0 +1,103 @@
1
+ import { sameIdentifier } from "./identifier.js";
2
+ /** Every RFC 8414 member, by camelCase field. */
3
+ export const AUTHORIZATION_SERVER_FIELDS = {
4
+ issuer: { wire: "issuer", kind: "text", required: true },
5
+ authorizationEndpoint: { wire: "authorization_endpoint", kind: "url" },
6
+ tokenEndpoint: { wire: "token_endpoint", kind: "url" },
7
+ jwksUri: { wire: "jwks_uri", kind: "url" },
8
+ registrationEndpoint: { wire: "registration_endpoint", kind: "url" },
9
+ revocationEndpoint: { wire: "revocation_endpoint", kind: "url" },
10
+ introspectionEndpoint: { wire: "introspection_endpoint", kind: "url" },
11
+ deviceAuthorizationEndpoint: { wire: "device_authorization_endpoint", kind: "url" },
12
+ pushedAuthorizationRequestEndpoint: {
13
+ wire: "pushed_authorization_request_endpoint",
14
+ kind: "url",
15
+ },
16
+ scopesSupported: { wire: "scopes_supported", kind: "strings" },
17
+ responseTypesSupported: { wire: "response_types_supported", kind: "strings", required: true },
18
+ responseModesSupported: { wire: "response_modes_supported", kind: "strings" },
19
+ grantTypesSupported: { wire: "grant_types_supported", kind: "strings" },
20
+ tokenEndpointAuthMethodsSupported: {
21
+ wire: "token_endpoint_auth_methods_supported",
22
+ kind: "strings",
23
+ },
24
+ tokenEndpointAuthSigningAlgValuesSupported: {
25
+ wire: "token_endpoint_auth_signing_alg_values_supported",
26
+ kind: "strings",
27
+ },
28
+ revocationEndpointAuthMethodsSupported: {
29
+ wire: "revocation_endpoint_auth_methods_supported",
30
+ kind: "strings",
31
+ },
32
+ introspectionEndpointAuthMethodsSupported: {
33
+ wire: "introspection_endpoint_auth_methods_supported",
34
+ kind: "strings",
35
+ },
36
+ codeChallengeMethodsSupported: { wire: "code_challenge_methods_supported", kind: "strings" },
37
+ uiLocalesSupported: { wire: "ui_locales_supported", kind: "strings" },
38
+ serviceDocumentation: { wire: "service_documentation", kind: "url" },
39
+ opPolicyUri: { wire: "op_policy_uri", kind: "url" },
40
+ opTosUri: { wire: "op_tos_uri", kind: "url" },
41
+ authorizationResponseIssParameterSupported: {
42
+ wire: "authorization_response_iss_parameter_supported",
43
+ kind: "boolean",
44
+ },
45
+ protectedResources: { wire: "protected_resources", kind: "urls" },
46
+ signedMetadata: { wire: "signed_metadata", kind: "text" },
47
+ };
48
+ /**
49
+ * Adds the §3.3 check to a read: the published `issuer` must name the issuer the
50
+ * document was fetched for.
51
+ *
52
+ * @param expected - The issuer asked for, if the caller named one.
53
+ */
54
+ export function issuerCheck(expected) {
55
+ return (fields, issues) => {
56
+ if (expected === undefined)
57
+ return;
58
+ if (sameIdentifier(String(fields.issuer), expected))
59
+ return;
60
+ issues.push({
61
+ at: "/issuer",
62
+ message: `The document names issuer "${String(fields.issuer)}", not "${String(expected)}".`,
63
+ });
64
+ };
65
+ }
66
+ /**
67
+ * OpenID Connect Discovery §3 members over the RFC 8414 ones. `authorization_endpoint`
68
+ * and `jwks_uri` become required, and `request_uri_parameter_supported` defaults to
69
+ * `true`, as §3 states for an absent member.
70
+ */
71
+ export const OPENID_PROVIDER_FIELDS = {
72
+ ...AUTHORIZATION_SERVER_FIELDS,
73
+ authorizationEndpoint: { wire: "authorization_endpoint", kind: "url", required: true },
74
+ jwksUri: { wire: "jwks_uri", kind: "url", required: true },
75
+ userinfoEndpoint: { wire: "userinfo_endpoint", kind: "url" },
76
+ endSessionEndpoint: { wire: "end_session_endpoint", kind: "url" },
77
+ checkSessionIframe: { wire: "check_session_iframe", kind: "url" },
78
+ subjectTypesSupported: { wire: "subject_types_supported", kind: "strings", required: true },
79
+ idTokenSigningAlgValuesSupported: {
80
+ wire: "id_token_signing_alg_values_supported",
81
+ kind: "strings",
82
+ required: true,
83
+ },
84
+ claimsSupported: { wire: "claims_supported", kind: "strings" },
85
+ promptValuesSupported: { wire: "prompt_values_supported", kind: "strings" },
86
+ acrValuesSupported: { wire: "acr_values_supported", kind: "strings" },
87
+ requestParameterSupported: { wire: "request_parameter_supported", kind: "boolean" },
88
+ requestUriParameterSupported: {
89
+ wire: "request_uri_parameter_supported",
90
+ kind: "boolean",
91
+ default: true,
92
+ },
93
+ frontchannelLogoutSupported: { wire: "frontchannel_logout_supported", kind: "boolean" },
94
+ frontchannelLogoutSessionSupported: {
95
+ wire: "frontchannel_logout_session_supported",
96
+ kind: "boolean",
97
+ },
98
+ backchannelLogoutSupported: { wire: "backchannel_logout_supported", kind: "boolean" },
99
+ backchannelLogoutSessionSupported: {
100
+ wire: "backchannel_logout_session_supported",
101
+ kind: "boolean",
102
+ },
103
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A fetch-router middleware that answers the well-known names an app lists and passes
3
+ * every other path on, so the registry composes with the app's own routes, including
4
+ * app-specific names under `/.well-known/`.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { Middleware, RequestContext } from "remix/router";
10
+ import type { WellKnownFormat } from "./format.js";
11
+ import type { RespondOptions } from "./response.js";
12
+ /** What one name answers with; `null` falls through to the next middleware, usually a 404. */
13
+ export type WellKnownEntry = (ctx: RequestContext) => Response | null | Promise<Response | null>;
14
+ /**
15
+ * Serves a format's document, produced per request so tenant or clock data can vary.
16
+ * A `null` document falls through to the next middleware.
17
+ *
18
+ * @param format - The document's format.
19
+ * @param produce - Builds the document for the request.
20
+ * @param options - The cache policy and extra headers.
21
+ * @template Document - The format's document type.
22
+ * @example
23
+ * serve(securityTxt, () => SECURITY_TXT);
24
+ */
25
+ export declare function serve<Document>(format: WellKnownFormat<Document>, produce: (ctx: RequestContext) => Document | null | Promise<Document | null>, options?: Omit<RespondOptions, "request">): WellKnownEntry;
26
+ /**
27
+ * Answers `GET` and `HEAD` on `/.well-known/<name>` (and `/.well-known/<name>/<path>` for an
28
+ * inserted name) for the names given, `OPTIONS` for a CORS format, and a 405 with `Allow`
29
+ * for any other method. Every other path goes to `next()`.
30
+ *
31
+ * @param entries - Each name the app serves, mapped to what answers it.
32
+ * @example
33
+ * wellKnown({ "security.txt": serve(securityTxt, () => SECURITY_TXT) });
34
+ */
35
+ export declare function wellKnown(entries: Record<string, WellKnownEntry>): Middleware;
@@ -0,0 +1,88 @@
1
+ import { respond } from "./response.js";
2
+ /** The path prefix RFC 8615 reserves. */
3
+ const PREFIX = "/.well-known/";
4
+ /** The methods every name answers. */
5
+ const READ_METHODS = ["GET", "HEAD"];
6
+ /**
7
+ * What the middleware knows about an entry `serve` built: whether its format inserts
8
+ * the suffix (so a path may follow the name) and whether it answers CORS preflights.
9
+ */
10
+ const ENTRY_FORMATS = new WeakMap();
11
+ /**
12
+ * Serves a format's document, produced per request so tenant or clock data can vary.
13
+ * A `null` document falls through to the next middleware.
14
+ *
15
+ * @param format - The document's format.
16
+ * @param produce - Builds the document for the request.
17
+ * @param options - The cache policy and extra headers.
18
+ * @template Document - The format's document type.
19
+ * @example
20
+ * serve(securityTxt, () => SECURITY_TXT);
21
+ */
22
+ export function serve(format, produce, options = {}) {
23
+ let entry = async (ctx) => {
24
+ let document = await produce(ctx);
25
+ if (document === null)
26
+ return null;
27
+ return await respond(format, document, { ...options, request: ctx.request });
28
+ };
29
+ ENTRY_FORMATS.set(entry, { placement: format.placement, cors: format.cors });
30
+ return entry;
31
+ }
32
+ /**
33
+ * The entry a path names: the exact name, or, for a format that inserts its suffix
34
+ * before an identifier's path, the name followed by that path.
35
+ *
36
+ * @param entries - The names the app serves.
37
+ * @param suffix - The path after `/.well-known/`.
38
+ */
39
+ function findEntry(entries, suffix) {
40
+ for (let [name, entry] of Object.entries(entries)) {
41
+ if (suffix === name)
42
+ return entry;
43
+ if (suffix.startsWith(`${name}/`) && ENTRY_FORMATS.get(entry)?.placement === "insert")
44
+ return entry;
45
+ }
46
+ return null;
47
+ }
48
+ /**
49
+ * Answers `GET` and `HEAD` on `/.well-known/<name>` (and `/.well-known/<name>/<path>` for an
50
+ * inserted name) for the names given, `OPTIONS` for a CORS format, and a 405 with `Allow`
51
+ * for any other method. Every other path goes to `next()`.
52
+ *
53
+ * @param entries - Each name the app serves, mapped to what answers it.
54
+ * @example
55
+ * wellKnown({ "security.txt": serve(securityTxt, () => SECURITY_TXT) });
56
+ */
57
+ export function wellKnown(entries) {
58
+ return async (ctx, next) => {
59
+ let path = ctx.url.pathname;
60
+ if (!path.startsWith(PREFIX))
61
+ return next();
62
+ let entry = findEntry(entries, path.slice(PREFIX.length));
63
+ if (entry === null)
64
+ return next();
65
+ let cors = ENTRY_FORMATS.get(entry)?.cors ?? false;
66
+ let allow = cors ? [...READ_METHODS, "OPTIONS"] : READ_METHODS;
67
+ let method = ctx.method.toUpperCase();
68
+ if (method === "OPTIONS" && cors) {
69
+ return new Response(null, {
70
+ status: 204,
71
+ headers: {
72
+ Allow: allow.join(", "),
73
+ "Access-Control-Allow-Origin": "*",
74
+ "Access-Control-Allow-Methods": allow.join(", "),
75
+ },
76
+ });
77
+ }
78
+ if (!READ_METHODS.includes(method)) {
79
+ return new Response(null, { status: 405, headers: { Allow: allow.join(", ") } });
80
+ }
81
+ let response = await entry(ctx);
82
+ if (response === null)
83
+ return next();
84
+ if (method === "HEAD" && response.body !== null)
85
+ return new Response(null, response);
86
+ return response;
87
+ };
88
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * OAuth 2.0 Authorization Server Metadata (RFC 8414): the typed document, its reader
3
+ * with the §3.3 issuer check, its writer and `define`, so a server publishes and a
4
+ * client reads the same member names from one mapping table.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { Result } from "@sdxc/result";
10
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
11
+ import type { WellKnownFormat } from "./format.js";
12
+ import type { WellKnownParseError } from "./parse-error.js";
13
+ export declare const NAME = "oauth-authorization-server";
14
+ export declare const MEDIA_TYPE = "application/json";
15
+ /**
16
+ * RFC 8414 §2 plus the registry members an app in this repo publishes. `issuer` is a
17
+ * string because it is compared as published and carried verbatim in tokens' `iss`.
18
+ *
19
+ * @template Extensions - The members outside this list, validated by a schema on parse.
20
+ */
21
+ export interface AuthorizationServerMetadata<Extensions extends object = {}> {
22
+ issuer: string;
23
+ authorizationEndpoint: URL | null;
24
+ tokenEndpoint: URL | null;
25
+ jwksUri: URL | null;
26
+ registrationEndpoint: URL | null;
27
+ revocationEndpoint: URL | null;
28
+ introspectionEndpoint: URL | null;
29
+ /** RFC 8628. */
30
+ deviceAuthorizationEndpoint: URL | null;
31
+ /** RFC 9126. */
32
+ pushedAuthorizationRequestEndpoint: URL | null;
33
+ scopesSupported: string[];
34
+ responseTypesSupported: string[];
35
+ responseModesSupported: string[];
36
+ grantTypesSupported: string[];
37
+ tokenEndpointAuthMethodsSupported: string[];
38
+ tokenEndpointAuthSigningAlgValuesSupported: string[];
39
+ revocationEndpointAuthMethodsSupported: string[];
40
+ introspectionEndpointAuthMethodsSupported: string[];
41
+ codeChallengeMethodsSupported: string[];
42
+ uiLocalesSupported: string[];
43
+ serviceDocumentation: URL | null;
44
+ opPolicyUri: URL | null;
45
+ opTosUri: URL | null;
46
+ /** RFC 9207. */
47
+ authorizationResponseIssParameterSupported: boolean;
48
+ /** RFC 9728 §4. */
49
+ protectedResources: URL[];
50
+ /** A JWT carrying the metadata, kept opaque. */
51
+ signedMetadata: string | null;
52
+ extensions: Extensions;
53
+ }
54
+ /**
55
+ * How a metadata document is read.
56
+ *
57
+ * @template Extensions - The members the schema produces.
58
+ */
59
+ export interface ParseOptions<Extensions extends object> {
60
+ /** The issuer the document was fetched for; a different `issuer` fails (§3.3). */
61
+ issuer?: URL | string;
62
+ /** Validates the members outside the standard list; without one they stay unchecked. */
63
+ extensions?: StandardSchemaV1<unknown, Extensions>;
64
+ }
65
+ /**
66
+ * Reads authorization server metadata. Absent lists read as empty and absent URLs as
67
+ * `null`; members outside RFC 8414 land in `extensions`. A missing `issuer` or
68
+ * `response_types_supported`, a malformed member, or a different issuer fails.
69
+ *
70
+ * @param text - The served JSON.
71
+ * @param options - The expected issuer and an extension schema.
72
+ * @template Extensions - The extension members the schema produces.
73
+ * @example
74
+ * let metadata = parse(await response.text(), { issuer: "https://as.example" });
75
+ */
76
+ export declare function parse<Extensions extends object = Record<string, unknown>>(text: string, options?: ParseOptions<Extensions>): Result<AuthorizationServerMetadata<Extensions>, WellKnownParseError>;
77
+ /**
78
+ * Writes the document as its registered member names, leaving out `null` members,
79
+ * optional empty lists and `false` flags. `response_types_supported` is REQUIRED, so
80
+ * it is written even when empty and the output always passes {@link parse}.
81
+ *
82
+ * @param document - The metadata to publish.
83
+ */
84
+ export declare function stringify(document: AuthorizationServerMetadata<object>): string;
85
+ /**
86
+ * Builds a document from the members an app sets; lists default to empty, URLs to
87
+ * `null`, flags to `false` and extensions to none.
88
+ *
89
+ * @param document - The members the app publishes.
90
+ * @template Extensions - Members outside RFC 8414 the app also publishes.
91
+ * @example
92
+ * define({ issuer: "https://as.example", responseTypesSupported: ["code"] });
93
+ */
94
+ export declare function define<Extensions extends object = {}>(document: Pick<AuthorizationServerMetadata<Extensions>, "issuer" | "responseTypesSupported"> & Partial<AuthorizationServerMetadata<Extensions>>): AuthorizationServerMetadata<Extensions>;
95
+ /**
96
+ * RFC 8414 metadata as a servable format. Its `parse` checks no issuer; a client
97
+ * reading a fetched document calls `parse` with `issuer`.
98
+ */
99
+ export declare const authorizationServerMetadata: WellKnownFormat<AuthorizationServerMetadata<object>>;
@@ -0,0 +1,61 @@
1
+ import { defaults, readMetadata, writeMetadata } from "./lib/metadata.js";
2
+ import { AUTHORIZATION_SERVER_FIELDS, issuerCheck } from "./lib/oauth-fields.js";
3
+ export const NAME = "oauth-authorization-server";
4
+ export const MEDIA_TYPE = "application/json";
5
+ /**
6
+ * Reads authorization server metadata. Absent lists read as empty and absent URLs as
7
+ * `null`; members outside RFC 8414 land in `extensions`. A missing `issuer` or
8
+ * `response_types_supported`, a malformed member, or a different issuer fails.
9
+ *
10
+ * @param text - The served JSON.
11
+ * @param options - The expected issuer and an extension schema.
12
+ * @template Extensions - The extension members the schema produces.
13
+ * @example
14
+ * let metadata = parse(await response.text(), { issuer: "https://as.example" });
15
+ */
16
+ export function parse(text, options = {}) {
17
+ return readMetadata(text, {
18
+ format: NAME,
19
+ table: AUTHORIZATION_SERVER_FIELDS,
20
+ extensions: options.extensions,
21
+ check: issuerCheck(options.issuer),
22
+ });
23
+ }
24
+ /**
25
+ * Writes the document as its registered member names, leaving out `null` members,
26
+ * optional empty lists and `false` flags. `response_types_supported` is REQUIRED, so
27
+ * it is written even when empty and the output always passes {@link parse}.
28
+ *
29
+ * @param document - The metadata to publish.
30
+ */
31
+ export function stringify(document) {
32
+ return writeMetadata(document, AUTHORIZATION_SERVER_FIELDS);
33
+ }
34
+ /**
35
+ * Builds a document from the members an app sets; lists default to empty, URLs to
36
+ * `null`, flags to `false` and extensions to none.
37
+ *
38
+ * @param document - The members the app publishes.
39
+ * @template Extensions - Members outside RFC 8414 the app also publishes.
40
+ * @example
41
+ * define({ issuer: "https://as.example", responseTypesSupported: ["code"] });
42
+ */
43
+ export function define(document) {
44
+ return {
45
+ ...defaults(AUTHORIZATION_SERVER_FIELDS),
46
+ extensions: {},
47
+ ...document,
48
+ };
49
+ }
50
+ /**
51
+ * RFC 8414 metadata as a servable format. Its `parse` checks no issuer; a client
52
+ * reading a fetched document calls `parse` with `issuer`.
53
+ */
54
+ export const authorizationServerMetadata = {
55
+ name: NAME,
56
+ mediaType: MEDIA_TYPE,
57
+ placement: "insert",
58
+ cors: false,
59
+ stringify,
60
+ parse: (text) => parse(text),
61
+ };