@keycardai/oauth 0.20.0 → 0.21.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 (50) hide show
  1. package/README.md +60 -4
  2. package/dist/cjs/discovery.d.ts +21 -0
  3. package/dist/cjs/discovery.d.ts.map +1 -1
  4. package/dist/cjs/discovery.js +8 -0
  5. package/dist/cjs/discovery.js.map +1 -1
  6. package/dist/cjs/errors.d.ts +17 -0
  7. package/dist/cjs/errors.d.ts.map +1 -1
  8. package/dist/cjs/errors.js +26 -1
  9. package/dist/cjs/errors.js.map +1 -1
  10. package/dist/cjs/index.d.ts +5 -1
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +8 -1
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/pkce.d.ts +5 -0
  15. package/dist/cjs/pkce.d.ts.map +1 -1
  16. package/dist/cjs/pkce.js +4 -1
  17. package/dist/cjs/pkce.js.map +1 -1
  18. package/dist/cjs/userinfo.d.ts +35 -0
  19. package/dist/cjs/userinfo.d.ts.map +1 -0
  20. package/dist/cjs/userinfo.js +79 -0
  21. package/dist/cjs/userinfo.js.map +1 -0
  22. package/dist/cjs/webApp.d.ts +78 -0
  23. package/dist/cjs/webApp.d.ts.map +1 -0
  24. package/dist/cjs/webApp.js +95 -0
  25. package/dist/cjs/webApp.js.map +1 -0
  26. package/dist/esm/discovery.d.ts +21 -0
  27. package/dist/esm/discovery.d.ts.map +1 -1
  28. package/dist/esm/discovery.js +8 -0
  29. package/dist/esm/discovery.js.map +1 -1
  30. package/dist/esm/errors.d.ts +17 -0
  31. package/dist/esm/errors.d.ts.map +1 -1
  32. package/dist/esm/errors.js +23 -0
  33. package/dist/esm/errors.js.map +1 -1
  34. package/dist/esm/index.d.ts +5 -1
  35. package/dist/esm/index.d.ts.map +1 -1
  36. package/dist/esm/index.js +3 -1
  37. package/dist/esm/index.js.map +1 -1
  38. package/dist/esm/pkce.d.ts +5 -0
  39. package/dist/esm/pkce.d.ts.map +1 -1
  40. package/dist/esm/pkce.js +4 -1
  41. package/dist/esm/pkce.js.map +1 -1
  42. package/dist/esm/userinfo.d.ts +35 -0
  43. package/dist/esm/userinfo.d.ts.map +1 -0
  44. package/dist/esm/userinfo.js +76 -0
  45. package/dist/esm/userinfo.js.map +1 -0
  46. package/dist/esm/webApp.d.ts +78 -0
  47. package/dist/esm/webApp.d.ts.map +1 -0
  48. package/dist/esm/webApp.js +88 -0
  49. package/dist/esm/webApp.js.map +1 -0
  50. package/package.json +11 -1
@@ -0,0 +1,78 @@
1
+ import { type OAuthAuthorizationServerMetadata } from "./discovery.js";
2
+ import type { TokenResponse } from "./tokenExchange.js";
3
+ export interface BeginAuthorizationOptions {
4
+ clientId: string;
5
+ /** Registered redirect URI handled by the web application. */
6
+ redirectUri: string;
7
+ /**
8
+ * Protected resources the authorization is targeting. Each entry is sent as
9
+ * its own RFC 8707 `resource` parameter, so one authorization can cover
10
+ * several resources and the issued token's audience covers all of them.
11
+ */
12
+ resources?: readonly string[];
13
+ scopes?: readonly string[];
14
+ /** Pre-discovered metadata. When set, no discovery request is made. */
15
+ metadata?: OAuthAuthorizationServerMetadata;
16
+ signal?: AbortSignal;
17
+ }
18
+ export interface AuthorizationRedirect {
19
+ /** The authorization URL to redirect the user's browser to. */
20
+ url: string;
21
+ /** Generated CSRF value to store until the callback. */
22
+ state: string;
23
+ /** Generated PKCE verifier to store until the callback; never send it to the browser. */
24
+ codeVerifier: string;
25
+ /**
26
+ * The resources the authorization request was scoped to. They are not needed
27
+ * to redeem the code — the authorization server derives the issued token's
28
+ * audience from the code itself — but applications commonly need to know
29
+ * which resources a session was authorized for.
30
+ */
31
+ resources: string[];
32
+ }
33
+ export interface CompleteAuthorizationOptions {
34
+ /** Query parameters the callback route received. */
35
+ callbackParams: URLSearchParams | Record<string, string>;
36
+ /** The `state` stored at the begin step. */
37
+ state: string;
38
+ /** The `codeVerifier` stored at the begin step. */
39
+ codeVerifier: string;
40
+ clientId: string;
41
+ /** The same registered redirect URI used at the begin step. */
42
+ redirectUri: string;
43
+ /** Client secret for confidential clients. Public clients omit it. */
44
+ clientSecret?: string;
45
+ /** Pre-discovered metadata. When set, no discovery request is made. */
46
+ metadata?: OAuthAuthorizationServerMetadata;
47
+ signal?: AbortSignal;
48
+ }
49
+ /**
50
+ * Begin a web-app authorization-code-with-PKCE flow.
51
+ *
52
+ * For applications that own a registered redirect URI and receive the callback
53
+ * on their own route, where the loopback listener `authenticate()` runs is
54
+ * wrong. Generates the PKCE pair and a CSRF `state`, resolves the
55
+ * `authorization_endpoint` by discovery, and builds the authorization URL with
56
+ * one `resource` parameter per entry of `resources`.
57
+ *
58
+ * The SDK holds no state between begin and complete: where `state` and
59
+ * `codeVerifier` live between the redirect and the callback (a session, a
60
+ * signed cookie) is the application's concern, which is what makes the flow
61
+ * safe under concurrent sign-ins and multi-process servers.
62
+ */
63
+ export declare function beginAuthorization(issuer: string, options: BeginAuthorizationOptions): Promise<AuthorizationRedirect>;
64
+ /**
65
+ * Complete a web-app authorization-code-with-PKCE flow from the callback route.
66
+ *
67
+ * Callback validation happens before discovery or any token request: a
68
+ * callback carrying `error` throws `AuthorizationDeniedError`, a missing or
69
+ * non-matching `state` throws `StateMismatchError`, and a missing `code`
70
+ * throws `OAuthError("invalid_request")`.
71
+ *
72
+ * No RFC 8707 `resource` parameter is sent on the token request: the
73
+ * authorization server derives the issued token's audience from the
74
+ * authorization code, which already records the resources authorized at the
75
+ * begin step.
76
+ */
77
+ export declare function completeAuthorization(issuer: string, options: CompleteAuthorizationOptions): Promise<TokenResponse>;
78
+ //# sourceMappingURL=webApp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webApp.d.ts","sourceRoot":"","sources":["../../src/webApp.ts"],"names":[],"mappings":"AACA,OAAO,EAAoC,KAAK,gCAAgC,EAAE,MAAM,gBAAgB,CAAC;AAGzG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,gCAAgC,CAAC;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,oDAAoD;IACpD,cAAc,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,gCAAgC,CAAC;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,qBAAqB,CAAC,CA0BhC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,aAAa,CAAC,CAmCxB"}
@@ -0,0 +1,88 @@
1
+ import base64url from "./base64url.js";
2
+ import { fetchAuthorizationServerMetadata } from "./discovery.js";
3
+ import { AuthorizationDeniedError, OAuthError, StateMismatchError } from "./errors.js";
4
+ import { buildAuthorizeUrl, exchangeAuthorizationCode, generatePkcePair } from "./pkce.js";
5
+ /**
6
+ * Begin a web-app authorization-code-with-PKCE flow.
7
+ *
8
+ * For applications that own a registered redirect URI and receive the callback
9
+ * on their own route, where the loopback listener `authenticate()` runs is
10
+ * wrong. Generates the PKCE pair and a CSRF `state`, resolves the
11
+ * `authorization_endpoint` by discovery, and builds the authorization URL with
12
+ * one `resource` parameter per entry of `resources`.
13
+ *
14
+ * The SDK holds no state between begin and complete: where `state` and
15
+ * `codeVerifier` live between the redirect and the callback (a session, a
16
+ * signed cookie) is the application's concern, which is what makes the flow
17
+ * safe under concurrent sign-ins and multi-process servers.
18
+ */
19
+ export async function beginAuthorization(issuer, options) {
20
+ const metadata = options.metadata
21
+ ?? await fetchAuthorizationServerMetadata(issuer, { signal: options.signal });
22
+ if (!metadata.authorization_endpoint) {
23
+ throw new Error(`Authorization server "${issuer}" does not advertise an authorization_endpoint`);
24
+ }
25
+ const { codeVerifier, codeChallenge } = await generatePkcePair("S256");
26
+ const stateBytes = new Uint8Array(32);
27
+ crypto.getRandomValues(stateBytes);
28
+ const state = base64url.encode(stateBytes.buffer);
29
+ const resources = [...options.resources ?? []];
30
+ const url = buildAuthorizeUrl(metadata.authorization_endpoint, {
31
+ clientId: options.clientId,
32
+ redirectUri: options.redirectUri,
33
+ codeChallenge,
34
+ state,
35
+ scope: options.scopes && options.scopes.length > 0 ? options.scopes.join(" ") : undefined,
36
+ resources,
37
+ });
38
+ return { url, state, codeVerifier, resources };
39
+ }
40
+ /**
41
+ * Complete a web-app authorization-code-with-PKCE flow from the callback route.
42
+ *
43
+ * Callback validation happens before discovery or any token request: a
44
+ * callback carrying `error` throws `AuthorizationDeniedError`, a missing or
45
+ * non-matching `state` throws `StateMismatchError`, and a missing `code`
46
+ * throws `OAuthError("invalid_request")`.
47
+ *
48
+ * No RFC 8707 `resource` parameter is sent on the token request: the
49
+ * authorization server derives the issued token's audience from the
50
+ * authorization code, which already records the resources authorized at the
51
+ * begin step.
52
+ */
53
+ export async function completeAuthorization(issuer, options) {
54
+ const params = options.callbackParams instanceof URLSearchParams
55
+ ? options.callbackParams
56
+ : new URLSearchParams(options.callbackParams);
57
+ const error = params.get("error");
58
+ if (error) {
59
+ throw new AuthorizationDeniedError(error, params.get("error_description") ?? undefined, params.get("error_uri") ?? undefined);
60
+ }
61
+ const callbackState = params.get("state");
62
+ if (callbackState === null || !timingSafeEqual(callbackState, options.state)) {
63
+ throw new StateMismatchError();
64
+ }
65
+ const code = params.get("code");
66
+ if (!code) {
67
+ throw new OAuthError("invalid_request", "Authorization callback is missing 'code'");
68
+ }
69
+ return exchangeAuthorizationCode(issuer, code, {
70
+ codeVerifier: options.codeVerifier,
71
+ redirectUri: options.redirectUri,
72
+ clientId: options.clientId,
73
+ clientSecret: options.clientSecret,
74
+ metadata: options.metadata,
75
+ signal: options.signal,
76
+ });
77
+ }
78
+ /** Compare two strings without leaking their common prefix length via timing. */
79
+ function timingSafeEqual(a, b) {
80
+ const left = new TextEncoder().encode(a);
81
+ const right = new TextEncoder().encode(b);
82
+ let diff = left.length ^ right.length;
83
+ for (let i = 0; i < left.length; i++) {
84
+ diff |= left[i] ^ right[i % (right.length || 1)];
85
+ }
86
+ return diff === 0;
87
+ }
88
+ //# sourceMappingURL=webApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webApp.js","sourceRoot":"","sources":["../../src/webApp.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keycardai/oauth",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "[Preview] OAuth 2.0 primitives for Keycard: JWKS keyring, JWT signing/verification, server-tier token verifier, AccessContext, ClientSecret credentials, and impersonation via RFC 8693 token exchange",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -94,6 +94,16 @@
94
94
  "import": "./dist/esm/pkce.js",
95
95
  "require": "./dist/cjs/pkce.js",
96
96
  "types": "./dist/esm/pkce.d.ts"
97
+ },
98
+ "./webApp": {
99
+ "import": "./dist/esm/webApp.js",
100
+ "require": "./dist/cjs/webApp.js",
101
+ "types": "./dist/esm/webApp.d.ts"
102
+ },
103
+ "./userinfo": {
104
+ "import": "./dist/esm/userinfo.js",
105
+ "require": "./dist/cjs/userinfo.js",
106
+ "types": "./dist/esm/userinfo.d.ts"
97
107
  }
98
108
  },
99
109
  "files": [