@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
@@ -0,0 +1,153 @@
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
+ import { OAuthError } from "./errors.js";
20
+ import { clearVisa, getVisa, setVisa } from "./services/main.js";
21
+ import { VisaManager } from "./VisaManager.js";
22
+ export default class VisaProvider {
23
+ app;
24
+ #owned;
25
+ constructor(app) {
26
+ this.app = app;
27
+ }
28
+ register() {
29
+ this.app.container.singleton(VisaManager, () => {
30
+ const config = this.app.config.get("visa");
31
+ if (config === undefined) {
32
+ throw new Error("[visa] no `config/visa.ts` — run `ream configure @c9up/visa`.");
33
+ }
34
+ const manager = new VisaManager(config);
35
+ setVisa(manager);
36
+ return manager;
37
+ });
38
+ this.app.container.singleton("visa", () => this.app.container.resolve(VisaManager));
39
+ }
40
+ async boot() {
41
+ const manager = await this.app.container.resolve(VisaManager);
42
+ this.#owned = manager;
43
+ setVisa(manager);
44
+ }
45
+ async start() {
46
+ if (!this.app.container.has("router"))
47
+ return;
48
+ const router = await this.app.container.resolve("router");
49
+ const manager = await this.app.container.resolve(VisaManager);
50
+ const config = this.app.config.get("visa");
51
+ const prefix = config?.prefix ?? "/oauth";
52
+ router.post(`${prefix}/token`, async (ctx) => {
53
+ await this.#answer(ctx, async () => {
54
+ const body = ctx.request.all();
55
+ const credentials = manager.readCredentials({
56
+ body,
57
+ ...readAuthorization(ctx),
58
+ });
59
+ const tokens = await manager.token(body, credentials);
60
+ // No store may keep a token response: it carries credentials
61
+ // (RFC 6749 §5.1), and a shared cache would hand them to the
62
+ // next caller.
63
+ ctx.response.header("cache-control", "no-store");
64
+ ctx.response.header("pragma", "no-cache");
65
+ return tokens;
66
+ });
67
+ });
68
+ router.post(`${prefix}/introspect`, async (ctx) => {
69
+ await this.#answer(ctx, async () => {
70
+ const body = ctx.request.all();
71
+ const credentials = manager.readCredentials({
72
+ body,
73
+ ...readAuthorization(ctx),
74
+ });
75
+ ctx.response.header("cache-control", "no-store");
76
+ return manager.introspect(body, credentials);
77
+ });
78
+ });
79
+ router.post(`${prefix}/revoke`, async (ctx) => {
80
+ await this.#answer(ctx, async () => {
81
+ const body = ctx.request.all();
82
+ const credentials = manager.readCredentials({
83
+ body,
84
+ ...readAuthorization(ctx),
85
+ });
86
+ await manager.revoke(body, credentials);
87
+ // RFC 7009 §2.2: 200 with an empty body, whatever the token was.
88
+ ctx.response.status(200);
89
+ return null;
90
+ });
91
+ });
92
+ // RFC 8414 — what a client fetches to discover the endpoints.
93
+ router.get("/.well-known/oauth-authorization-server", async (ctx) => {
94
+ ctx.response.header("content-type", "application/json");
95
+ ctx.response.send({
96
+ issuer: manager.issuer,
97
+ authorization_endpoint: `${manager.issuer}${prefix}/authorize`,
98
+ token_endpoint: `${manager.issuer}${prefix}/token`,
99
+ introspection_endpoint: `${manager.issuer}${prefix}/introspect`,
100
+ revocation_endpoint: `${manager.issuer}${prefix}/revoke`,
101
+ response_types_supported: ["code"],
102
+ grant_types_supported: [
103
+ "authorization_code",
104
+ "refresh_token",
105
+ "client_credentials",
106
+ ],
107
+ // S256 only unless the application turned `plain` back on.
108
+ code_challenge_methods_supported: config?.allowPlainChallenge === true ? ["S256", "plain"] : ["S256"],
109
+ token_endpoint_auth_methods_supported: [
110
+ "client_secret_basic",
111
+ "client_secret_post",
112
+ "none",
113
+ ],
114
+ });
115
+ });
116
+ }
117
+ async shutdown() {
118
+ if (this.#owned !== undefined && getVisa() === this.#owned)
119
+ clearVisa();
120
+ this.#owned = undefined;
121
+ }
122
+ /**
123
+ * Run a handler and turn an `OAuthError` into the body the spec names.
124
+ *
125
+ * Anything else is rethrown: an unexpected failure is the application's
126
+ * exception handler's business, and dressing it as an OAuth error would
127
+ * tell a client its request was bad when the server broke.
128
+ */
129
+ async #answer(ctx, work) {
130
+ try {
131
+ const body = await work();
132
+ if (body !== null)
133
+ ctx.response.send(body);
134
+ else
135
+ ctx.response.send("");
136
+ }
137
+ catch (error) {
138
+ if (!(error instanceof OAuthError))
139
+ throw error;
140
+ ctx.response.status(error.status);
141
+ if (error.code === "invalid_client") {
142
+ // §5.2: a 401 has to say how to authenticate.
143
+ ctx.response.header("www-authenticate", 'Basic realm="oauth"');
144
+ }
145
+ ctx.response.send(error.toResponse());
146
+ }
147
+ }
148
+ }
149
+ function readAuthorization(ctx) {
150
+ const header = ctx.request.header("authorization");
151
+ return header === undefined ? {} : { authorization: header };
152
+ }
153
+ //# sourceMappingURL=VisaProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VisaProvider.js","sourceRoot":"","sources":["../src/VisaProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Teach ream's `ContainerBindings` what `container.make(...)` returns for the
3
+ * token visa binds.
4
+ *
5
+ * ream declares that interface open on purpose: it registers its own entries
6
+ * and expects each package to contribute the ones it owns. Without this,
7
+ * resolving by the string token answers `unknown` and every call site has to
8
+ * assert a type it cannot prove — on the object that mints credentials, which
9
+ * is the last place to be guessing.
10
+ *
11
+ * Loaded from the barrel and from the provider, so importing visa anywhere is
12
+ * enough; nobody writes a `declare module` of their own.
13
+ *
14
+ * Type-only, and ream stays an OPTIONAL peer: nothing here reaches a runtime
15
+ * import, and a `declare module` for a specifier that does not resolve is
16
+ * simply inert.
17
+ */
18
+ import type { VisaManager } from "./VisaManager.js";
19
+ declare module "@c9up/ream/types" {
20
+ interface ContainerBindings {
21
+ /** The authorization server, bound by `VisaProvider`. */
22
+ visa: VisaManager;
23
+ }
24
+ }
25
+ //# sourceMappingURL=augmentations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"augmentations.d.ts","sourceRoot":"","sources":["../src/augmentations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,QAAQ,kBAAkB,CAAC;IACjC,UAAU,iBAAiB;QAC1B,yDAAyD;QACzD,IAAI,EAAE,WAAW,CAAC;KAClB;CACD"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Teach ream's `ContainerBindings` what `container.make(...)` returns for the
3
+ * token visa binds.
4
+ *
5
+ * ream declares that interface open on purpose: it registers its own entries
6
+ * and expects each package to contribute the ones it owns. Without this,
7
+ * resolving by the string token answers `unknown` and every call site has to
8
+ * assert a type it cannot prove — on the object that mints credentials, which
9
+ * is the last place to be guessing.
10
+ *
11
+ * Loaded from the barrel and from the provider, so importing visa anywhere is
12
+ * enough; nobody writes a `declare module` of their own.
13
+ *
14
+ * Type-only, and ream stays an OPTIONAL peer: nothing here reaches a runtime
15
+ * import, and a `declare module` for a specifier that does not resolve is
16
+ * simply inert.
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=augmentations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"augmentations.js","sourceRoot":"","sources":["../src/augmentations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The authorization endpoint.
3
+ *
4
+ * It is the half a human sees, and the half where an error must NOT be
5
+ * redirected: if the redirect URI is missing, unregistered or malformed, the
6
+ * server tells the user and stops (§4.1.2.1). Bouncing the error to an
7
+ * unverified URI is how an attacker learns that a client exists and gets a
8
+ * page of their choosing loaded in the user's session.
9
+ */
10
+ import { OAuthError } from "./errors.js";
11
+ import type { VisaStore } from "./store.js";
12
+ import type { Client } from "./types.js";
13
+ /** The query as it arrived, untrusted. */
14
+ export interface AuthorizationRequest {
15
+ response_type?: string;
16
+ client_id?: string;
17
+ redirect_uri?: string;
18
+ scope?: string;
19
+ state?: string;
20
+ code_challenge?: string;
21
+ code_challenge_method?: string;
22
+ nonce?: string;
23
+ /** A client may ask to always see the consent screen. */
24
+ prompt?: string;
25
+ }
26
+ /** A request that passed every check, with the values it resolved to. */
27
+ export interface ValidatedRequest {
28
+ client: Client;
29
+ redirectUri: string;
30
+ scopes: string[];
31
+ state: string | undefined;
32
+ codeChallenge: string;
33
+ codeChallengeMethod: "S256" | "plain";
34
+ nonce: string | undefined;
35
+ }
36
+ /**
37
+ * A failure that cannot be redirected — the user must be told directly.
38
+ *
39
+ * Separate from `OAuthError` because the CALLER has to treat it differently:
40
+ * render a page, never `Location:`.
41
+ */
42
+ export declare class UnredirectableError extends Error {
43
+ readonly error: OAuthError;
44
+ constructor(error: OAuthError);
45
+ }
46
+ export interface AuthorizeOptions {
47
+ /** Lifetime of the code. Short on purpose — see `issueAuthorizationCode`. */
48
+ codeTtlSeconds?: number;
49
+ /**
50
+ * Accept `plain` as a code challenge method.
51
+ *
52
+ * NAMED DEVIATION — the spec allows `plain`; this refuses it by default.
53
+ * `plain` puts the verifier in the authorization request, so anything that
54
+ * can read that request (a log, a referrer, a proxy) can complete the
55
+ * exchange, which is precisely what PKCE exists to stop. Every client that
56
+ * can hash can use S256.
57
+ */
58
+ allowPlainChallenge?: boolean;
59
+ }
60
+ /**
61
+ * Check an authorization request. Throws `UnredirectableError` when the error
62
+ * must not be bounced back, `OAuthError` when it may.
63
+ */
64
+ export declare function validateAuthorizationRequest(request: AuthorizationRequest, store: VisaStore, options?: AuthorizeOptions): Promise<ValidatedRequest>;
65
+ /**
66
+ * Mint the code, once the user is known and has consented.
67
+ *
68
+ * Thirty seconds by default. A code is exchanged by a server the client
69
+ * controls, milliseconds after the redirect — a long window buys the client
70
+ * nothing and gives anything that can read a URL (a log, a referrer header, a
71
+ * shoulder) time to use it.
72
+ */
73
+ export declare function issueAuthorizationCode(validated: ValidatedRequest, userId: string, store: VisaStore, options?: AuthorizeOptions, now?: Date): Promise<string>;
74
+ /** Build the redirect a successful authorization ends on. */
75
+ export declare function successRedirect(validated: ValidatedRequest, code: string): string;
76
+ /** Build the redirect a refused or failed authorization ends on. */
77
+ export declare function errorRedirect(redirectUri: string, error: OAuthError, state?: string): string;
78
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAqB,MAAM,EAAE,MAAM,YAAY,CAAC;AAE5D,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;gBACf,KAAK,EAAE,UAAU;CAK7B;AAED,MAAM,WAAW,gBAAgB;IAChC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CACjD,OAAO,EAAE,oBAAoB,EAC7B,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,gBAAqB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAgD3B;AA2DD;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC3C,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,gBAAqB,EAC9B,GAAG,GAAE,IAAiB,GACpB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAC9B,SAAS,EAAE,gBAAgB,EAC3B,IAAI,EAAE,MAAM,GACV,MAAM,CAQR;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAC5B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,UAAU,EACjB,KAAK,CAAC,EAAE,MAAM,GACZ,MAAM,CAQR"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The authorization endpoint.
3
+ *
4
+ * It is the half a human sees, and the half where an error must NOT be
5
+ * redirected: if the redirect URI is missing, unregistered or malformed, the
6
+ * server tells the user and stops (§4.1.2.1). Bouncing the error to an
7
+ * unverified URI is how an attacker learns that a client exists and gets a
8
+ * page of their choosing loaded in the user's session.
9
+ */
10
+ import { assertGrantAllowed, redirectUriMatches, resolveScopes, } from "./clients.js";
11
+ import { hashSecret, randomToken } from "./crypto.js";
12
+ import { OAuthError } from "./errors.js";
13
+ /**
14
+ * A failure that cannot be redirected — the user must be told directly.
15
+ *
16
+ * Separate from `OAuthError` because the CALLER has to treat it differently:
17
+ * render a page, never `Location:`.
18
+ */
19
+ export class UnredirectableError extends Error {
20
+ error;
21
+ constructor(error) {
22
+ super(error.message);
23
+ this.name = "UnredirectableError";
24
+ this.error = error;
25
+ }
26
+ }
27
+ /**
28
+ * Check an authorization request. Throws `UnredirectableError` when the error
29
+ * must not be bounced back, `OAuthError` when it may.
30
+ */
31
+ export async function validateAuthorizationRequest(request, store, options = {}) {
32
+ // Order matters: everything that decides WHERE an error may go has to be
33
+ // settled before any error is produced.
34
+ if (request.client_id === undefined || request.client_id === "") {
35
+ throw new UnredirectableError(new OAuthError("invalid_request", "client_id is required."));
36
+ }
37
+ const client = await store.findClient(request.client_id);
38
+ if (client === null) {
39
+ throw new UnredirectableError(new OAuthError("invalid_request", "Unknown client."));
40
+ }
41
+ const redirectUri = resolveRedirectUri(request.redirect_uri, client);
42
+ // From here the redirect URI is verified, so an error may travel back.
43
+ if (request.response_type !== "code") {
44
+ throw new OAuthError(request.response_type === undefined
45
+ ? "invalid_request"
46
+ : "unsupported_response_type",
47
+ // The implicit grant is gone in 2.1, and `token` is its response
48
+ // type — saying so beats a blank refusal for whoever is porting.
49
+ request.response_type === "token"
50
+ ? "The implicit grant is not supported; use response_type=code."
51
+ : "Only response_type=code is supported.");
52
+ }
53
+ assertGrantAllowed(client, "authorization_code");
54
+ const { codeChallenge, codeChallengeMethod } = readChallenge(request, options);
55
+ const scopes = resolveScopes(request.scope, client);
56
+ return {
57
+ client,
58
+ redirectUri,
59
+ scopes,
60
+ state: request.state,
61
+ codeChallenge,
62
+ codeChallengeMethod,
63
+ nonce: request.nonce,
64
+ };
65
+ }
66
+ /**
67
+ * Which redirect URI this request lands on.
68
+ *
69
+ * Omitting it is allowed only when the client registered exactly one — with
70
+ * two or more, "the first one" would be a choice the server makes on the
71
+ * client's behalf, and the wrong choice sends the code elsewhere.
72
+ */
73
+ function resolveRedirectUri(requested, client) {
74
+ if (client.redirectUris.length === 0) {
75
+ throw new UnredirectableError(new OAuthError("invalid_request", "This client has no redirect URI."));
76
+ }
77
+ if (requested === undefined || requested === "") {
78
+ const only = client.redirectUris[0];
79
+ if (client.redirectUris.length > 1 || only === undefined) {
80
+ throw new UnredirectableError(new OAuthError("invalid_request", "redirect_uri is required when a client registers more than one."));
81
+ }
82
+ return only;
83
+ }
84
+ if (!redirectUriMatches(requested, client.redirectUris)) {
85
+ throw new UnredirectableError(new OAuthError("invalid_request", "redirect_uri is not registered."));
86
+ }
87
+ return requested;
88
+ }
89
+ function readChallenge(request, options) {
90
+ const challenge = request.code_challenge;
91
+ if (challenge === undefined || challenge === "") {
92
+ // Mandatory in 2.1 for every client, confidential ones included.
93
+ throw new OAuthError("invalid_request", "code_challenge is required.");
94
+ }
95
+ const method = request.code_challenge_method ?? "plain";
96
+ if (method === "S256")
97
+ return { codeChallenge: challenge, codeChallengeMethod: "S256" };
98
+ if (method === "plain" && options.allowPlainChallenge === true) {
99
+ return { codeChallenge: challenge, codeChallengeMethod: "plain" };
100
+ }
101
+ throw new OAuthError("invalid_request", "code_challenge_method must be S256.");
102
+ }
103
+ /**
104
+ * Mint the code, once the user is known and has consented.
105
+ *
106
+ * Thirty seconds by default. A code is exchanged by a server the client
107
+ * controls, milliseconds after the redirect — a long window buys the client
108
+ * nothing and gives anything that can read a URL (a log, a referrer header, a
109
+ * shoulder) time to use it.
110
+ */
111
+ export async function issueAuthorizationCode(validated, userId, store, options = {}, now = new Date()) {
112
+ const code = randomToken();
113
+ const ttl = options.codeTtlSeconds ?? 30;
114
+ const record = {
115
+ codeHash: hashSecret(code),
116
+ clientId: validated.client.id,
117
+ userId,
118
+ redirectUri: validated.redirectUri,
119
+ scopes: validated.scopes,
120
+ codeChallenge: validated.codeChallenge,
121
+ codeChallengeMethod: validated.codeChallengeMethod,
122
+ expiresAt: new Date(now.getTime() + ttl * 1000),
123
+ ...(validated.nonce === undefined ? {} : { nonce: validated.nonce }),
124
+ };
125
+ await store.saveAuthorizationCode(record);
126
+ return code;
127
+ }
128
+ /** Build the redirect a successful authorization ends on. */
129
+ export function successRedirect(validated, code) {
130
+ const url = new URL(validated.redirectUri);
131
+ url.searchParams.set("code", code);
132
+ // Returned verbatim when the client sent one, absent when it did not —
133
+ // inventing a state would break the client's own CSRF check.
134
+ if (validated.state !== undefined)
135
+ url.searchParams.set("state", validated.state);
136
+ return url.toString();
137
+ }
138
+ /** Build the redirect a refused or failed authorization ends on. */
139
+ export function errorRedirect(redirectUri, error, state) {
140
+ const url = new URL(redirectUri);
141
+ url.searchParams.set("error", error.code);
142
+ if (error.description !== undefined) {
143
+ url.searchParams.set("error_description", error.description);
144
+ }
145
+ if (state !== undefined)
146
+ url.searchParams.set("state", state);
147
+ return url.toString();
148
+ }
149
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Who is asking, and are they allowed to ask that.
3
+ *
4
+ * Three checks live here and each one closes a known attack: the redirect URI
5
+ * comparison (an open redirect on the client's domain turns into a stolen
6
+ * code), the client authentication (a leaked client_id is not a credential),
7
+ * and the scope narrowing (a client must not be able to grant itself more
8
+ * than it was registered for).
9
+ */
10
+ import type { Client, ClientAuthMethod, GrantType } from "./types.js";
11
+ /**
12
+ * Does this redirect URI match one the client registered?
13
+ *
14
+ * EXACT string comparison, as OAuth 2.1 §2.3.1 requires — with the single
15
+ * exception it names: a loopback redirect may differ in its port, because a
16
+ * native app cannot know in advance which port the OS will hand it.
17
+ *
18
+ * No prefix matching, no wildcard, no "same origin is close enough": every
19
+ * one of those has a published attack where the code lands on a URL the
20
+ * attacker controls.
21
+ */
22
+ export declare function redirectUriMatches(candidate: string, registered: readonly string[]): boolean;
23
+ /** Credentials as they arrived, from either place the spec allows. */
24
+ export interface ClientCredentials {
25
+ clientId?: string;
26
+ clientSecret?: string;
27
+ /**
28
+ * WHERE they came from.
29
+ *
30
+ * A client registers one authentication method and must use that one: a
31
+ * client declared `client_secret_basic` sending its secret in the body is
32
+ * either misconfigured or being replayed by something that could only read
33
+ * the body. Accepting both makes the registered method decorative.
34
+ */
35
+ method?: ClientAuthMethod;
36
+ /** The raw `Authorization` header, if there was one. */
37
+ authorization?: string;
38
+ }
39
+ /**
40
+ * Read `client_id` / `client_secret` out of a request.
41
+ *
42
+ * A client MUST NOT use more than one authentication method in a single
43
+ * request (§2.4): sending both Basic and body credentials is a sign of a
44
+ * confused proxy or of an attempt to have one of them ignored, so it is
45
+ * refused rather than resolved by precedence.
46
+ */
47
+ export declare function readCredentials(input: {
48
+ authorization?: string;
49
+ body: Record<string, unknown>;
50
+ }): ClientCredentials;
51
+ /**
52
+ * Authenticate a client for the token endpoint.
53
+ *
54
+ * Every failure answers `invalid_client` with no detail — "no such client",
55
+ * "wrong secret" and "that client has no secret" are the same sentence to the
56
+ * caller, because telling them apart is how a client list gets enumerated.
57
+ */
58
+ export declare function authenticateClient(credentials: ClientCredentials, load: (id: string) => Promise<Client | null>): Promise<Client>;
59
+ /** Is this grant one the client was registered for? */
60
+ export declare function assertGrantAllowed(client: Client, grant: GrantType): void;
61
+ /**
62
+ * Narrow a requested scope against what the client may ever have.
63
+ *
64
+ * Absent, the request gets the client's registered set — never "everything".
65
+ * A scope the client was not registered for is refused rather than dropped:
66
+ * silently granting less than asked is how a client ends up believing it has
67
+ * a permission it does not.
68
+ */
69
+ export declare function resolveScopes(requested: string | undefined, client: Client): string[];
70
+ //# sourceMappingURL=clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../src/clients.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CA2BT;AAMD,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B,GAAG,iBAAiB,CA4BpB;AAgCD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACvC,WAAW,EAAE,iBAAiB,EAC9B,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAC1C,OAAO,CAAC,MAAM,CAAC,CAoCjB;AAED,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAMzE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,MAAM,GACZ,MAAM,EAAE,CAaV"}