@grest-ts/oauth 0.0.0-stage → 0.0.90

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -2
  3. package/dist/src/_dedupCheck.d.ts +2 -0
  4. package/dist/src/_dedupCheck.d.ts.map +1 -0
  5. package/dist/src/_dedupCheck.js +28 -0
  6. package/dist/src/_dedupCheck.js.map +1 -0
  7. package/dist/src/browser/GGOAuthClient.d.ts +50 -0
  8. package/dist/src/browser/GGOAuthClient.d.ts.map +1 -0
  9. package/dist/src/browser/GGOAuthClient.js +152 -0
  10. package/dist/src/browser/GGOAuthClient.js.map +1 -0
  11. package/dist/src/index-browser.d.ts +6 -0
  12. package/dist/src/index-browser.d.ts.map +1 -0
  13. package/dist/src/index-browser.js +4 -0
  14. package/dist/src/index-browser.js.map +1 -0
  15. package/dist/src/index-node.d.ts +8 -0
  16. package/dist/src/index-node.d.ts.map +1 -0
  17. package/dist/src/index-node.js +5 -0
  18. package/dist/src/index-node.js.map +1 -0
  19. package/dist/src/node/GGOAuthServer.d.ts +72 -0
  20. package/dist/src/node/GGOAuthServer.d.ts.map +1 -0
  21. package/dist/src/node/GGOAuthServer.js +430 -0
  22. package/dist/src/node/GGOAuthServer.js.map +1 -0
  23. package/dist/src/node/store/DynamoOAuthStore.d.ts +28 -0
  24. package/dist/src/node/store/DynamoOAuthStore.d.ts.map +1 -0
  25. package/dist/src/node/store/DynamoOAuthStore.js +78 -0
  26. package/dist/src/node/store/DynamoOAuthStore.js.map +1 -0
  27. package/dist/src/node/store/GGOAuthStore.d.ts +18 -0
  28. package/dist/src/node/store/GGOAuthStore.d.ts.map +1 -0
  29. package/dist/src/node/store/GGOAuthStore.js +16 -0
  30. package/dist/src/node/store/GGOAuthStore.js.map +1 -0
  31. package/dist/src/node/store/InMemoryOAuthStore.d.ts +16 -0
  32. package/dist/src/node/store/InMemoryOAuthStore.d.ts.map +1 -0
  33. package/dist/src/node/store/InMemoryOAuthStore.js +44 -0
  34. package/dist/src/node/store/InMemoryOAuthStore.js.map +1 -0
  35. package/dist/src/shared/interactionContract.d.ts +77 -0
  36. package/dist/src/shared/interactionContract.d.ts.map +1 -0
  37. package/dist/src/shared/interactionContract.js +34 -0
  38. package/dist/src/shared/interactionContract.js.map +1 -0
  39. package/dist/src/tsconfig.json +18 -0
  40. package/dist/tsconfig.publish.tsbuildinfo +1 -0
  41. package/package.json +71 -4
  42. package/src/_dedupCheck.ts +29 -0
  43. package/src/browser/GGOAuthClient.test.ts +76 -0
  44. package/src/browser/GGOAuthClient.ts +187 -0
  45. package/src/index-browser.ts +5 -0
  46. package/src/index-node.ts +7 -0
  47. package/src/node/GGOAuthServer.test.ts +416 -0
  48. package/src/node/GGOAuthServer.ts +500 -0
  49. package/src/node/store/DynamoOAuthStore.ts +93 -0
  50. package/src/node/store/GGOAuthStore.ts +41 -0
  51. package/src/node/store/InMemoryOAuthStore.ts +54 -0
  52. package/src/shared/interactionContract.ts +39 -0
  53. package/src/tsconfig.json +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Grest Games OÜ
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 CHANGED
@@ -1,3 +1,166 @@
1
- # Temporary Holding Version
1
+ <!-- GREST-TS-BANNER-START -->
2
+ > Part of the [grest-ts](https://github.com/grest-ts/grest-ts) framework.
3
+ > [Documentation](https://github.com/grest-ts/grest-ts#readme) | [All packages](https://github.com/grest-ts/grest-ts#package-reference)
4
+ <!-- GREST-TS-BANNER-END -->
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @grest-ts/oauth
7
+
8
+ Make a grest-ts app its own **OAuth 2.1 authorization server** with this package. The token a
9
+ client receives is your app's existing [`@grest-ts/auth`](../auth/README.md) access token: the same
10
+ signer, audience and claims. Your wires and per-route permissions work unchanged for OAuth
11
+ clients, whether that is your own SPA, an MCP connector (Claude, ChatGPT), a CLI or a script.
12
+
13
+ The protocol is [panva/oidc-provider](https://github.com/panva/node-oidc-provider)
14
+ (OpenID-certified). This package owns the grest-ts seams:
15
+
16
+ - mounting on a `GGHttpServer`
17
+ - storage
18
+ - token claims
19
+ - handing login and consent to your own UI
20
+
21
+ | What you get | |
22
+ |---|---|
23
+ | Authorization code + PKCE (S256 only, required) | No implicit/hybrid, no ROPC |
24
+ | Dynamic client registration (RFC 7591), Client ID Metadata Documents | Opt-in. CIMD is what MCP clients prefer |
25
+ | Resource indicators (RFC 8707), `iss` in responses (RFC 9207) | What the MCP authorization spec requires |
26
+ | Refresh rotation with reuse detection | Absolute lifetime: rotation never extends it |
27
+ | Revocation, RFC 8414 + OIDC discovery, RP-initiated logout | |
28
+
29
+ ## Server
30
+
31
+ A server issues tokens for one or more **resources** (RFC 8707). Each resource has its own token
32
+ kind: audience, claims and TTL. A token for one resource is therefore rejected by another's wire.
33
+ Every resource must set its own `audience` (the constructor refuses otherwise): with a shared signer,
34
+ the audience is the only thing that tells the tokens apart.
35
+
36
+ ```ts
37
+ import {GGOAuthServer, type GGOAuthResource} from "@grest-ts/oauth"
38
+ import {DynamoOAuthStore} from "@grest-ts/oauth/dynamodb"
39
+
40
+ // Your own app's API: its ordinary user token, only for your own clients.
41
+ const api: GGOAuthResource<UserClaims> = {
42
+ resource: () => config.issuerUrl.get(),
43
+ accessToken: userTokenEngine.access, // GGAuthAccessToken — signer must expose jwsKey()
44
+ scopes: ["app"], // at least one; what they permit is up to you
45
+ firstPartyOnly: true,
46
+ claims: async (subject, client) => claimsFor(subject), // at issue AND every refresh; undefined → invalid_grant
47
+ }
48
+ // An MCP endpoint for other apps (Claude, ChatGPT…): own audience, its own (smaller) claims.
49
+ const mcp: GGOAuthResource<McpClaims> = {
50
+ resource: () => `${config.issuerUrl.get()}/mcp`, // exactly the URL users enter
51
+ accessToken: mcpToken, // e.g. audience "app-mcp"
52
+ scopes: ["mcp"],
53
+ claims: async (subject) => ({permissions: []}),
54
+ }
55
+
56
+ const oauth = new GGOAuthServer({
57
+ issuer: () => config.issuerUrl.get(), // public origin, no path
58
+ store: new DynamoOAuthStore(dynamo, "my-oauth"),
59
+ secret: () => config.tokenSecret.reveal(), // derives cookie + JWKS keys
60
+ resources: [api, mcp], // first = default for first-party clients
61
+ interactionUrl: (uid, {firstParty, redirectUri}) => `${appUrl}/?oauthLogin=${uid}`,
62
+ firstPartyClients: () => [{clientId: "web", name: "My App", redirectUris: [`${appUrl}/oauth/callback`]}],
63
+ clientIdMetadataDocument: {allowedUrlPrefixes: () => ["https://claude.ai/oauth/", "https://chatgpt.com/oauth/"]},
64
+ // dynamicRegistration: true, // open RFC 7591 registration — anyone, any name
65
+ })
66
+ oauth.mount(httpServer) // /oauth/* + /.well-known/*, announced to discovery
67
+ oauth.protect(httpServer, mcp, "/mcp", handleMcp) // 401 + resource metadata, then your handler
68
+ new GGHttp(httpServer).http(OAuthInteractionApi, oauth.interactionApi(() => USER.get()!.id))
69
+ ```
70
+
71
+ `claims` works like the `resolve` callback of `GGAuthRefreshToken.refresh`: role changes apply on
72
+ the next refresh, and a deleted or deactivated user's OAuth session ends there.
73
+
74
+ `listGrants(subject)` and `revokeGrant(subject, grantId)` back a "connected apps" page.
75
+ `revokeAll(subject)` is "log out everywhere"; call it next to `GGAuthRefreshToken.revokeAll`.
76
+
77
+ ### Protected resources (MCP)
78
+
79
+ `protect(server, resource, path, handler)` puts `path` behind the resource's token. It also
80
+ provides the discovery handshake MCP clients run:
81
+
82
+ - A request without a valid token gets `401 WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource{path}", scope="…"`.
83
+ - That metadata (RFC 9728) names this server and the resource's scopes.
84
+ - Your handler receives the verified token payload. For example, run the official MCP SDK's
85
+ `StreamableHTTPServerTransport` on the raw `req`/`res`.
86
+
87
+ ### Which clients may sign in
88
+
89
+ | Client | How it's known | Consent |
90
+ |---|---|---|
91
+ | First-party (`firstPartyClients`) | Pre-registered | None; silent while the login session lives |
92
+ | Client ID Metadata Document under `allowedUrlPrefixes` (https, default port) | `client_id` is its document URL, fetched by the server | Once per grant |
93
+ | Loopback / CLI (no `application_type` declared → native) | Any port on `localhost`/`127.0.0.1` matches | **Every** authorization: any local process could claim the redirect |
94
+ | Dynamic registration (opt-in) | `POST /oauth/register` | Once per grant |
95
+
96
+ ### Login and consent stay in your app
97
+
98
+ `/oauth/authorize` sends the browser to `interactionUrl(uid)`. That page uses your normal login,
99
+ then calls `GGOAuthInteractionContract`. Bind the contract with **your** user wire, so the
100
+ signed-in user becomes the OAuth subject:
101
+
102
+ ```ts
103
+ export const OAuthInteractionApi = new GGHttpSchema({
104
+ contract: GGOAuthInteractionContract, pathPrefix: "api/oauth/interaction", use: [USER_WIRE],
105
+ routes: {get: GGRpc.GET("get"), approve: GGRpc.POST("approve"), deny: GGRpc.POST("deny")},
106
+ })
107
+ ```
108
+
109
+ The page works like this:
110
+
111
+ 1. `get({uid})` returns the client (name, `firstParty`, redirect hosts, `loopbackOnly`) and the
112
+ requested scopes.
113
+ 2. For a first-party client, call `approve` right away. For any other client, show a consent
114
+ screen first. Display the redirect hosts, and warn when `loopbackOnly` is set.
115
+ 3. `approve`/`deny` return `redirectUrl` for the browser to follow. The result applies only in
116
+ the browser holding the flow's interaction cookie, so a user approving someone else's flow
117
+ can't hand them a code.
118
+
119
+ A first-party client re-authorizes silently while the login session lives.
120
+
121
+ ### Storage
122
+
123
+ `GGOAuthStore` is a small key/value interface over oidc-provider's models. Two implementations
124
+ ship:
125
+
126
+ - `InMemoryOAuthStore`: single process only (tests, local dev).
127
+ - `DynamoOAuthStore` (`@grest-ts/oauth/dynamodb`). Its table needs:
128
+ - HASH key `pk` (S)
129
+ - TTL attribute `expiresAtSec`
130
+ - GSIs projecting ALL: `grantId-index`, `uid-index`, `accountId-index`
131
+
132
+ ## Browser client
133
+
134
+ ```ts
135
+ import {GGOAuthClient, decodeAccessToken} from "@grest-ts/oauth"
136
+
137
+ const oauth = new GGOAuthClient({
138
+ server: apiBase, clientId: "web", scope: "app",
139
+ redirectUri: `${location.origin}/oauth/callback`, postLogoutRedirectUri: `${location.origin}/`,
140
+ identity: (token) => toUser(decodeAccessToken(token)), // session.get() data; UI-only, unverified
141
+ })
142
+ const session = GGAuthSession.withToken(USER_WIRE, {localStorageKey: "auth", refresh: oauth.refresh})
143
+
144
+ if (oauth.isCallback(location.href)) session.start((await oauth.handleCallback(location.href)).result)
145
+ else if (!session.isLoggedIn()) await oauth.authorize({returnTo: location.pathname + location.search})
146
+
147
+ oauth.logout(undefined) // ends the authorization server's login session too
148
+ ```
149
+
150
+ - `iss` is checked against the issuer in the server's metadata.
151
+ - `resource` defaults to that issuer.
152
+ - `invalid_grant` on refresh surfaces as `NOT_AUTHORIZED`, which ends the session.
153
+
154
+ ## Notes
155
+
156
+ - The issuer, first-party clients, keys and resources are handed to oidc-provider when the first
157
+ OAuth request arrives. Changing them takes a restart; `allowedUrlPrefixes` is read on every fetch.
158
+ - A sign-in finishes on the origin it started on only if that is the issuer or one of `origins`.
159
+ Request hosts are client-controlled, so anything else falls back to the issuer.
160
+ - The issuer is an origin. Clients bind to it, so changing it disconnects them.
161
+ - Endpoints in the metadata follow the request host. Serve the issuer host directly or keep
162
+ `Host` through your proxy, and pass `proxy: true` behind a TLS-terminating load balancer (it trusts
163
+ `X-Forwarded-Proto`; `X-Forwarded-Host` is always ignored — clients can send it).
164
+ - oidc-provider's CIMD support is experimental (draft-02). The version is pinned exactly.
165
+ - The OAuth endpoints use the standard form-encoded protocol, not grest-ts contracts. That is the
166
+ one place this package speaks raw HTTP.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_dedupCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dedupCheck.d.ts","sourceRoot":"","sources":["../../src/_dedupCheck.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ // =============================================================================
2
+ // THIS FILE IS GENERATED - DO NOT EDIT
3
+ //
4
+ // Detects duplicate loads of @grest-ts/oauth. Two copies of a grest-ts
5
+ // framework package in the same process break async context, validation
6
+ // registries, and service discovery. Throw with both paths so the packaging
7
+ // mistake is immediately diagnosable.
8
+ //
9
+ // Generated by scripts/packager — re-run `npm run generate` to refresh.
10
+ // =============================================================================
11
+ const __KEY = Symbol.for("grest-ts:dedup:@grest-ts/oauth");
12
+ const __slot = globalThis;
13
+ const __existing = __slot[__KEY];
14
+ const __here = { path: import.meta.url, version: "0.0.90" };
15
+ // Same path + same version means the same physical package is being
16
+ // re-evaluated by a different ESM runner (e.g. vitest projects share one
17
+ // process but each project has its own module cache). That isn't a true
18
+ // duplicate package — only differing paths or versions are.
19
+ if (__existing && (__existing.path !== __here.path || __existing.version !== __here.version)) {
20
+ throw new Error("[@grest-ts/oauth] Duplicate package load detected.\n" +
21
+ " Already loaded: " + __existing.path + " (v" + __existing.version + ")\n" +
22
+ " Now loading: " + __here.path + " (v" + __here.version + ")\n" +
23
+ "Two copies of grest-ts framework packages in one process break\n" +
24
+ "async context, validation, and service discovery. Fix the packaging.");
25
+ }
26
+ __slot[__KEY] = __here;
27
+ export {};
28
+ //# sourceMappingURL=_dedupCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dedupCheck.js","sourceRoot":"","sources":["../../src/_dedupCheck.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,uCAAuC;AACvC,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,4EAA4E;AAC5E,sCAAsC;AACtC,EAAE;AACF,wEAAwE;AACxE,gFAAgF;AAEhF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;AAC3D,MAAM,MAAM,GAAG,UAAqC,CAAC;AACrD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAkD,CAAC;AAClF,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC5D,oEAAoE;AACpE,yEAAyE;AACzE,wEAAwE;AACxE,4DAA4D;AAC5D,IAAI,UAAU,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3F,MAAM,IAAI,KAAK,CACX,sDAAsD;QACtD,oBAAoB,GAAG,UAAU,CAAC,IAAI,GAAG,KAAK,GAAG,UAAU,CAAC,OAAO,GAAG,KAAK;QAC3E,oBAAoB,GAAG,MAAM,CAAC,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,OAAO,GAAG,KAAK;QACnE,kEAAkE;QAClE,sEAAsE,CACzE,CAAC;AACN,CAAC;AACD,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC"}
@@ -0,0 +1,50 @@
1
+ export interface GGOAuthResult<I> {
2
+ tokens: {
3
+ access: {
4
+ token: string;
5
+ expiresAt: number;
6
+ };
7
+ refresh?: {
8
+ token: string;
9
+ expiresAt: number;
10
+ };
11
+ };
12
+ data: I;
13
+ }
14
+ export interface GGOAuthClientConfig<I> {
15
+ server: string;
16
+ clientId: string;
17
+ redirectUri: string;
18
+ postLogoutRedirectUri?: string;
19
+ resource?: string;
20
+ scope?: string;
21
+ identity: (accessToken: string) => I;
22
+ refreshTtlMs?: number;
23
+ }
24
+ export declare class GGOAuthClient<I = unknown> {
25
+ private metadata;
26
+ private leaving;
27
+ private readonly config;
28
+ constructor(config: GGOAuthClientConfig<I>);
29
+ authorize: (opts?: {
30
+ returnTo?: string;
31
+ }) => Promise<void>;
32
+ isCallback: (href: string) => boolean;
33
+ handleCallback: (href: string) => Promise<{
34
+ result: GGOAuthResult<I>;
35
+ returnTo?: string;
36
+ }>;
37
+ refresh: ({ refreshToken }: {
38
+ refreshToken: string;
39
+ }) => Promise<GGOAuthResult<I>>;
40
+ logout: (refreshToken: string | undefined) => Promise<void>;
41
+ private leave;
42
+ private token;
43
+ private discover;
44
+ }
45
+ export declare function decodeAccessToken<T>(accessToken: string): {
46
+ sub: string;
47
+ exp: number;
48
+ data: T;
49
+ };
50
+ //# sourceMappingURL=GGOAuthClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GGOAuthClient.d.ts","sourceRoot":"","sources":["../../../src/browser/GGOAuthClient.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa,CAAC,CAAC;IAC5B,MAAM,EAAE;QAAC,MAAM,EAAE;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAC,CAAC;QAAC,OAAO,CAAC,EAAE;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAC,CAAA;KAAC,CAAA;IAClG,IAAI,EAAE,CAAC,CAAA;CACV;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IAGlC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAI9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,CAAC,CAAA;IAGpC,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AASD,qBAAa,aAAa,CAAC,CAAC,GAAG,OAAO;IAElC,OAAO,CAAC,QAAQ,CAA+B;IAG/C,OAAO,CAAC,OAAO,CAA2B;IAE1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAEnC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAKnC,SAAS,GAAI,OAAO;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB5D;IAEK,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,CAI1C;IAEM,cAAc,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC;QAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAkBnG;IAEM,OAAO,GAAU,kBAAgB;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAChB;IAInE,MAAM,GAAI,cAAc,MAAM,GAAG,SAAS,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB/D;YAEY,KAAK;YAkBL,KAAK;IAuBnB,OAAO,CAAC,QAAQ;CAQnB;AAID,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAC,CAE7F"}
@@ -0,0 +1,152 @@
1
+ /// <reference lib="dom" />
2
+ import { decodeJwt } from "jose";
3
+ import { NOT_AUTHORIZED } from "@grest-ts/schema";
4
+ const PENDING_KEY = "gg-oauth-pending";
5
+ // Browser OAuth client (authorization code + PKCE S256), the `refresh` of a GGAuthSession — see README.
6
+ // The token/revocation endpoints are OAuth protocol endpoints (form-encoded), not grest-ts contracts.
7
+ export class GGOAuthClient {
8
+ metadata;
9
+ // The redirect away being prepared: a second one (e.g. a signed-out view starting sign-in while
10
+ // logout redirects) waits for it — forever if it navigates, and takes over if it fails.
11
+ leaving;
12
+ config;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ // Redirects the browser to the authorization server; resolves never (the page unloads).
17
+ authorize = (opts) => this.leave(async () => {
18
+ const meta = await this.discover();
19
+ const verifier = randomString();
20
+ const pending = { state: randomString(), verifier, returnTo: opts?.returnTo };
21
+ sessionStorage.setItem(PENDING_KEY, JSON.stringify(pending));
22
+ const url = new URL(meta.authorization_endpoint);
23
+ url.search = new URLSearchParams({
24
+ response_type: "code",
25
+ client_id: this.config.clientId,
26
+ redirect_uri: this.config.redirectUri,
27
+ state: pending.state,
28
+ code_challenge: await s256(verifier),
29
+ code_challenge_method: "S256",
30
+ resource: this.config.resource ?? meta.issuer,
31
+ ...(this.config.scope ? { scope: this.config.scope } : {}),
32
+ }).toString();
33
+ return url.href;
34
+ });
35
+ isCallback = (href) => {
36
+ const url = new URL(href);
37
+ return url.origin + url.pathname === new URL(this.config.redirectUri).href.replace(/[?#].*$/, "")
38
+ && (url.searchParams.has("code") || url.searchParams.has("error"));
39
+ };
40
+ handleCallback = async (href) => {
41
+ const params = new URL(href).searchParams;
42
+ const raw = sessionStorage.getItem(PENDING_KEY);
43
+ sessionStorage.removeItem(PENDING_KEY);
44
+ const pending = raw ? JSON.parse(raw) : undefined;
45
+ if (!pending || params.get("state") !== pending.state)
46
+ throw new NOT_AUTHORIZED({ debugMessage: "OAuth state mismatch" });
47
+ // RFC 9207: reject a response from a different authorization server (mix-up attack).
48
+ const iss = params.get("iss");
49
+ if (iss !== null && iss !== (await this.discover()).issuer)
50
+ throw new NOT_AUTHORIZED({ debugMessage: "OAuth issuer mismatch" });
51
+ const error = params.get("error");
52
+ if (error)
53
+ throw new NOT_AUTHORIZED({ debugMessage: `OAuth ${error}: ${params.get("error_description") ?? ""}` });
54
+ const result = await this.token({
55
+ grant_type: "authorization_code",
56
+ code: params.get("code") ?? "",
57
+ redirect_uri: this.config.redirectUri,
58
+ code_verifier: pending.verifier,
59
+ });
60
+ return { result, returnTo: pending.returnTo };
61
+ };
62
+ refresh = async ({ refreshToken }) => this.token({ grant_type: "refresh_token", refresh_token: refreshToken });
63
+ // Revokes the refresh token and ends the authorization server's login session (redirects away
64
+ // when the server supports it).
65
+ logout = (refreshToken) => this.leave(async () => {
66
+ const meta = await this.discover();
67
+ const revoke = refreshToken && meta.revocation_endpoint
68
+ ? fetch(meta.revocation_endpoint, { method: "POST", keepalive: true, signal: AbortSignal.timeout(5000), body: new URLSearchParams({ token: refreshToken, client_id: this.config.clientId }) }).then(() => undefined, () => undefined)
69
+ : Promise.resolve();
70
+ if (!meta.end_session_endpoint) {
71
+ await revoke;
72
+ return undefined;
73
+ }
74
+ // Revocation not awaited (keepalive outlives the navigation): the sooner the redirect, the
75
+ // shorter the window in which other tabs could still sign in silently.
76
+ const url = new URL(meta.end_session_endpoint);
77
+ url.search = new URLSearchParams({
78
+ client_id: this.config.clientId,
79
+ ...(this.config.postLogoutRedirectUri ? { post_logout_redirect_uri: this.config.postLogoutRedirectUri } : {}),
80
+ }).toString();
81
+ return url.href;
82
+ });
83
+ async leave(target) {
84
+ while (this.leaving)
85
+ await this.leaving.catch(() => undefined);
86
+ const run = (async () => {
87
+ const url = await target();
88
+ if (url === undefined)
89
+ return;
90
+ // Back from the back/forward cache (navigation undone): the redirect is over.
91
+ window.addEventListener("pageshow", () => { if (this.leaving === run)
92
+ this.leaving = undefined; }, { once: true });
93
+ window.location.assign(url);
94
+ await new Promise(() => undefined);
95
+ })();
96
+ this.leaving = run;
97
+ try {
98
+ await run;
99
+ }
100
+ finally {
101
+ if (this.leaving === run)
102
+ this.leaving = undefined;
103
+ }
104
+ }
105
+ async token(body) {
106
+ const meta = await this.discover();
107
+ const res = await fetch(meta.token_endpoint, {
108
+ method: "POST",
109
+ body: new URLSearchParams({ ...body, client_id: this.config.clientId, resource: this.config.resource ?? meta.issuer }),
110
+ });
111
+ const json = await res.json().catch(() => ({}));
112
+ if (!res.ok) {
113
+ // invalid_grant = the refresh/code is dead → fatal for the session; anything else may be transient.
114
+ if (res.status === 400 || res.status === 401)
115
+ throw new NOT_AUTHORIZED({ debugMessage: `OAuth ${String(json.error)}` });
116
+ throw new Error(`OAuth token endpoint ${res.status}`);
117
+ }
118
+ const now = Date.now();
119
+ const access = String(json.access_token);
120
+ return {
121
+ tokens: {
122
+ access: { token: access, expiresAt: now + Number(json.expires_in) * 1000 },
123
+ ...(json.refresh_token ? { refresh: { token: String(json.refresh_token), expiresAt: now + (this.config.refreshTtlMs ?? 30 * 24 * 3600 * 1000) } } : {}),
124
+ },
125
+ data: this.config.identity(access),
126
+ };
127
+ }
128
+ discover() {
129
+ this.metadata ??= fetch(`${this.config.server}/.well-known/oauth-authorization-server`).then(async (res) => {
130
+ if (!res.ok)
131
+ throw new Error(`OAuth discovery failed: ${res.status}`);
132
+ return await res.json();
133
+ });
134
+ this.metadata.catch(() => { this.metadata = undefined; });
135
+ return this.metadata;
136
+ }
137
+ }
138
+ // A grest-ts access token's payload, unverified — for UI identity only; the server re-verifies
139
+ // every request.
140
+ export function decodeAccessToken(accessToken) {
141
+ return decodeJwt(accessToken);
142
+ }
143
+ function randomString() {
144
+ return base64url(crypto.getRandomValues(new Uint8Array(32)));
145
+ }
146
+ async function s256(verifier) {
147
+ return base64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
148
+ }
149
+ function base64url(bytes) {
150
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
151
+ }
152
+ //# sourceMappingURL=GGOAuthClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GGOAuthClient.js","sourceRoot":"","sources":["../../../src/browser/GGOAuthClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import "./_dedupCheck";
2
+ export { GGOAuthClient, decodeAccessToken } from "./browser/GGOAuthClient";
3
+ export type { GGOAuthClientConfig, GGOAuthResult } from "./browser/GGOAuthClient";
4
+ export { GGOAuthInteractionContract, IsGGOAuthInteraction, IsGGOAuthInteractionClient } from "./shared/interactionContract";
5
+ export type { GGOAuthInteraction, GGOAuthInteractionClient } from "./shared/interactionContract";
6
+ //# sourceMappingURL=index-browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-browser.d.ts","sourceRoot":"","sources":["../../src/index-browser.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,yBAAyB,CAAA;AACxE,YAAY,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAC/E,OAAO,EAAC,0BAA0B,EAAE,oBAAoB,EAAE,0BAA0B,EAAC,MAAM,8BAA8B,CAAA;AACzH,YAAY,EAAC,kBAAkB,EAAE,wBAAwB,EAAC,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,4 @@
1
+ import "./_dedupCheck.js";
2
+ export { GGOAuthClient, decodeAccessToken } from "./browser/GGOAuthClient.js";
3
+ export { GGOAuthInteractionContract, IsGGOAuthInteraction, IsGGOAuthInteractionClient } from "./shared/interactionContract.js";
4
+ //# sourceMappingURL=index-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-browser.js","sourceRoot":"","sources":["../../src/index-browser.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,yBAAyB,CAAA;AAExE,OAAO,EAAC,0BAA0B,EAAE,oBAAoB,EAAE,0BAA0B,EAAC,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,8 @@
1
+ import "./_dedupCheck";
2
+ export { GGOAuthServer } from "./node/GGOAuthServer";
3
+ export type { GGOAuthServerOptions, GGOAuthFirstPartyClient, GGOAuthResource } from "./node/GGOAuthServer";
4
+ export type { GGOAuthStore, GGOAuthPayload } from "./node/store/GGOAuthStore";
5
+ export { InMemoryOAuthStore } from "./node/store/InMemoryOAuthStore";
6
+ export { GGOAuthInteractionContract, IsGGOAuthInteraction, IsGGOAuthInteractionClient } from "./shared/interactionContract";
7
+ export type { GGOAuthInteraction, GGOAuthInteractionClient } from "./shared/interactionContract";
8
+ //# sourceMappingURL=index-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-node.d.ts","sourceRoot":"","sources":["../../src/index-node.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAClD,YAAY,EAAC,oBAAoB,EAAE,uBAAuB,EAAE,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACxG,YAAY,EAAC,YAAY,EAAE,cAAc,EAAC,MAAM,2BAA2B,CAAA;AAC3E,OAAO,EAAC,kBAAkB,EAAC,MAAM,iCAAiC,CAAA;AAClE,OAAO,EAAC,0BAA0B,EAAE,oBAAoB,EAAE,0BAA0B,EAAC,MAAM,8BAA8B,CAAA;AACzH,YAAY,EAAC,kBAAkB,EAAE,wBAAwB,EAAC,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,5 @@
1
+ import "./_dedupCheck.js";
2
+ export { GGOAuthServer } from "./node/GGOAuthServer.js";
3
+ export { InMemoryOAuthStore } from "./node/store/InMemoryOAuthStore.js";
4
+ export { GGOAuthInteractionContract, IsGGOAuthInteraction, IsGGOAuthInteractionClient } from "./shared/interactionContract.js";
5
+ //# sourceMappingURL=index-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-node.js","sourceRoot":"","sources":["../../src/index-node.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAA;AAGlD,OAAO,EAAC,kBAAkB,EAAC,MAAM,iCAAiC,CAAA;AAClE,OAAO,EAAC,0BAA0B,EAAE,oBAAoB,EAAE,0BAA0B,EAAC,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,72 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { GGHttpServer } from "@grest-ts/http";
3
+ import type { GGAccessPayload, GGAuthAccessToken } from "@grest-ts/auth";
4
+ import { GGOAuthInteractionContract } from "../shared/interactionContract";
5
+ import { type GGOAuthStore } from "./store/GGOAuthStore";
6
+ export interface GGOAuthFirstPartyClient {
7
+ clientId: string;
8
+ name: string;
9
+ redirectUris: string[];
10
+ postLogoutRedirectUris?: string[];
11
+ }
12
+ export interface GGOAuthResource<C extends object> {
13
+ resource: () => string;
14
+ accessToken: GGAuthAccessToken<C>;
15
+ scopes: [string, ...string[]];
16
+ claims: (subject: string, client: {
17
+ clientId: string;
18
+ name: string;
19
+ }) => Promise<C | undefined>;
20
+ firstPartyOnly?: boolean;
21
+ }
22
+ export interface GGOAuthServerOptions {
23
+ issuer: () => string;
24
+ origins?: () => string[];
25
+ store: GGOAuthStore;
26
+ secret: () => string;
27
+ resources: [GGOAuthResource<any>, ...GGOAuthResource<any>[]];
28
+ interactionUrl: (uid: string, request: {
29
+ redirectUri: string;
30
+ firstParty: boolean;
31
+ }) => string;
32
+ firstPartyClients?: () => GGOAuthFirstPartyClient[];
33
+ dynamicRegistration?: boolean;
34
+ clientIdMetadataDocument?: {
35
+ allowedUrlPrefixes: () => string[];
36
+ };
37
+ refreshTtlSec?: number;
38
+ sessionTtlSec?: number;
39
+ proxy?: boolean;
40
+ }
41
+ export declare class GGOAuthServer {
42
+ private _provider;
43
+ private _callback;
44
+ private _firstParty;
45
+ private readonly options;
46
+ constructor(options: GGOAuthServerOptions);
47
+ private get firstParty();
48
+ mount(server: GGHttpServer): void;
49
+ protect<C extends object>(server: GGHttpServer, resource: GGOAuthResource<C>, path: string, handle: (req: IncomingMessage, res: ServerResponse, token: GGAccessPayload<C>) => Promise<void>): void;
50
+ interactionApi(subject: () => string): typeof GGOAuthInteractionContract.infer;
51
+ listGrants: (subject: string) => Promise<Array<{
52
+ grantId: string;
53
+ clientId: string;
54
+ clientName: string;
55
+ createdAt: number;
56
+ }>>;
57
+ revokeGrant: (subject: string, grantId: string) => Promise<void>;
58
+ revokeAll: (subject: string) => Promise<void>;
59
+ private describeInteraction;
60
+ private approve;
61
+ private findInteraction;
62
+ private ticket;
63
+ private finishInteraction;
64
+ private grantRequested;
65
+ private requestedResource;
66
+ private resource;
67
+ private clientOf;
68
+ private usableResource;
69
+ private defaultResource;
70
+ private provider;
71
+ }
72
+ //# sourceMappingURL=GGOAuthServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GGOAuthServer.d.ts","sourceRoot":"","sources":["../../../src/node/GGOAuthServer.ts"],"names":[],"mappings":"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"}