tiny-http-mcp-server 0.1.35 → 0.1.37
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/composition.json +1 -1
- package/node_modules/mcp-oauth/README.md +17 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +9 -2
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +2 -2
- package/node_modules/mcp-oauth/dist/client/client-registration.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/client-registration.js +45 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +22 -8
- package/node_modules/mcp-oauth/dist/client/resource-bound-store.d.ts +9 -0
- package/node_modules/mcp-oauth/dist/client/resource-bound-store.js +108 -0
- package/node_modules/mcp-oauth/dist/client/types.d.ts +4 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +4 -0
- package/node_modules/tiny-mcp-client/dist/index.js +1324 -1165
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -102,6 +102,15 @@ normalized. An imported grant must declare its scope when a scope is configured.
|
|
|
102
102
|
Authorization records the requested set when the endpoint omits scope, and
|
|
103
103
|
refresh retains the previous granted set. Mismatched responses never activate
|
|
104
104
|
credentials; an unusable refresh response retains the pending refresh record.
|
|
105
|
+
`resourceIdentity` selects a native-owned logical server within its optional
|
|
106
|
+
persistence namespace. One encrypted, locked document owns that identity's
|
|
107
|
+
current resource URL, sessions and registrations. Changing the URL retires its
|
|
108
|
+
credentials permanently; returning to the old URL does not restore them.
|
|
109
|
+
Retired identities also withhold stale initial grants; authorize again or select
|
|
110
|
+
a fresh explicit profile to import a new grant. This option requires native persistence; custom
|
|
111
|
+
session stores own their durable resource trust policy. Without it, the native
|
|
112
|
+
client retains its existing resource-URL cache behavior.
|
|
113
|
+
|
|
105
114
|
Select a separate persistence namespace for another scope profile. No scope is
|
|
106
115
|
invented when the client does not configure one.
|
|
107
116
|
|
|
@@ -132,6 +141,14 @@ but an expired secret is never submitted for refresh. Native DCR can replace
|
|
|
132
141
|
an expired registration during explicit authorization; caller-owned imports
|
|
133
142
|
must be updated. Headless requests retain the old record and report recovery
|
|
134
143
|
is required without creating a pending refresh marker.
|
|
144
|
+
Native registrations retain `requestedRedirectUri`, the actual listener URI
|
|
145
|
+
submitted to DCR, separately from the full response metadata. Fresh responses
|
|
146
|
+
may normalize a loopback port or represent IPv4 loopback as portless localhost;
|
|
147
|
+
host, path, scheme, query and fragment differences outside that boundary fail.
|
|
148
|
+
Authorization and code exchange always use the actual listener URI. Silent
|
|
149
|
+
refresh keeps its original client regardless of callback changes. At interactive
|
|
150
|
+
authorization, a native registration with an obsolete captured callback is
|
|
151
|
+
replaced; caller-owned full registration imports retain their original identity.
|
|
135
152
|
Set `client.tokenEndpointAuthMethod` to `none`, `client_secret_post` or
|
|
136
153
|
`client_secret_basic`; a full registration can supply the same field as
|
|
137
154
|
`token_endpoint_auth_method`. Public clients never transmit a stored secret.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type CreateSecretStoreInput } from "auth-store";
|
|
2
|
-
import type { OAuthSessionStore, StoredOAuthClient } from "./types.js";
|
|
1
|
+
import { type CreateSecretStoreInput, type SecretStore } from "auth-store";
|
|
2
|
+
import type { OAuthSessionStore, StoredOAuthSession, StoredOAuthClient } from "./types.js";
|
|
3
3
|
export interface OAuthClientStore {
|
|
4
4
|
load(issuer: string): Promise<StoredOAuthClient | null>;
|
|
5
5
|
save(issuer: string, client: StoredOAuthClient): Promise<void>;
|
|
@@ -7,4 +7,11 @@ export interface OAuthClientStore {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function createAuthStoreSessionStore(options?: CreateSecretStoreInput, namespace?: string): OAuthSessionStore;
|
|
9
9
|
export declare function createAuthStoreClientStore(options: CreateSecretStoreInput, namespace?: string): OAuthClientStore;
|
|
10
|
+
export declare function createNamedSecretStore(key: string, options: CreateSecretStoreInput, defaults: {
|
|
11
|
+
salt: string;
|
|
12
|
+
directory: string;
|
|
13
|
+
service: string;
|
|
14
|
+
accountPrefix: string;
|
|
15
|
+
}, namespace?: string): SecretStore;
|
|
10
16
|
export declare function assertPersistenceNamespace(namespace: string | undefined): void;
|
|
17
|
+
export declare function isStoredOAuthSession(value: unknown): value is StoredOAuthSession;
|
|
@@ -79,7 +79,7 @@ export function createAuthStoreClientStore(options, namespace) {
|
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
function createNamedSecretStore(key, options, defaults, namespace) {
|
|
82
|
+
export function createNamedSecretStore(key, options, defaults, namespace) {
|
|
83
83
|
const hash = crypto.createHash("sha256").update(namespace === undefined ? key : JSON.stringify([namespace, key])).digest("hex");
|
|
84
84
|
const configuredFilePath = options.fileStore?.filePath;
|
|
85
85
|
const parsedFilePath = configuredFilePath === undefined ? null : path.parse(configuredFilePath);
|
|
@@ -132,7 +132,7 @@ function getOwnString(record, key) {
|
|
|
132
132
|
const value = getOwnEntry(record, key);
|
|
133
133
|
return typeof value === "string" ? value : undefined;
|
|
134
134
|
}
|
|
135
|
-
function isStoredOAuthSession(value) {
|
|
135
|
+
export function isStoredOAuthSession(value) {
|
|
136
136
|
if (!isObjectRecord(value)) {
|
|
137
137
|
return false;
|
|
138
138
|
}
|
|
@@ -2,3 +2,5 @@ import type { OAuthClientRegistration, StoredOAuthClient } from "./types.js";
|
|
|
2
2
|
/** Validate and copy a bounded JSON DCR response without quoting credential input. */
|
|
3
3
|
export declare function parseOAuthClientRegistration(value: unknown): OAuthClientRegistration;
|
|
4
4
|
export declare function normalizeStoredOAuthClient(value: unknown): StoredOAuthClient | null;
|
|
5
|
+
/** Fresh DCR may describe a normalized loopback port; saved identity stays exact. */
|
|
6
|
+
export declare function registrationMatchesRedirect(client: StoredOAuthClient, requestedUri: string, fresh?: boolean): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loopbackTarget } from "./loopback-authorization.js";
|
|
1
2
|
import { normalizeOAuthScope } from "./scope.js";
|
|
2
3
|
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
3
4
|
/** Validate and copy a bounded JSON DCR response without quoting credential input. */
|
|
@@ -84,6 +85,18 @@ export function normalizeStoredOAuthClient(value) {
|
|
|
84
85
|
(clientSecret !== undefined && (typeof clientSecret !== "string" || clientSecret.trim() === "")))
|
|
85
86
|
return null;
|
|
86
87
|
const client = { clientId: clientId.trim(), ...(clientSecret === undefined ? {} : { clientSecret: clientSecret.trim() }) };
|
|
88
|
+
const requestedRedirectUri = Object.hasOwn(record, "requestedRedirectUri") ? record.requestedRedirectUri : undefined;
|
|
89
|
+
if (requestedRedirectUri !== undefined) {
|
|
90
|
+
try {
|
|
91
|
+
if (typeof requestedRedirectUri !== "string")
|
|
92
|
+
throw new Error("Invalid redirect identity");
|
|
93
|
+
loopbackTarget({ redirectUri: requestedRedirectUri });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new Error("Invalid stored OAuth registration redirect identity");
|
|
97
|
+
}
|
|
98
|
+
client.requestedRedirectUri = requestedRedirectUri;
|
|
99
|
+
}
|
|
87
100
|
const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record, "tokenEndpointAuthMethod") ? record.tokenEndpointAuthMethod : undefined);
|
|
88
101
|
if (Object.hasOwn(record, "registration") && record.registration !== undefined) {
|
|
89
102
|
const registration = parseOAuthClientRegistration(record.registration);
|
|
@@ -101,3 +114,35 @@ export function normalizeStoredOAuthClient(value) {
|
|
|
101
114
|
client.tokenEndpointAuthMethod = method;
|
|
102
115
|
return client;
|
|
103
116
|
}
|
|
117
|
+
/** Fresh DCR may describe a normalized loopback port; saved identity stays exact. */
|
|
118
|
+
export function registrationMatchesRedirect(client, requestedUri, fresh = false) {
|
|
119
|
+
if (client.requestedRedirectUri !== undefined)
|
|
120
|
+
return client.requestedRedirectUri === requestedUri;
|
|
121
|
+
const redirects = client.registration?.redirect_uris;
|
|
122
|
+
if (redirects === undefined || redirects === null || redirects.length === 0)
|
|
123
|
+
return true;
|
|
124
|
+
return redirects.some(returnedUri => {
|
|
125
|
+
if (returnedUri === requestedUri)
|
|
126
|
+
return true;
|
|
127
|
+
if (!fresh)
|
|
128
|
+
return false;
|
|
129
|
+
let returned, requested;
|
|
130
|
+
try {
|
|
131
|
+
returned = new URL(returnedUri);
|
|
132
|
+
requested = new URL(requestedUri);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (requested.protocol !== "http:" || returned.protocol !== "http:" ||
|
|
138
|
+
!["127.0.0.1", "[::1]", "localhost"].includes(requested.hostname))
|
|
139
|
+
return false;
|
|
140
|
+
const sameHost = returned.hostname === requested.hostname;
|
|
141
|
+
const normalizedIpv4 = requested.hostname === "127.0.0.1" && returned.hostname === "localhost" && returned.port === "";
|
|
142
|
+
if (!sameHost && !normalizedIpv4)
|
|
143
|
+
return false;
|
|
144
|
+
returned.hostname = requested.hostname;
|
|
145
|
+
returned.port = requested.port;
|
|
146
|
+
return returned.href === requested.href;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createResourceBoundOAuthStores } from "./resource-bound-store.js";
|
|
2
|
+
import { normalizeStoredOAuthClient, parseOAuthClientRegistration, registrationMatchesRedirect } from "./client-registration.js";
|
|
2
3
|
import { normalizeOAuthScope } from "./scope.js";
|
|
3
4
|
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
4
5
|
import { isIP } from "node:net";
|
|
@@ -26,8 +27,13 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
26
27
|
const configuredClient = normalizeConfiguredClient(options.client);
|
|
27
28
|
const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
|
|
28
29
|
const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
if (options.resourceIdentity !== undefined && options.sessionStore !== undefined)
|
|
31
|
+
throw new Error("OAuth resourceIdentity requires native-owned persistence; custom stores own their resource trust policy");
|
|
32
|
+
const resourceStores = options.resourceIdentity === undefined ? undefined :
|
|
33
|
+
createResourceBoundOAuthStores(options.authStore ?? {}, options.persistenceNamespace, options.resourceIdentity);
|
|
34
|
+
const sessionStore = resourceStores?.sessionStore ?? options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
35
|
+
const clientStore = resourceStores?.clientStore ??
|
|
36
|
+
(options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace));
|
|
31
37
|
const now = options.now ?? Date.now;
|
|
32
38
|
const registeredClients = new Map();
|
|
33
39
|
if (options.initialGrant !== undefined) {
|
|
@@ -127,6 +133,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
127
133
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
128
134
|
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
129
135
|
let session = await loadSession(canonicalResource);
|
|
136
|
+
if (resourceStores !== undefined) {
|
|
137
|
+
registeredClients.clear();
|
|
138
|
+
if (!resourceStores.initialGrantAllowed)
|
|
139
|
+
initialGrantConsumed = true;
|
|
140
|
+
}
|
|
130
141
|
if (session !== null)
|
|
131
142
|
assertRegistrationIssuer(session.client, session.authorizationServer);
|
|
132
143
|
if (configuredClient !== null && discovery !== undefined)
|
|
@@ -372,7 +383,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
372
383
|
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
373
384
|
if (storedClient !== null) {
|
|
374
385
|
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
375
|
-
if (hasExpiredClientSecret(storedClient, now)) {
|
|
386
|
+
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
376
387
|
await clearRegisteredClient(discovery.authorizationServer);
|
|
377
388
|
storedClient = null;
|
|
378
389
|
}
|
|
@@ -385,7 +396,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
385
396
|
};
|
|
386
397
|
}
|
|
387
398
|
if (registrationEndpoint === undefined) {
|
|
388
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now)) {
|
|
399
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
389
400
|
return {
|
|
390
401
|
kind: "dynamic",
|
|
391
402
|
fromStoredRegistration: true,
|
|
@@ -394,7 +405,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
394
405
|
}
|
|
395
406
|
throw new Error("Authorization server metadata is missing registration_endpoint");
|
|
396
407
|
}
|
|
397
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now)) {
|
|
408
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
398
409
|
const isConfiguredStaticFallback = configuredClient !== null &&
|
|
399
410
|
existingSession.client.clientId === configuredClient.clientId &&
|
|
400
411
|
existingSession.client.clientSecret === configuredClient.clientSecret;
|
|
@@ -437,11 +448,14 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
437
448
|
assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
|
|
438
449
|
if (hasExpiredClientSecret(registeredClient, now))
|
|
439
450
|
throw new Error("OAuth client secret has expired in the registration response");
|
|
440
|
-
|
|
451
|
+
if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
|
|
452
|
+
throw new Error("OAuth registration response does not match the requested redirect URI");
|
|
453
|
+
const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
|
|
454
|
+
await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
|
|
441
455
|
return {
|
|
442
456
|
kind: "dynamic",
|
|
443
457
|
fromStoredRegistration: false,
|
|
444
|
-
client:
|
|
458
|
+
client: clientWithRedirect
|
|
445
459
|
};
|
|
446
460
|
}
|
|
447
461
|
async function loadSession(resource) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CreateSecretStoreInput } from "auth-store";
|
|
2
|
+
import { type OAuthClientStore } from "./auth-store-session-store.js";
|
|
3
|
+
import type { OAuthSessionStore } from "./types.js";
|
|
4
|
+
/** One locked document owns a logical server's URL history, grant and clients. */
|
|
5
|
+
export declare function createResourceBoundOAuthStores(options: CreateSecretStoreInput, namespace: string | undefined, identity: string): {
|
|
6
|
+
sessionStore: OAuthSessionStore;
|
|
7
|
+
clientStore: OAuthClientStore;
|
|
8
|
+
initialGrantAllowed: boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
2
|
+
import { assertPersistenceNamespace, createNamedSecretStore, isStoredOAuthSession } from "./auth-store-session-store.js";
|
|
3
|
+
import { normalizeStoredOAuthClient } from "./client-registration.js";
|
|
4
|
+
/** One locked document owns a logical server's URL history, grant and clients. */
|
|
5
|
+
export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
6
|
+
assertPersistenceNamespace(identity);
|
|
7
|
+
const store = createNamedSecretStore(identity, options, { salt: "poe-code:mcp-oauth:resources:v1",
|
|
8
|
+
directory: ".poe-code/mcp-oauth/resources", service: "poe-code-mcp-oauth-resources", accountPrefix: "resource" }, namespace);
|
|
9
|
+
const result = { initialGrantAllowed: true, sessionStore: {}, clientStore: {} };
|
|
10
|
+
async function read() {
|
|
11
|
+
const raw = await store.get();
|
|
12
|
+
if (raw === null)
|
|
13
|
+
return null;
|
|
14
|
+
let value;
|
|
15
|
+
try {
|
|
16
|
+
value = JSON.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error("Stored OAuth resource identity must be valid JSON; reset explicitly to recover");
|
|
20
|
+
}
|
|
21
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
22
|
+
throw new Error("Invalid stored OAuth resource identity");
|
|
23
|
+
const record = value;
|
|
24
|
+
if (!["version", "resource", "generation", "session", "clients"].every(key => Object.hasOwn(record, key)) || record.version !== 1 || typeof record.resource !== "string" || record.resource !== canonicalizeResourceIndicator(record.resource) ||
|
|
25
|
+
!Number.isSafeInteger(record.generation) || record.generation < 0 ||
|
|
26
|
+
(record.session !== null && (!isStoredOAuthSession(record.session) || record.session.resource !== record.resource)) ||
|
|
27
|
+
typeof record.clients !== "object" || record.clients === null || Array.isArray(record.clients))
|
|
28
|
+
throw new Error("Invalid stored OAuth resource identity; reset explicitly to recover");
|
|
29
|
+
let resourceUrl;
|
|
30
|
+
try {
|
|
31
|
+
resourceUrl = new URL(record.resource);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error("Invalid stored OAuth resource identity URL");
|
|
35
|
+
}
|
|
36
|
+
if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.hash)
|
|
37
|
+
throw new Error("Invalid stored OAuth resource identity URL");
|
|
38
|
+
for (const [issuer, client] of Object.entries(record.clients)) {
|
|
39
|
+
const normalized = normalizeStoredOAuthClient(client);
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(issuer);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new Error("Invalid stored OAuth resource client issuer");
|
|
46
|
+
}
|
|
47
|
+
if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
48
|
+
throw new Error("Invalid stored OAuth resource client");
|
|
49
|
+
Object.defineProperty(record.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
|
|
50
|
+
}
|
|
51
|
+
return record;
|
|
52
|
+
}
|
|
53
|
+
async function reconcile(resource) {
|
|
54
|
+
resource = canonicalizeResourceIndicator(resource);
|
|
55
|
+
const existing = await read();
|
|
56
|
+
if (existing?.resource === resource) {
|
|
57
|
+
result.initialGrantAllowed = existing.generation === 0;
|
|
58
|
+
return existing;
|
|
59
|
+
}
|
|
60
|
+
const record = { version: 1, resource, generation: existing === null ? 0 : existing.generation + 1, session: null, clients: {} };
|
|
61
|
+
if (!Number.isSafeInteger(record.generation))
|
|
62
|
+
throw new Error("OAuth resource identity generation limit exceeded");
|
|
63
|
+
await store.set(JSON.stringify(record));
|
|
64
|
+
result.initialGrantAllowed = record.generation === 0;
|
|
65
|
+
return record;
|
|
66
|
+
}
|
|
67
|
+
result.sessionStore = {
|
|
68
|
+
async withLock(_resource, operation, options) {
|
|
69
|
+
if (store.withLock === undefined)
|
|
70
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
71
|
+
return store.withLock(operation, options);
|
|
72
|
+
},
|
|
73
|
+
async load(resource) { return (await reconcile(resource)).session; },
|
|
74
|
+
async save(resource, session) {
|
|
75
|
+
const record = await reconcile(resource);
|
|
76
|
+
if (canonicalizeResourceIndicator(session.resource) !== record.resource)
|
|
77
|
+
throw new Error("OAuth session does not match its resource identity");
|
|
78
|
+
record.session = session;
|
|
79
|
+
await store.set(JSON.stringify(record));
|
|
80
|
+
},
|
|
81
|
+
async clear(resource) {
|
|
82
|
+
const record = await reconcile(resource);
|
|
83
|
+
record.session = null;
|
|
84
|
+
// Preserve a tombstone so environment grants cannot revive a cleared family.
|
|
85
|
+
record.generation = Math.max(1, record.generation);
|
|
86
|
+
result.initialGrantAllowed = false;
|
|
87
|
+
await store.set(JSON.stringify(record));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
result.clientStore = {
|
|
91
|
+
async load(issuer) { const record = await read(); return record !== null && Object.hasOwn(record.clients, issuer) ? record.clients[issuer] : null; },
|
|
92
|
+
async save(issuer, client) {
|
|
93
|
+
const record = await read();
|
|
94
|
+
if (record === null)
|
|
95
|
+
throw new Error("OAuth resource identity must be bound before registering a client");
|
|
96
|
+
Object.defineProperty(record.clients, issuer, { value: client, enumerable: true, configurable: true, writable: true });
|
|
97
|
+
await store.set(JSON.stringify(record));
|
|
98
|
+
},
|
|
99
|
+
async clear(issuer) {
|
|
100
|
+
const record = await read();
|
|
101
|
+
if (record === null || !Object.hasOwn(record.clients, issuer))
|
|
102
|
+
return;
|
|
103
|
+
delete record.clients[issuer];
|
|
104
|
+
await store.set(JSON.stringify(record));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
@@ -92,6 +92,8 @@ export interface OAuthClientRegistration extends Record<string, unknown> {
|
|
|
92
92
|
}
|
|
93
93
|
export interface StoredOAuthClient {
|
|
94
94
|
clientId: string;
|
|
95
|
+
/** Actual listener URI submitted when this native client was registered. */
|
|
96
|
+
requestedRedirectUri?: string;
|
|
95
97
|
clientSecret?: string;
|
|
96
98
|
registration?: OAuthClientRegistration;
|
|
97
99
|
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
@@ -145,6 +147,8 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
145
147
|
sessionLockTimeoutMs?: number;
|
|
146
148
|
/** Isolate native persisted sessions and registrations for a named profile. */
|
|
147
149
|
persistenceNamespace?: string;
|
|
150
|
+
/** Native-owned durable logical server identity; changing URL retires its credentials. */
|
|
151
|
+
resourceIdentity?: string;
|
|
148
152
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
149
153
|
initialGrant?: {
|
|
150
154
|
resource: string;
|
|
@@ -195,6 +195,8 @@ interface OAuthClientRegistration extends Record<string, unknown> {
|
|
|
195
195
|
}
|
|
196
196
|
interface StoredOAuthClient {
|
|
197
197
|
clientId: string;
|
|
198
|
+
/** Actual listener URI submitted when this native client was registered. */
|
|
199
|
+
requestedRedirectUri?: string;
|
|
198
200
|
clientSecret?: string;
|
|
199
201
|
registration?: OAuthClientRegistration;
|
|
200
202
|
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
@@ -248,6 +250,8 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
248
250
|
sessionLockTimeoutMs?: number;
|
|
249
251
|
/** Isolate native persisted sessions and registrations for a named profile. */
|
|
250
252
|
persistenceNamespace?: string;
|
|
253
|
+
/** Native-owned durable logical server identity; changing URL retires its credentials. */
|
|
254
|
+
resourceIdentity?: string;
|
|
251
255
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
252
256
|
initialGrant?: {
|
|
253
257
|
resource: string;
|