@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.
- package/LICENSE +21 -0
- package/README.md +162 -2
- package/dist/src/_dedupCheck.d.ts +2 -0
- package/dist/src/_dedupCheck.d.ts.map +1 -0
- package/dist/src/_dedupCheck.js +28 -0
- package/dist/src/_dedupCheck.js.map +1 -0
- package/dist/src/browser/GGOAuthClient.d.ts +50 -0
- package/dist/src/browser/GGOAuthClient.d.ts.map +1 -0
- package/dist/src/browser/GGOAuthClient.js +152 -0
- package/dist/src/browser/GGOAuthClient.js.map +1 -0
- package/dist/src/index-browser.d.ts +6 -0
- package/dist/src/index-browser.d.ts.map +1 -0
- package/dist/src/index-browser.js +4 -0
- package/dist/src/index-browser.js.map +1 -0
- package/dist/src/index-node.d.ts +8 -0
- package/dist/src/index-node.d.ts.map +1 -0
- package/dist/src/index-node.js +5 -0
- package/dist/src/index-node.js.map +1 -0
- package/dist/src/node/GGOAuthServer.d.ts +72 -0
- package/dist/src/node/GGOAuthServer.d.ts.map +1 -0
- package/dist/src/node/GGOAuthServer.js +417 -0
- package/dist/src/node/GGOAuthServer.js.map +1 -0
- package/dist/src/node/store/DynamoOAuthStore.d.ts +28 -0
- package/dist/src/node/store/DynamoOAuthStore.d.ts.map +1 -0
- package/dist/src/node/store/DynamoOAuthStore.js +78 -0
- package/dist/src/node/store/DynamoOAuthStore.js.map +1 -0
- package/dist/src/node/store/GGOAuthStore.d.ts +18 -0
- package/dist/src/node/store/GGOAuthStore.d.ts.map +1 -0
- package/dist/src/node/store/GGOAuthStore.js +16 -0
- package/dist/src/node/store/GGOAuthStore.js.map +1 -0
- package/dist/src/node/store/InMemoryOAuthStore.d.ts +16 -0
- package/dist/src/node/store/InMemoryOAuthStore.d.ts.map +1 -0
- package/dist/src/node/store/InMemoryOAuthStore.js +44 -0
- package/dist/src/node/store/InMemoryOAuthStore.js.map +1 -0
- package/dist/src/shared/interactionContract.d.ts +77 -0
- package/dist/src/shared/interactionContract.d.ts.map +1 -0
- package/dist/src/shared/interactionContract.js +34 -0
- package/dist/src/shared/interactionContract.js.map +1 -0
- package/dist/src/tsconfig.json +18 -0
- package/dist/tsconfig.publish.tsbuildinfo +1 -0
- package/package.json +71 -4
- package/src/_dedupCheck.ts +29 -0
- package/src/browser/GGOAuthClient.test.ts +76 -0
- package/src/browser/GGOAuthClient.ts +187 -0
- package/src/index-browser.ts +5 -0
- package/src/index-node.ts +7 -0
- package/src/node/GGOAuthServer.test.ts +389 -0
- package/src/node/GGOAuthServer.ts +485 -0
- package/src/node/store/DynamoOAuthStore.ts +93 -0
- package/src/node/store/GGOAuthStore.ts +41 -0
- package/src/node/store/InMemoryOAuthStore.ts +54 -0
- package/src/shared/interactionContract.ts +39 -0
- 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
|
+
}
|