@grest-ts/oauth 0.0.0-stage → 0.0.89

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 +162 -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 +417 -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 +389 -0
  48. package/src/node/GGOAuthServer.ts +485 -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
@@ -0,0 +1,54 @@
1
+ import {errors as oidcErrors} from "oidc-provider"
2
+ import type {GGOAuthPayload, GGOAuthStore} from "./GGOAuthStore"
3
+
4
+ type Row = {model: string, id: string, payload: GGOAuthPayload, expiresAt: number | undefined}
5
+
6
+ // Single-process only (tests, local dev): state is lost on restart and not shared across instances.
7
+ export class InMemoryOAuthStore implements GGOAuthStore {
8
+
9
+ private readonly rows = new Map<string, Row>()
10
+
11
+ private readonly now: () => number
12
+
13
+ constructor(now: () => number = Date.now) {
14
+ this.now = now
15
+ }
16
+
17
+ public upsert = async (model: string, id: string, payload: GGOAuthPayload, expiresInSec: number | undefined): Promise<void> => {
18
+ this.rows.set(`${model}:${id}`, {model, id, payload, expiresAt: expiresInSec ? this.now() + expiresInSec * 1000 : undefined})
19
+ }
20
+
21
+ public find = async (model: string, id: string): Promise<GGOAuthPayload | undefined> => this.live(this.rows.get(`${model}:${id}`))?.payload
22
+
23
+ public findByUid = async (model: string, uid: string) => this.findWhere(model, p => p.uid === uid)
24
+
25
+ public consume = async (model: string, id: string): Promise<void> => {
26
+ const row = this.rows.get(`${model}:${id}`)
27
+ if (!row) {
28
+ if (model === "AuthorizationCode" || model === "RefreshToken") throw new oidcErrors.InvalidGrant(`${model} revoked`)
29
+ return
30
+ }
31
+ if (row.payload.consumed !== undefined) throw new oidcErrors.InvalidGrant(`${model} already used`)
32
+ row.payload = {...row.payload, consumed: Math.floor(this.now() / 1000)}
33
+ }
34
+
35
+ public destroy = async (model: string, id: string): Promise<void> => {
36
+ this.rows.delete(`${model}:${id}`)
37
+ }
38
+
39
+ public revokeByGrantId = async (grantId: string): Promise<void> => {
40
+ for (const [key, row] of this.rows) if (row.payload.grantId === grantId) this.rows.delete(key)
41
+ }
42
+
43
+ public listIdsByAccount = async (model: string, accountId: string): Promise<string[]> =>
44
+ [...this.rows.values()].filter(r => r.model === model && this.live(r) && r.payload.accountId === accountId).map(r => r.id)
45
+
46
+ private findWhere(model: string, match: (p: GGOAuthPayload) => boolean): GGOAuthPayload | undefined {
47
+ for (const row of this.rows.values()) if (row.model === model && this.live(row) && match(row.payload)) return row.payload
48
+ return undefined
49
+ }
50
+
51
+ private live(row: Row | undefined): Row | undefined {
52
+ return row && (row.expiresAt === undefined || row.expiresAt > this.now()) ? row : undefined
53
+ }
54
+ }
@@ -0,0 +1,39 @@
1
+ import {FORBIDDEN, GG_NO_PERMISSIONS, GGContractClass, IsArray, IsBoolean, IsObject, IsString, NOT_AUTHORIZED, NOT_FOUND, SERVER_ERROR} from "@grest-ts/schema"
2
+
3
+ export const IsGGOAuthInteractionClient = IsObject({
4
+ clientId: IsString,
5
+ name: IsString,
6
+ /** Pre-registered by the app (its own UI/CLI). Third-party clients must be shown a consent screen. */
7
+ firstParty: IsBoolean,
8
+ /** Hosts the authorization code is sent to — show these on the consent screen. */
9
+ redirectHosts: IsArray(IsString),
10
+ /** Every redirect is a loopback address: any local process could be posing as this client. */
11
+ loopbackOnly: IsBoolean,
12
+ })
13
+ export type GGOAuthInteractionClient = typeof IsGGOAuthInteractionClient.infer
14
+
15
+ export const IsGGOAuthInteraction = IsObject({
16
+ uid: IsString,
17
+ client: IsGGOAuthInteractionClient,
18
+ scopes: IsArray(IsString),
19
+ /** The resource (API) the client asks for access to. */
20
+ resource: IsString,
21
+ })
22
+ export type GGOAuthInteraction = typeof IsGGOAuthInteraction.infer
23
+
24
+ const IsUid = IsObject({uid: IsString.nonEmpty})
25
+ /** Where the browser must navigate next: back to the authorization server, which finishes the flow. */
26
+ const IsRedirect = IsObject({redirectUrl: IsString})
27
+
28
+ /**
29
+ * The app's login/consent page talks to this. Bind it with the app's own user wire — the
30
+ * signed-in user becomes the OAuth subject — e.g.
31
+ * new GGHttpSchema({contract: GGOAuthInteractionContract, pathPrefix: "api/oauth/interaction", use: [USER_WIRE], routes: {...}})
32
+ */
33
+ export const GGOAuthInteractionContract = new GGContractClass("GGOAuthInteraction", {
34
+ get: {input: IsUid, success: IsGGOAuthInteraction, errors: [NOT_FOUND, NOT_AUTHORIZED, SERVER_ERROR], permission: GG_NO_PERMISSIONS},
35
+ /** The signed-in user approves: the browser then follows redirectUrl to receive the code. */
36
+ approve: {input: IsUid, success: IsRedirect, errors: [NOT_FOUND, NOT_AUTHORIZED, FORBIDDEN, SERVER_ERROR], permission: GG_NO_PERMISSIONS},
37
+ /** The user declines: the client receives access_denied. */
38
+ deny: {input: IsUid, success: IsRedirect, errors: [NOT_FOUND, NOT_AUTHORIZED, SERVER_ERROR], permission: GG_NO_PERMISSIONS},
39
+ })
@@ -0,0 +1,18 @@
1
+ {
2
+ "//": "THIS FILE IS GENERATED - DO NOT EDIT",
3
+ "extends": "../../../../tsconfig.base.json",
4
+ "compilerOptions": {
5
+ "rootDir": ".",
6
+ "lib": [
7
+ "ES2022",
8
+ "DOM"
9
+ ],
10
+ "types": [
11
+ "node",
12
+ "vitest/globals"
13
+ ]
14
+ },
15
+ "include": [
16
+ "**/*"
17
+ ]
18
+ }