@c9up/visa 0.1.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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/VisaManager.d.ts +94 -0
  4. package/dist/VisaManager.d.ts.map +1 -0
  5. package/dist/VisaManager.js +216 -0
  6. package/dist/VisaManager.js.map +1 -0
  7. package/dist/VisaProvider.d.ts +41 -0
  8. package/dist/VisaProvider.d.ts.map +1 -0
  9. package/dist/VisaProvider.js +153 -0
  10. package/dist/VisaProvider.js.map +1 -0
  11. package/dist/augmentations.d.ts +25 -0
  12. package/dist/augmentations.d.ts.map +1 -0
  13. package/dist/augmentations.js +19 -0
  14. package/dist/augmentations.js.map +1 -0
  15. package/dist/authorize.d.ts +78 -0
  16. package/dist/authorize.d.ts.map +1 -0
  17. package/dist/authorize.js +149 -0
  18. package/dist/authorize.js.map +1 -0
  19. package/dist/clients.d.ts +70 -0
  20. package/dist/clients.d.ts.map +1 -0
  21. package/dist/clients.js +177 -0
  22. package/dist/clients.js.map +1 -0
  23. package/dist/config.d.ts +30 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +8 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/configure.d.ts +28 -0
  28. package/dist/configure.d.ts.map +1 -0
  29. package/dist/configure.js +24 -0
  30. package/dist/configure.js.map +1 -0
  31. package/dist/crypto.d.ts +35 -0
  32. package/dist/crypto.d.ts.map +1 -0
  33. package/dist/crypto.js +56 -0
  34. package/dist/crypto.js.map +1 -0
  35. package/dist/errors.d.ts +47 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +50 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/index.d.ts +25 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +23 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/introspect.d.ts +32 -0
  44. package/dist/introspect.d.ts.map +1 -0
  45. package/dist/introspect.js +110 -0
  46. package/dist/introspect.js.map +1 -0
  47. package/dist/services/main.d.ts +29 -0
  48. package/dist/services/main.d.ts.map +1 -0
  49. package/dist/services/main.js +48 -0
  50. package/dist/services/main.js.map +1 -0
  51. package/dist/store.d.ts +44 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +12 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/stores/memory.d.ts +34 -0
  56. package/dist/stores/memory.d.ts.map +1 -0
  57. package/dist/stores/memory.js +100 -0
  58. package/dist/stores/memory.js.map +1 -0
  59. package/dist/stubs.d.ts +14 -0
  60. package/dist/stubs.d.ts.map +1 -0
  61. package/dist/stubs.js +15 -0
  62. package/dist/stubs.js.map +1 -0
  63. package/dist/testing/main.d.ts +49 -0
  64. package/dist/testing/main.d.ts.map +1 -0
  65. package/dist/testing/main.js +77 -0
  66. package/dist/testing/main.js.map +1 -0
  67. package/dist/token.d.ts +20 -0
  68. package/dist/token.d.ts.map +1 -0
  69. package/dist/token.js +212 -0
  70. package/dist/token.js.map +1 -0
  71. package/dist/types.d.ts +123 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +9 -0
  74. package/dist/types.js.map +1 -0
  75. package/package.json +83 -0
  76. package/src/VisaManager.ts +346 -0
  77. package/src/VisaProvider.ts +197 -0
  78. package/src/augmentations.ts +29 -0
  79. package/src/authorize.ts +250 -0
  80. package/src/clients.ts +231 -0
  81. package/src/config.ts +34 -0
  82. package/src/configure.ts +42 -0
  83. package/src/crypto.ts +59 -0
  84. package/src/errors.ts +79 -0
  85. package/src/index.ts +67 -0
  86. package/src/introspect.ts +139 -0
  87. package/src/services/main.ts +57 -0
  88. package/src/store.ts +56 -0
  89. package/src/stores/memory.ts +118 -0
  90. package/src/stubs.ts +16 -0
  91. package/src/testing/main.ts +119 -0
  92. package/src/token.ts +325 -0
  93. package/src/types.ts +134 -0
  94. package/stubs/config/visa.stub +25 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C9up
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,156 @@
1
+ # @c9up/visa
2
+
3
+ An OAuth 2.1 authorization server for the Ream ecosystem. It issues tokens to
4
+ **other** applications on a user's behalf.
5
+
6
+ It does not authenticate anyone — that stays warden's job. Visa asks your
7
+ application who is signed in, and everything else is its business.
8
+
9
+ No dependencies: `node:crypto` only. A package in the path that mints and
10
+ compares credentials is a package that can replace them.
11
+
12
+ ```bash
13
+ ream configure @c9up/visa
14
+ ```
15
+
16
+ ## What it implements
17
+
18
+ | | |
19
+ |---|---|
20
+ | Grants | `authorization_code` (PKCE required), `refresh_token`, `client_credentials` |
21
+ | Endpoints | `/oauth/token`, `/oauth/revoke`, `/oauth/introspect`, `/.well-known/oauth-authorization-server` |
22
+ | Removed | `implicit` and `password` — gone from OAuth 2.1, and not available under any option |
23
+
24
+ `/authorize` is **not** mounted, deliberately: it needs a signed-in user and a
25
+ consent screen, and both belong to your application. See below.
26
+
27
+ ## Security decisions, and why
28
+
29
+ - **PKCE on every client**, confidential ones included — a code on the front
30
+ channel is interceptable whoever asked for it.
31
+ - **`S256` only.** `plain` puts the verifier in the authorization request, so
32
+ anything that can read that request can complete the exchange, which is what
33
+ PKCE exists to stop. `allowPlainChallenge: true` brings it back; don't.
34
+ - **Exact redirect URI matching**, with the one exception the spec names (a
35
+ loopback port). A prefix match is how an open redirect on the client's own
36
+ domain becomes a stolen code.
37
+ - **An error is never redirected to an unverified URI.** A bad `redirect_uri`
38
+ is shown to the user, not bounced.
39
+ - **Everything is hashed at rest** — codes, access tokens, refresh tokens,
40
+ client secrets. A database dump is not a set of working credentials.
41
+ - **Refresh rotation with replay detection.** Every use mints a new token; a
42
+ spent one coming back proves a leak, and since there is no telling the thief
43
+ from the victim, the whole family goes.
44
+ - **One sentence per failure.** "No such code", "expired", "already used" and
45
+ "wrong client" are `invalid_grant: The code is not valid.` — telling them
46
+ apart is how a code space gets probed.
47
+
48
+ ## Wiring `/authorize`
49
+
50
+ The decision is visa's; the page is yours.
51
+
52
+ ```ts
53
+ // start/routes.ts
54
+ import visa from '@c9up/visa/services/main'
55
+
56
+ router.get('/oauth/authorize', async (ctx) => {
57
+ const outcome = await visa.authorize(ctx.request.qs(), ctx.auth.user?.id)
58
+
59
+ if (outcome.type === 'redirect') return ctx.response.redirect(outcome.url)
60
+ if (outcome.type === 'error') {
61
+ // NOT a redirect: the redirect URI is what failed validation.
62
+ return ctx.view.render('oauth/error', { error: outcome.error.toResponse() })
63
+ }
64
+ if (!ctx.auth.user) return ctx.response.redirect('/login?next=' + encodeURIComponent(ctx.request.url(true)))
65
+
66
+ return ctx.view.render('oauth/consent', {
67
+ client: outcome.request.client,
68
+ scopes: outcome.request.scopes,
69
+ })
70
+ })
71
+
72
+ router.post('/oauth/consent', async (ctx) => {
73
+ const outcome = await visa.authorize(ctx.request.all(), ctx.auth.user.id)
74
+ if (outcome.type !== 'consent') return ctx.response.redirect('/')
75
+ const url = ctx.request.input('approve')
76
+ ? await visa.grant(outcome.request, ctx.auth.user.id)
77
+ : visa.deny(outcome.request)
78
+ return ctx.response.redirect(url)
79
+ })
80
+ ```
81
+
82
+ ## Registering a client
83
+
84
+ ```ts
85
+ const { client, secret } = await visa.registerClient({
86
+ id: 'invoices',
87
+ name: 'Invoices',
88
+ redirectUris: ['https://invoices.example.com/callback'],
89
+ scopes: ['profile', 'invoices:read'],
90
+ })
91
+ // `secret` is shown once and never stored in plaintext. A client that loses
92
+ // it gets a new one.
93
+ ```
94
+
95
+ A public client — a SPA, a native app — registers with
96
+ `tokenEndpointAuthMethod: 'none'` and gets no secret. It may not use
97
+ `client_credentials`: "the client itself" means nothing when anyone can read
98
+ its id out of a browser.
99
+
100
+ ## Protecting a resource
101
+
102
+ ```ts
103
+ const grant = await visa.verify(bearerToken)
104
+ if (!grant) return ctx.response.unauthorized({ error: 'invalid_token' })
105
+ if (!grant.scopes.includes('invoices:read')) {
106
+ return ctx.response.forbidden({ error: 'insufficient_scope' })
107
+ }
108
+ ```
109
+
110
+ ## Testing
111
+
112
+ `@c9up/visa/testing` hands back the REAL server on a memory store, with a
113
+ client already registered:
114
+
115
+ ```ts
116
+ import { testVisa } from '@c9up/visa/testing'
117
+
118
+ const t = await testVisa({ scopes: ['profile', 'invoices:read'] })
119
+ const tokens = await t.tokensFor('user-7', 'invoices:read')
120
+ const grant = await t.visa.verify(tokens.access_token)
121
+ ```
122
+
123
+ `tokensFor()` walks authorize → consent → code → exchange rather than minting a
124
+ token directly. Not a lenient double on purpose: a helper that granted whatever
125
+ was asked would teach applications to ship a consent screen nobody has seen
126
+ refuse.
127
+
128
+ ## The issuer
129
+
130
+ It identifies the SERVER, and that is all it does here. An access token is
131
+ opaque — a random string with no claims inside — so nothing is "bound" to an
132
+ issuer cryptographically; the only thing that can vouch for such a token is the
133
+ server that minted it. That is why `/oauth/introspect` answers with `iss`, and
134
+ why the metadata document names it.
135
+
136
+ It is validated at boot rather than at the first request: it must be a URL,
137
+ `https` (localhost excepted), with no query string and no fragment, and a
138
+ trailing slash is dropped — every endpoint is built by concatenation, so
139
+ `https://auth.test/` would otherwise produce `https://auth.test//oauth/token`.
140
+
141
+ ## The store
142
+
143
+ `MemoryStore` is for tests and a single development process. Everything else
144
+ implements `VisaStore` — twelve methods. Two of them, `consumeAuthorizationCode`
145
+ and `consumeRefreshToken`, **must be atomic**: two requests racing with the same
146
+ code must not both succeed, and that single-use guarantee is what replay
147
+ detection is built on.
148
+
149
+ ## What is not here yet
150
+
151
+ OpenID Connect — `id_token`, discovery, JWKS, `/userinfo`. The `nonce` is
152
+ already carried through the authorization code for it.
153
+
154
+ ## License
155
+
156
+ MIT
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The server, assembled.
3
+ *
4
+ * Transport-agnostic on purpose: every method takes plain values and returns
5
+ * plain values, so the same object is driven by a ream route, by a test, or by
6
+ * a host that is not ream at all. The HTTP binding lives in the provider.
7
+ */
8
+ import { type AuthorizationRequest, type AuthorizeOptions, type ValidatedRequest } from "./authorize.js";
9
+ import { type ClientCredentials } from "./clients.js";
10
+ import { OAuthError } from "./errors.js";
11
+ import type { VisaStore } from "./store.js";
12
+ import { type TokenOptions } from "./token.js";
13
+ import type { Client, ClientAuthMethod, GrantType, IntrospectionResponse, TokenResponse } from "./types.js";
14
+ export interface VisaConfig extends AuthorizeOptions, TokenOptions {
15
+ /** Where this server lives — `https://auth.example.com`. */
16
+ issuer: string;
17
+ store: VisaStore;
18
+ }
19
+ /** What `authorize()` decided. */
20
+ export type AuthorizationOutcome =
21
+ /** Send the user here. */
22
+ {
23
+ type: "redirect";
24
+ url: string;
25
+ }
26
+ /** Ask first: this user has not agreed to these scopes for this client. */
27
+ | {
28
+ type: "consent";
29
+ request: ValidatedRequest;
30
+ }
31
+ /**
32
+ * Show the user this error. It must NOT be redirected — the redirect URI
33
+ * is the thing that failed validation.
34
+ */
35
+ | {
36
+ type: "error";
37
+ error: OAuthError;
38
+ };
39
+ export declare class VisaManager {
40
+ #private;
41
+ constructor(config: VisaConfig);
42
+ get issuer(): string;
43
+ get store(): VisaStore;
44
+ /**
45
+ * Register a client and hand back its secret ONCE.
46
+ *
47
+ * The plaintext is never stored and cannot be recovered; a client that
48
+ * loses it gets a new one. That is not an inconvenience to work around —
49
+ * it is the reason a database dump is not a set of working credentials.
50
+ */
51
+ registerClient(input: {
52
+ id: string;
53
+ name: string;
54
+ redirectUris: string[];
55
+ grantTypes?: GrantType[];
56
+ scopes?: string[];
57
+ tokenEndpointAuthMethod?: ClientAuthMethod;
58
+ trusted?: boolean;
59
+ }): Promise<{
60
+ client: Client;
61
+ secret?: string;
62
+ }>;
63
+ /**
64
+ * Step one: check the request, and decide what happens to the user.
65
+ *
66
+ * `userId` is whoever is signed in — warden's business, not visa's. Pass
67
+ * `undefined` and the caller is told to authenticate first.
68
+ */
69
+ authorize(request: AuthorizationRequest, userId: string | undefined): Promise<AuthorizationOutcome>;
70
+ /**
71
+ * Step two: the user said yes. Record it and build the redirect.
72
+ *
73
+ * Split from `authorize` because the consent screen sits between them, and
74
+ * the answer to it is a POST the application owns.
75
+ */
76
+ grant(validated: ValidatedRequest, userId: string, now?: Date): Promise<string>;
77
+ /** The user said no. */
78
+ deny(validated: ValidatedRequest): string;
79
+ token(body: Record<string, unknown>, credentials: ClientCredentials, now?: Date): Promise<TokenResponse>;
80
+ introspect(body: Record<string, unknown>, credentials: ClientCredentials, now?: Date): Promise<IntrospectionResponse>;
81
+ revoke(body: Record<string, unknown>, credentials: ClientCredentials, now?: Date): Promise<void>;
82
+ /** What a resource server calls on every request. */
83
+ verify(presented: string, now?: Date): Promise<{
84
+ clientId: string;
85
+ userId?: string;
86
+ scopes: string[];
87
+ } | null>;
88
+ /** Read credentials out of a request, both places the spec allows. */
89
+ readCredentials(input: {
90
+ authorization?: string;
91
+ body: Record<string, unknown>;
92
+ }): ClientCredentials;
93
+ }
94
+ //# sourceMappingURL=VisaManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisaManager.d.ts","sourceRoot":"","sources":["../src/VisaManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The server, assembled.
3
+ *
4
+ * Transport-agnostic on purpose: every method takes plain values and returns
5
+ * plain values, so the same object is driven by a ream route, by a test, or by
6
+ * a host that is not ream at all. The HTTP binding lives in the provider.
7
+ */
8
+ import { errorRedirect, issueAuthorizationCode, successRedirect, UnredirectableError, validateAuthorizationRequest, } from "./authorize.js";
9
+ import { readCredentials, redirectUriMatches, } from "./clients.js";
10
+ import { hashSecret, randomToken } from "./crypto.js";
11
+ import { OAuthError, VisaError } from "./errors.js";
12
+ import { introspect, revoke, verifyAccessToken } from "./introspect.js";
13
+ import { token } from "./token.js";
14
+ export class VisaManager {
15
+ #config;
16
+ constructor(config) {
17
+ // Validated here rather than at the first request: a server that boots
18
+ // with a broken issuer answers every metadata fetch with something no
19
+ // client can use, and says so nowhere.
20
+ this.#config = { ...config, issuer: normalizeIssuer(config.issuer) };
21
+ }
22
+ get issuer() {
23
+ return this.#config.issuer;
24
+ }
25
+ get store() {
26
+ return this.#config.store;
27
+ }
28
+ /**
29
+ * Register a client and hand back its secret ONCE.
30
+ *
31
+ * The plaintext is never stored and cannot be recovered; a client that
32
+ * loses it gets a new one. That is not an inconvenience to work around —
33
+ * it is the reason a database dump is not a set of working credentials.
34
+ */
35
+ async registerClient(input) {
36
+ // Refused at registration, not at the first authorization request: a
37
+ // URI that cannot be parsed would otherwise throw out of
38
+ // `successRedirect`, turning a client's mistake into a 500 on a path
39
+ // an attacker can reach.
40
+ assertRedirectUris(input.redirectUris);
41
+ const method = input.tokenEndpointAuthMethod ?? "client_secret_basic";
42
+ const secret = method === "none" ? undefined : randomToken();
43
+ const client = {
44
+ id: input.id,
45
+ name: input.name,
46
+ redirectUris: input.redirectUris,
47
+ grantTypes: input.grantTypes ?? ["authorization_code", "refresh_token"],
48
+ scopes: input.scopes ?? [],
49
+ tokenEndpointAuthMethod: method,
50
+ ...(input.trusted === undefined ? {} : { trusted: input.trusted }),
51
+ ...(secret === undefined ? {} : { secretHash: hashSecret(secret) }),
52
+ };
53
+ const store = this.#config.store;
54
+ if (!("addClient" in store) || typeof store.addClient !== "function") {
55
+ throw new VisaError("E_VISA_STORE_READ_ONLY", "This store cannot register clients; create them where they live.", {
56
+ hint: "MemoryStore.addClient exists; a database store registers rows.",
57
+ });
58
+ }
59
+ store.addClient(client);
60
+ return secret === undefined ? { client } : { client, secret };
61
+ }
62
+ /**
63
+ * Step one: check the request, and decide what happens to the user.
64
+ *
65
+ * `userId` is whoever is signed in — warden's business, not visa's. Pass
66
+ * `undefined` and the caller is told to authenticate first.
67
+ */
68
+ async authorize(request, userId) {
69
+ let validated;
70
+ try {
71
+ validated = await validateAuthorizationRequest(request, this.#config.store, this.#config);
72
+ }
73
+ catch (error) {
74
+ if (error instanceof UnredirectableError) {
75
+ return { type: "error", error: error.error };
76
+ }
77
+ if (error instanceof OAuthError) {
78
+ // Redirectable: the URI was verified before this check ran.
79
+ const uri = await this.#redirectUriFor(request);
80
+ if (uri === null)
81
+ return { type: "error", error };
82
+ return {
83
+ type: "redirect",
84
+ url: errorRedirect(uri, error, request.state),
85
+ };
86
+ }
87
+ throw error;
88
+ }
89
+ if (userId === undefined) {
90
+ return { type: "consent", request: validated };
91
+ }
92
+ const consent = await this.#config.store.findConsent(userId, validated.client.id);
93
+ const covered = consent !== null &&
94
+ validated.scopes.every((scope) => consent.scopes.includes(scope));
95
+ const forced = request.prompt === "consent";
96
+ if (validated.client.trusted !== true && (!covered || forced)) {
97
+ return { type: "consent", request: validated };
98
+ }
99
+ return { type: "redirect", url: await this.grant(validated, userId) };
100
+ }
101
+ /**
102
+ * Step two: the user said yes. Record it and build the redirect.
103
+ *
104
+ * Split from `authorize` because the consent screen sits between them, and
105
+ * the answer to it is a POST the application owns.
106
+ */
107
+ async grant(validated, userId, now = new Date()) {
108
+ await this.#config.store.saveConsent({
109
+ userId,
110
+ clientId: validated.client.id,
111
+ scopes: validated.scopes,
112
+ grantedAt: now,
113
+ });
114
+ const code = await issueAuthorizationCode(validated, userId, this.#config.store, this.#config, now);
115
+ return successRedirect(validated, code);
116
+ }
117
+ /** The user said no. */
118
+ deny(validated) {
119
+ return errorRedirect(validated.redirectUri, new OAuthError("access_denied", "The user refused the request."), validated.state);
120
+ }
121
+ async token(body, credentials, now = new Date()) {
122
+ return token(body, credentials, this.#config.store, this.#config, now);
123
+ }
124
+ async introspect(body, credentials, now = new Date()) {
125
+ return introspect(body, credentials, this.#config.store, now, this.issuer);
126
+ }
127
+ async revoke(body, credentials, now = new Date()) {
128
+ return revoke(body, credentials, this.#config.store, now);
129
+ }
130
+ /** What a resource server calls on every request. */
131
+ async verify(presented, now = new Date()) {
132
+ return verifyAccessToken(presented, this.#config.store, now);
133
+ }
134
+ /** Read credentials out of a request, both places the spec allows. */
135
+ readCredentials(input) {
136
+ return readCredentials(input);
137
+ }
138
+ /**
139
+ * The redirect URI an error may travel to, or `null` when there is none to
140
+ * trust. Re-resolved rather than remembered: the validation that failed is
141
+ * the one that would have produced it.
142
+ */
143
+ async #redirectUriFor(request) {
144
+ if (request.client_id === undefined)
145
+ return null;
146
+ const client = await this.#config.store.findClient(request.client_id);
147
+ if (client === null)
148
+ return null;
149
+ if (request.redirect_uri === undefined) {
150
+ return client.redirectUris.length === 1
151
+ ? (client.redirectUris[0] ?? null)
152
+ : null;
153
+ }
154
+ return redirectUriMatches(request.redirect_uri, client.redirectUris)
155
+ ? request.redirect_uri
156
+ : null;
157
+ }
158
+ }
159
+ /**
160
+ * The issuer, checked and trimmed.
161
+ *
162
+ * A trailing slash is not cosmetic here: every endpoint is built by
163
+ * concatenation, so `https://auth.test/` yields `https://auth.test//oauth/token`
164
+ * — a URL some clients normalise and others do not.
165
+ */
166
+ function normalizeIssuer(issuer) {
167
+ if (issuer === undefined || issuer === "") {
168
+ throw new VisaError("E_VISA_MISSING_ISSUER", "visa needs an issuer — the public URL this authorization server answers on.", { hint: "Set `issuer` in config/visa.ts, e.g. https://auth.example.com" });
169
+ }
170
+ let url;
171
+ try {
172
+ url = new URL(issuer);
173
+ }
174
+ catch {
175
+ throw new VisaError("E_VISA_INVALID_ISSUER", `visa's issuer is not a URL: ${issuer}`, { hint: "It is the public origin, e.g. https://auth.example.com" });
176
+ }
177
+ if (url.protocol !== "https:" && url.hostname !== "localhost") {
178
+ // Tokens travel to it. `http://` is for a laptop, and the exception is
179
+ // named so nobody has to guess whether it applies in production.
180
+ throw new VisaError("E_VISA_INSECURE_ISSUER", `visa's issuer must be https (localhost excepted): ${issuer}`);
181
+ }
182
+ if (url.hash !== "" || url.search !== "") {
183
+ throw new VisaError("E_VISA_INVALID_ISSUER", "visa's issuer must carry no query string and no fragment.");
184
+ }
185
+ return issuer.replace(/\/+$/, "");
186
+ }
187
+ /**
188
+ * Every redirect URI a client registers, checked once.
189
+ *
190
+ * A fragment is refused because the authorization response appends its own
191
+ * query and a fragment would swallow it; a wildcard because this server
192
+ * compares exactly and `*` would simply never match, silently.
193
+ */
194
+ function assertRedirectUris(uris) {
195
+ if (uris.length === 0) {
196
+ throw new VisaError("E_VISA_NO_REDIRECT_URI", "A client needs at least one redirect URI.");
197
+ }
198
+ for (const uri of uris) {
199
+ let url;
200
+ try {
201
+ url = new URL(uri);
202
+ }
203
+ catch {
204
+ throw new VisaError("E_VISA_INVALID_REDIRECT_URI", `Not a URL: ${uri}`, {
205
+ hint: "Register the complete URI, scheme and path included.",
206
+ });
207
+ }
208
+ if (url.hash !== "") {
209
+ throw new VisaError("E_VISA_INVALID_REDIRECT_URI", `A redirect URI must carry no fragment: ${uri}`);
210
+ }
211
+ if (uri.includes("*")) {
212
+ throw new VisaError("E_VISA_INVALID_REDIRECT_URI", `Wildcards are not matched, so this would never accept anything: ${uri}`);
213
+ }
214
+ }
215
+ }
216
+ //# sourceMappingURL=VisaManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisaManager.js","sourceRoot":"","sources":["../src/VisaManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Wires `config/visa.ts` into the container, and mounts the endpoints that
3
+ * need no human.
4
+ *
5
+ * `/token`, `/revoke` and `/introspect` are machine-to-machine: the spec says
6
+ * exactly what goes in and what comes out, so the provider owns them.
7
+ *
8
+ * `/authorize` is NOT mounted, and that is deliberate rather than unfinished.
9
+ * It needs two things visa has no opinion about: who is signed in — warden's
10
+ * business — and what a consent screen looks like, which is the application's.
11
+ * A route that guessed either would be wrong in a way that is hard to notice:
12
+ * a server that shows no consent screen grants silently, and one that assumes
13
+ * a session shape signs the wrong user in. `visa.authorize()` gives you the
14
+ * decision; the page around it is yours. The README shows the ten lines.
15
+ *
16
+ * Duck-typed throughout: visa must not import its host's HTTP types.
17
+ */
18
+ import "./augmentations.js";
19
+ interface VisaContainer {
20
+ singleton(token: unknown, factory: () => unknown): void;
21
+ resolve<T = unknown>(token: unknown): Promise<T>;
22
+ has(token: unknown): boolean;
23
+ }
24
+ interface VisaConfigStore {
25
+ get<T = unknown>(key: string): T | undefined;
26
+ }
27
+ export interface VisaAppContext {
28
+ container: VisaContainer;
29
+ config: VisaConfigStore;
30
+ }
31
+ export default class VisaProvider {
32
+ #private;
33
+ protected app: VisaAppContext;
34
+ constructor(app: VisaAppContext);
35
+ register(): void;
36
+ boot(): Promise<void>;
37
+ start(): Promise<void>;
38
+ shutdown(): Promise<void>;
39
+ }
40
+ export {};
41
+ //# sourceMappingURL=VisaProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisaProvider.d.ts","sourceRoot":"","sources":["../src/VisaProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,oBAAoB,CAAC;AAM5B,UAAU,aAAa;IACtB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CAC7B;AACD,UAAU,eAAe;IACxB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AACD,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EAAE,eAAe,CAAC;CACxB;AAqBD,MAAM,CAAC,OAAO,OAAO,YAAY;;IAGpB,SAAS,CAAC,GAAG,EAAE,cAAc;gBAAnB,GAAG,EAAE,cAAc;IAEzC,QAAQ,IAAI,IAAI;IAiBV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6EtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA8B/B"}