@trokky/trokky 3.5.1 → 3.5.2
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/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.d.ts +2 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.d.ts.map +1 -1
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.js +17 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.js.map +1 -1
- package/dist/adapters/filesystem-data/filesystem-data-adapter.d.ts +1 -0
- package/dist/adapters/filesystem-data/filesystem-data-adapter.d.ts.map +1 -1
- package/dist/adapters/filesystem-data/filesystem-data-adapter.js +24 -1
- package/dist/adapters/filesystem-data/filesystem-data-adapter.js.map +1 -1
- package/dist/adapters/postgres-data/postgres-data-adapter.d.ts +2 -0
- package/dist/adapters/postgres-data/postgres-data-adapter.d.ts.map +1 -1
- package/dist/adapters/postgres-data/postgres-data-adapter.js +24 -0
- package/dist/adapters/postgres-data/postgres-data-adapter.js.map +1 -1
- package/dist/core/core/engine.d.ts +10 -1
- package/dist/core/core/engine.d.ts.map +1 -1
- package/dist/core/core/engine.js +34 -4
- package/dist/core/core/engine.js.map +1 -1
- package/dist/core/security/auth-flow-store.d.ts +69 -0
- package/dist/core/security/auth-flow-store.d.ts.map +1 -0
- package/dist/core/security/auth-flow-store.js +145 -0
- package/dist/core/security/auth-flow-store.js.map +1 -0
- package/dist/core/security/oauth2/authorization-server.d.ts +68 -13
- package/dist/core/security/oauth2/authorization-server.d.ts.map +1 -1
- package/dist/core/security/oauth2/authorization-server.js +161 -151
- package/dist/core/security/oauth2/authorization-server.js.map +1 -1
- package/dist/core/security/oauth2/scoped-permissions.d.ts +15 -0
- package/dist/core/security/oauth2/scoped-permissions.d.ts.map +1 -0
- package/dist/core/security/oauth2/scoped-permissions.js +53 -0
- package/dist/core/security/oauth2/scoped-permissions.js.map +1 -0
- package/dist/core/services/auth-service.d.ts +4 -0
- package/dist/core/services/auth-service.d.ts.map +1 -1
- package/dist/core/services/auth-service.js +63 -5
- package/dist/core/services/auth-service.js.map +1 -1
- package/dist/core/services/document-service.d.ts.map +1 -1
- package/dist/core/services/document-service.js +5 -1
- package/dist/core/services/document-service.js.map +1 -1
- package/dist/core/services/oauth2-server-service.d.ts +48 -1
- package/dist/core/services/oauth2-server-service.d.ts.map +1 -1
- package/dist/core/services/oauth2-server-service.js +170 -10
- package/dist/core/services/oauth2-server-service.js.map +1 -1
- package/dist/core/types/index.d.ts +4 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js.map +1 -1
- package/dist/core/types/oauth2.d.ts +1 -1
- package/dist/core/types/oauth2.d.ts.map +1 -1
- package/dist/core/types/oauth2.js +1 -1
- package/dist/core/types/oauth2.js.map +1 -1
- package/dist/core/types/storage-adapters.d.ts +11 -1
- package/dist/core/types/storage-adapters.d.ts.map +1 -1
- package/dist/i18n/config.d.ts +34 -0
- package/dist/i18n/config.d.ts.map +1 -1
- package/dist/i18n/locales/en/studio.json +18 -1
- package/dist/i18n/locales/fr/studio.json +18 -1
- package/dist/integrations/express/config.d.ts +12 -0
- package/dist/integrations/express/config.d.ts.map +1 -1
- package/dist/integrations/express/config.js.map +1 -1
- package/dist/routes/auth/auth-flow-store.d.ts +3 -59
- package/dist/routes/auth/auth-flow-store.d.ts.map +1 -1
- package/dist/routes/auth/auth-flow-store.js +3 -119
- package/dist/routes/auth/auth-flow-store.js.map +1 -1
- package/dist/routes/auth/oauth.d.ts.map +1 -1
- package/dist/routes/auth/oauth.js +4 -2
- package/dist/routes/auth/oauth.js.map +1 -1
- package/dist/routes/auth/oauth2-server.d.ts.map +1 -1
- package/dist/routes/auth/oauth2-server.js +28 -8
- package/dist/routes/auth/oauth2-server.js.map +1 -1
- package/dist/routes/handlers/audit.d.ts +6 -0
- package/dist/routes/handlers/audit.d.ts.map +1 -1
- package/dist/routes/handlers/audit.js +19 -17
- package/dist/routes/handlers/audit.js.map +1 -1
- package/dist/routes/handlers/auth.d.ts +23 -1
- package/dist/routes/handlers/auth.d.ts.map +1 -1
- package/dist/routes/handlers/auth.js +101 -2
- package/dist/routes/handlers/auth.js.map +1 -1
- package/dist/routes/handlers/base.d.ts +14 -0
- package/dist/routes/handlers/base.d.ts.map +1 -1
- package/dist/routes/handlers/base.js +41 -4
- package/dist/routes/handlers/base.js.map +1 -1
- package/dist/routes/handlers/documents.d.ts.map +1 -1
- package/dist/routes/handlers/documents.js +7 -2
- package/dist/routes/handlers/documents.js.map +1 -1
- package/dist/routes/handlers/tokens.d.ts.map +1 -1
- package/dist/routes/handlers/tokens.js +5 -4
- package/dist/routes/handlers/tokens.js.map +1 -1
- package/dist/routes/handlers/users.d.ts.map +1 -1
- package/dist/routes/handlers/users.js +35 -6
- package/dist/routes/handlers/users.js.map +1 -1
- package/dist/routes/routes.d.ts.map +1 -1
- package/dist/routes/routes.js +8 -1
- package/dist/routes/routes.js.map +1 -1
- package/dist/types/auth.d.ts +8 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/oauth2.d.ts +13 -1
- package/dist/types/oauth2.d.ts.map +1 -1
- package/dist/types/oauth2.js +25 -10
- package/dist/types/oauth2.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage for a sign-in that spans two requests.
|
|
3
|
+
*
|
|
4
|
+
* OAuth login state with its PKCE verifier, and the WebAuthn challenge behind a
|
|
5
|
+
* passkey, are both written when a flow begins and read once when it completes.
|
|
6
|
+
* Held in the server process, a restart between the two halves fails the
|
|
7
|
+
* sign-in and a second replica cannot serve the second half at all.
|
|
8
|
+
*
|
|
9
|
+
* Persists through the data storage adapter when it implements the optional
|
|
10
|
+
* auth flow methods, and falls back to an in-process map when it does not, so
|
|
11
|
+
* an adapter written against the older interface keeps working.
|
|
12
|
+
*/
|
|
13
|
+
import type { DataStorageAdapter, AuthFlowState } from '../types/storage-adapters.js';
|
|
14
|
+
/**
|
|
15
|
+
* Store a pending flow.
|
|
16
|
+
*
|
|
17
|
+
* A failure to persist is raised, not swallowed. Keeping the entry in memory
|
|
18
|
+
* would report a durability the caller does not have and silently reintroduce
|
|
19
|
+
* the bug this store exists to fix: the sign-in would appear to start and then
|
|
20
|
+
* fail at the callback if it landed on another process. Failing here costs one
|
|
21
|
+
* retry and is honest about what happened.
|
|
22
|
+
*/
|
|
23
|
+
export declare function saveAuthFlowState(adapter: DataStorageAdapter | null | undefined, state: AuthFlowState): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Take a pending flow: return it and consume it, or return null.
|
|
26
|
+
*
|
|
27
|
+
* Single use is the security property here - an OAuth state is CSRF protection
|
|
28
|
+
* and a WebAuthn challenge must not be answerable twice - so the adapter is
|
|
29
|
+
* asked to return and delete in one atomic operation rather than being read and
|
|
30
|
+
* then deleted, which would let two concurrent callbacks both succeed.
|
|
31
|
+
*
|
|
32
|
+
* A storage failure propagates: a caller that cannot prove it consumed the
|
|
33
|
+
* state must not continue as though it had.
|
|
34
|
+
*/
|
|
35
|
+
export declare function consumeAuthFlowState(adapter: DataStorageAdapter | null | undefined, id: string, kind: AuthFlowState['kind']): Promise<AuthFlowState | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Read a pending flow without consuming it.
|
|
38
|
+
*
|
|
39
|
+
* An adapter without a read falls back to take-and-put: correct, at the cost of a brief
|
|
40
|
+
* window in which a concurrent reader finds nothing.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getAuthFlowState(adapter: DataStorageAdapter | null | undefined, id: string, kind: AuthFlowState['kind']): Promise<AuthFlowState | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Drop everything that has expired.
|
|
45
|
+
*
|
|
46
|
+
* Sweeping is best-effort: expired entries are rejected on read regardless, so
|
|
47
|
+
* a failed sweep costs storage rather than correctness.
|
|
48
|
+
*/
|
|
49
|
+
export declare function deleteExpiredAuthFlowStates(adapter: DataStorageAdapter | null | undefined): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Sweep expired states, driven by a request rather than by a timer.
|
|
52
|
+
*
|
|
53
|
+
* This used to be a module-scope `setInterval`, which assumes a process that
|
|
54
|
+
* outlives any one request. On an edge runtime that assumption fails twice over:
|
|
55
|
+
* work at import time is restricted, and nothing runs between requests, so the
|
|
56
|
+
* timer would either be rejected or simply never fire — while still looking
|
|
57
|
+
* correct in the source.
|
|
58
|
+
*
|
|
59
|
+
* Driving it from a request costs nothing, because expiry is already enforced on
|
|
60
|
+
* read (`isUsable`). Sweeping only reclaims space, so it is safe to skip, safe to
|
|
61
|
+
* fail, and safe to run on whichever instance happens to take the next request.
|
|
62
|
+
* The caller is not made to wait for it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function sweepExpiredAuthFlowStates(adapter: DataStorageAdapter | null | undefined, now?: number): void;
|
|
65
|
+
/** Test seam: forget when the last sweep happened. */
|
|
66
|
+
export declare function resetSweepClock(): void;
|
|
67
|
+
/** Test seam: drops the in-process entries without touching the adapter. */
|
|
68
|
+
export declare function clearFallbackAuthFlowStates(): void;
|
|
69
|
+
//# sourceMappingURL=auth-flow-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-flow-store.d.ts","sourceRoot":"","sources":["../../../src/core/security/auth-flow-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AA4BrF;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC9C,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC9C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAa/B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC9C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAe/B;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAC7C,OAAO,CAAC,IAAI,CAAC,CAYf;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC9C,GAAG,GAAE,MAAmB,GACvB,IAAI,CAIN;AAED,sDAAsD;AACtD,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,4EAA4E;AAC5E,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage for a sign-in that spans two requests.
|
|
3
|
+
*
|
|
4
|
+
* OAuth login state with its PKCE verifier, and the WebAuthn challenge behind a
|
|
5
|
+
* passkey, are both written when a flow begins and read once when it completes.
|
|
6
|
+
* Held in the server process, a restart between the two halves fails the
|
|
7
|
+
* sign-in and a second replica cannot serve the second half at all.
|
|
8
|
+
*
|
|
9
|
+
* Persists through the data storage adapter when it implements the optional
|
|
10
|
+
* auth flow methods, and falls back to an in-process map when it does not, so
|
|
11
|
+
* an adapter written against the older interface keeps working.
|
|
12
|
+
*/
|
|
13
|
+
import { createLogger } from '../utils/logger.js';
|
|
14
|
+
const logger = createLogger('routes', 'AuthFlowStore');
|
|
15
|
+
/** Entries live here only when the adapter cannot store them. */
|
|
16
|
+
const fallbackStore = new Map();
|
|
17
|
+
function adapterSupportsPersistence(adapter) {
|
|
18
|
+
return Boolean(adapter?.saveAuthFlowState && adapter.consumeAuthFlowState);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* An unparseable expiry is treated as expired rather than as "never expires":
|
|
22
|
+
* `NaN < Date.now()` is false, so a naive comparison would accept a corrupt
|
|
23
|
+
* record forever. The boundary is exclusive - a state is dead at its expiry
|
|
24
|
+
* instant, not one millisecond after.
|
|
25
|
+
*/
|
|
26
|
+
function isUsable(state) {
|
|
27
|
+
const expiresAt = new Date(state.expiresAt).getTime();
|
|
28
|
+
return Number.isFinite(expiresAt) && expiresAt > Date.now();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Store a pending flow.
|
|
32
|
+
*
|
|
33
|
+
* A failure to persist is raised, not swallowed. Keeping the entry in memory
|
|
34
|
+
* would report a durability the caller does not have and silently reintroduce
|
|
35
|
+
* the bug this store exists to fix: the sign-in would appear to start and then
|
|
36
|
+
* fail at the callback if it landed on another process. Failing here costs one
|
|
37
|
+
* retry and is honest about what happened.
|
|
38
|
+
*/
|
|
39
|
+
export async function saveAuthFlowState(adapter, state) {
|
|
40
|
+
if (adapterSupportsPersistence(adapter)) {
|
|
41
|
+
await adapter.saveAuthFlowState(state);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
fallbackStore.set(state.id, state);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Take a pending flow: return it and consume it, or return null.
|
|
48
|
+
*
|
|
49
|
+
* Single use is the security property here - an OAuth state is CSRF protection
|
|
50
|
+
* and a WebAuthn challenge must not be answerable twice - so the adapter is
|
|
51
|
+
* asked to return and delete in one atomic operation rather than being read and
|
|
52
|
+
* then deleted, which would let two concurrent callbacks both succeed.
|
|
53
|
+
*
|
|
54
|
+
* A storage failure propagates: a caller that cannot prove it consumed the
|
|
55
|
+
* state must not continue as though it had.
|
|
56
|
+
*/
|
|
57
|
+
export async function consumeAuthFlowState(adapter, id, kind) {
|
|
58
|
+
if (adapterSupportsPersistence(adapter)) {
|
|
59
|
+
const consumed = await adapter.consumeAuthFlowState(id, kind);
|
|
60
|
+
return consumed && isUsable(consumed) ? consumed : null;
|
|
61
|
+
}
|
|
62
|
+
// The fallback map is only reachable on a single process, and there is no
|
|
63
|
+
// await between the read and the delete, so this is atomic by the same
|
|
64
|
+
// argument that made the original in-process map safe.
|
|
65
|
+
const fallback = fallbackStore.get(id);
|
|
66
|
+
fallbackStore.delete(id);
|
|
67
|
+
if (!fallback || fallback.kind !== kind || !isUsable(fallback))
|
|
68
|
+
return null;
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read a pending flow without consuming it.
|
|
73
|
+
*
|
|
74
|
+
* An adapter without a read falls back to take-and-put: correct, at the cost of a brief
|
|
75
|
+
* window in which a concurrent reader finds nothing.
|
|
76
|
+
*/
|
|
77
|
+
export async function getAuthFlowState(adapter, id, kind) {
|
|
78
|
+
if (adapterSupportsPersistence(adapter)) {
|
|
79
|
+
if (adapter.getAuthFlowState) {
|
|
80
|
+
const state = await adapter.getAuthFlowState(id, kind);
|
|
81
|
+
return state && isUsable(state) ? state : null;
|
|
82
|
+
}
|
|
83
|
+
const taken = await adapter.consumeAuthFlowState(id, kind);
|
|
84
|
+
if (!taken || !isUsable(taken))
|
|
85
|
+
return null;
|
|
86
|
+
await adapter.saveAuthFlowState(taken);
|
|
87
|
+
return taken;
|
|
88
|
+
}
|
|
89
|
+
const fallback = fallbackStore.get(id);
|
|
90
|
+
if (!fallback || fallback.kind !== kind || !isUsable(fallback))
|
|
91
|
+
return null;
|
|
92
|
+
return fallback;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Drop everything that has expired.
|
|
96
|
+
*
|
|
97
|
+
* Sweeping is best-effort: expired entries are rejected on read regardless, so
|
|
98
|
+
* a failed sweep costs storage rather than correctness.
|
|
99
|
+
*/
|
|
100
|
+
export async function deleteExpiredAuthFlowStates(adapter) {
|
|
101
|
+
for (const [id, state] of fallbackStore.entries()) {
|
|
102
|
+
if (!isUsable(state))
|
|
103
|
+
fallbackStore.delete(id);
|
|
104
|
+
}
|
|
105
|
+
if (adapter?.deleteExpiredAuthFlowStates) {
|
|
106
|
+
try {
|
|
107
|
+
await adapter.deleteExpiredAuthFlowStates();
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
logger.error('Failed to sweep expired auth flow states', { error });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** At most one sweep per interval, per process. */
|
|
115
|
+
const SWEEP_INTERVAL_MS = 60_000;
|
|
116
|
+
let lastSweepAt = 0;
|
|
117
|
+
/**
|
|
118
|
+
* Sweep expired states, driven by a request rather than by a timer.
|
|
119
|
+
*
|
|
120
|
+
* This used to be a module-scope `setInterval`, which assumes a process that
|
|
121
|
+
* outlives any one request. On an edge runtime that assumption fails twice over:
|
|
122
|
+
* work at import time is restricted, and nothing runs between requests, so the
|
|
123
|
+
* timer would either be rejected or simply never fire — while still looking
|
|
124
|
+
* correct in the source.
|
|
125
|
+
*
|
|
126
|
+
* Driving it from a request costs nothing, because expiry is already enforced on
|
|
127
|
+
* read (`isUsable`). Sweeping only reclaims space, so it is safe to skip, safe to
|
|
128
|
+
* fail, and safe to run on whichever instance happens to take the next request.
|
|
129
|
+
* The caller is not made to wait for it.
|
|
130
|
+
*/
|
|
131
|
+
export function sweepExpiredAuthFlowStates(adapter, now = Date.now()) {
|
|
132
|
+
if (now - lastSweepAt < SWEEP_INTERVAL_MS)
|
|
133
|
+
return;
|
|
134
|
+
lastSweepAt = now;
|
|
135
|
+
void deleteExpiredAuthFlowStates(adapter);
|
|
136
|
+
}
|
|
137
|
+
/** Test seam: forget when the last sweep happened. */
|
|
138
|
+
export function resetSweepClock() {
|
|
139
|
+
lastSweepAt = 0;
|
|
140
|
+
}
|
|
141
|
+
/** Test seam: drops the in-process entries without touching the adapter. */
|
|
142
|
+
export function clearFallbackAuthFlowStates() {
|
|
143
|
+
fallbackStore.clear();
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=auth-flow-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-flow-store.js","sourceRoot":"","sources":["../../../src/core/security/auth-flow-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { OAuth2Client, OAuth2Scope, DeviceCodeState, TokenResponse, OAuth2ErrorCode } from '../../types/oauth2.js';
|
|
12
12
|
import type { Permission } from '../../types/user.js';
|
|
13
|
+
import type { AuthFlowState } from '../../types/storage-adapters.js';
|
|
14
|
+
/**
|
|
15
|
+
* Where device and authorization codes live between the requests of a flow.
|
|
16
|
+
*
|
|
17
|
+
* Not process memory: on Workers every request may land on another isolate, and behind a
|
|
18
|
+
* load balancer on another replica, so a device approved on one would poll as unknown on the
|
|
19
|
+
* next. The engine passes the data adapter's auth-flow store; without one this falls back to
|
|
20
|
+
* the store's in-process map, which is only correct on a single process.
|
|
21
|
+
*/
|
|
22
|
+
export interface OAuth2FlowStore {
|
|
23
|
+
save(state: AuthFlowState): Promise<void>;
|
|
24
|
+
get(id: string, kind: AuthFlowState['kind']): Promise<AuthFlowState | null>;
|
|
25
|
+
consume(id: string, kind: AuthFlowState['kind']): Promise<AuthFlowState | null>;
|
|
26
|
+
}
|
|
13
27
|
/**
|
|
14
28
|
* Configuration for registering an OAuth2 client
|
|
15
29
|
*/
|
|
@@ -30,6 +44,12 @@ export interface OAuth2ClientConfig {
|
|
|
30
44
|
allowedScopes?: string[];
|
|
31
45
|
/** Allowed grant types (default: authorization_code, refresh_token) */
|
|
32
46
|
grantTypes?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* First-party application: its tokens act as the signed-in user with the user's own role
|
|
49
|
+
* and permissions, whatever scopes they carry. Still barred from account and security
|
|
50
|
+
* routes. Default false.
|
|
51
|
+
*/
|
|
52
|
+
trusted?: boolean;
|
|
33
53
|
}
|
|
34
54
|
/**
|
|
35
55
|
* Configuration for the OAuth2 Authorization Server
|
|
@@ -53,6 +73,8 @@ export interface OAuth2ServerConfig {
|
|
|
53
73
|
pollingInterval?: number;
|
|
54
74
|
/** External OAuth2 clients to register */
|
|
55
75
|
clients?: OAuth2ClientConfig[];
|
|
76
|
+
/** Persistent storage for device and authorization codes. Default: in-process only. */
|
|
77
|
+
flowStore?: OAuth2FlowStore;
|
|
56
78
|
}
|
|
57
79
|
/**
|
|
58
80
|
* OAuth2 Authorization Server
|
|
@@ -62,10 +84,8 @@ export interface OAuth2ServerConfig {
|
|
|
62
84
|
export declare class OAuth2AuthorizationServer {
|
|
63
85
|
private config;
|
|
64
86
|
private externalClients;
|
|
65
|
-
private
|
|
66
|
-
private authorizationCodes;
|
|
87
|
+
private store;
|
|
67
88
|
private clients;
|
|
68
|
-
private cleanupInterval;
|
|
69
89
|
constructor(config: OAuth2ServerConfig);
|
|
70
90
|
/**
|
|
71
91
|
* Register the built-in CLI client
|
|
@@ -78,9 +98,9 @@ export declare class OAuth2AuthorizationServer {
|
|
|
78
98
|
/**
|
|
79
99
|
* Start periodic cleanup of expired codes
|
|
80
100
|
*/
|
|
81
|
-
private startCleanup;
|
|
82
101
|
/**
|
|
83
|
-
*
|
|
102
|
+
* Kept for callers that stop the server on shutdown. Codes now expire in the flow store,
|
|
103
|
+
* which sweeps on request, so there is no timer to stop.
|
|
84
104
|
*/
|
|
85
105
|
stopCleanup(): void;
|
|
86
106
|
/**
|
|
@@ -107,7 +127,7 @@ export declare class OAuth2AuthorizationServer {
|
|
|
107
127
|
* Start device authorization flow
|
|
108
128
|
* Returns device_code, user_code, and verification URI
|
|
109
129
|
*/
|
|
110
|
-
startDeviceAuthorization(clientId: string, requestedScopes: string[]): {
|
|
130
|
+
startDeviceAuthorization(clientId: string, requestedScopes: string[]): Promise<{
|
|
111
131
|
success: true;
|
|
112
132
|
response: {
|
|
113
133
|
device_code: string;
|
|
@@ -121,19 +141,33 @@ export declare class OAuth2AuthorizationServer {
|
|
|
121
141
|
success: false;
|
|
122
142
|
error: OAuth2ErrorCode;
|
|
123
143
|
error_description: string;
|
|
124
|
-
}
|
|
144
|
+
}>;
|
|
145
|
+
/** The device record behind a user code, with its expiry, or null. */
|
|
146
|
+
private findByUserCode;
|
|
125
147
|
/**
|
|
126
148
|
* Get device code state by user code (for verification page)
|
|
127
149
|
*/
|
|
128
|
-
getDeviceCodeByUserCode(userCode: string): DeviceCodeState | null
|
|
150
|
+
getDeviceCodeByUserCode(userCode: string): Promise<DeviceCodeState | null>;
|
|
129
151
|
/**
|
|
130
|
-
* Authorize a device code (called when user approves in Studio)
|
|
152
|
+
* Authorize a device code (called when user approves in Studio).
|
|
153
|
+
*
|
|
154
|
+
* `approvedScopes` is what the person left ticked on the consent screen. It can only
|
|
155
|
+
* narrow the request: anything not requested is ignored, and approving nothing is refused.
|
|
131
156
|
*/
|
|
132
|
-
authorizeDeviceCode(userCode: string, userId: string): boolean
|
|
157
|
+
authorizeDeviceCode(userCode: string, userId: string, approvedScopes?: string[]): Promise<boolean>;
|
|
133
158
|
/**
|
|
134
159
|
* Deny a device code (called when user denies in Studio)
|
|
135
160
|
*/
|
|
136
|
-
denyDeviceCode(userCode: string): boolean
|
|
161
|
+
denyDeviceCode(userCode: string): Promise<boolean>;
|
|
162
|
+
/**
|
|
163
|
+
* Record one decision on a pending code.
|
|
164
|
+
*
|
|
165
|
+
* Taking the user-code index is the lock: of two decisions racing (two clicks, an approve
|
|
166
|
+
* and a deny), exactly one gets it, and the other is refused rather than both reporting
|
|
167
|
+
* success while the last write wins. The index goes back afterwards so the approval page
|
|
168
|
+
* can still show that this code was already handled.
|
|
169
|
+
*/
|
|
170
|
+
private decide;
|
|
137
171
|
/**
|
|
138
172
|
* Poll for device token (called by CLI)
|
|
139
173
|
*/
|
|
@@ -145,7 +179,6 @@ export declare class OAuth2AuthorizationServer {
|
|
|
145
179
|
error: OAuth2ErrorCode;
|
|
146
180
|
error_description: string;
|
|
147
181
|
}>;
|
|
148
|
-
private checkDeviceCodeAndGenerateToken;
|
|
149
182
|
/**
|
|
150
183
|
* Validate authorization request parameters
|
|
151
184
|
*/
|
|
@@ -169,7 +202,29 @@ export declare class OAuth2AuthorizationServer {
|
|
|
169
202
|
/**
|
|
170
203
|
* Generate authorization code after user approval
|
|
171
204
|
*/
|
|
172
|
-
generateAuthorizationCode(clientId: string, redirectUri: string, scopes: OAuth2Scope[], codeChallenge: string, userId: string): string
|
|
205
|
+
generateAuthorizationCode(clientId: string, redirectUri: string, scopes: OAuth2Scope[], codeChallenge: string, userId: string): Promise<string>;
|
|
206
|
+
/**
|
|
207
|
+
* Check an approval posted from the consent screen before issuing a code for it.
|
|
208
|
+
*
|
|
209
|
+
* The approval arrives as a fresh request, so everything the authorization request was
|
|
210
|
+
* checked for is checked again: the client, its grant, the redirect URI, the PKCE
|
|
211
|
+
* challenge, and scopes the client may have. The person may have unticked scopes, but the
|
|
212
|
+
* result must still be within what the client is allowed.
|
|
213
|
+
*/
|
|
214
|
+
validateApproval(params: {
|
|
215
|
+
client_id: string;
|
|
216
|
+
redirect_uri: string;
|
|
217
|
+
scopes: unknown;
|
|
218
|
+
code_challenge: string;
|
|
219
|
+
/** A denial only redirects back: it needs a real client and redirect, not valid scopes */
|
|
220
|
+
denial?: boolean;
|
|
221
|
+
}): {
|
|
222
|
+
valid: true;
|
|
223
|
+
scopes: OAuth2Scope[];
|
|
224
|
+
} | {
|
|
225
|
+
valid: false;
|
|
226
|
+
error_description: string;
|
|
227
|
+
};
|
|
173
228
|
/**
|
|
174
229
|
* Exchange authorization code for tokens
|
|
175
230
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-server.d.ts","sourceRoot":"","sources":["../../../../src/core/security/oauth2/authorization-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EAEX,eAAe,EAEf,aAAa,EACb,eAAe,EAEhB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAQ,UAAU,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"authorization-server.d.ts","sourceRoot":"","sources":["../../../../src/core/security/oauth2/authorization-server.ts"],"names":[],"mappings":"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"}
|