@kwirthmagnify/kwirth-common-back 0.5.53 → 0.5.55
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/Consumer.d.ts +8 -0
- package/dist/Consumer.js +53 -0
- package/dist/IExtension.d.ts +30 -0
- package/dist/IFederation.d.ts +3 -3
- package/dist/IFederation.js +5 -5
- package/dist/IIdpConnector.d.ts +1 -1
- package/dist/IIdpConnector.js +6 -6
- package/dist/ILogin.d.ts +2 -2
- package/dist/IPluvider.d.ts +23 -23
- package/dist/IProvider.d.ts +129 -80
- package/dist/ISender.d.ts +3 -3
- package/dist/IWebhook.d.ts +3 -3
- package/dist/KubernetesTools.d.ts +3 -3
- package/dist/KubernetesTools.js +3 -3
- package/dist/github.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/oauth2.js +3 -3
- package/dist/oidc.js +10 -9
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The property the core stamps a consumer instance with. A plain string key, so any producer can read it. */
|
|
2
|
+
export declare const KWIRTH_CONSUMER_ID = "__kwirthConsumerId";
|
|
3
|
+
/** Extension types whose consumer id carries a prefix. Channels do not: their id is the bare channel id. */
|
|
4
|
+
export type TConsumerType = 'provider' | 'sender' | 'webhook' | 'homepage' | 'login' | 'idp' | 'theme' | 'docs' | 'pack';
|
|
5
|
+
export declare const consumerIdFor: (type: TConsumerType, id: string) => string;
|
|
6
|
+
/** Stamps an instance with its qualified consumer id. The core's job; an extension has no reason to call it. */
|
|
7
|
+
export declare const stampConsumerId: (instance: object, consumerId: string) => void;
|
|
8
|
+
export declare const consumerIdOf: (consumer: unknown) => string | undefined;
|
package/dist/Consumer.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Who is consuming a provider.
|
|
4
|
+
|
|
5
|
+
Anything that subscribes to a producer has to be able to NAME itself, or the core's registry of
|
|
6
|
+
who-consumes-what — the one the status graph is drawn from — ends up with anonymous edges, and a
|
|
7
|
+
producer that keeps one credential per consumer (the cloud accounts, say) cannot tell who is asking.
|
|
8
|
+
|
|
9
|
+
🔴 The identity is written by the CORE, never by the extension author: when the core wires an extension
|
|
10
|
+
up to the providers it consumes, it STAMPS the instance with its qualified id — `sender:ses`,
|
|
11
|
+
`webhook:gitlab`, `homepage:status`, `provider:aws` — and hands out a provider access already bound
|
|
12
|
+
to it. A consumer therefore cannot claim to be somebody else and be handed that somebody's secret.
|
|
13
|
+
|
|
14
|
+
Channels are the exception, for history: they name themselves through getChannelData() and their
|
|
15
|
+
consumer id is the bare channel id. A pluvider is a channel that also produces, so it is filed as a
|
|
16
|
+
channel — and the `plugin:` prefix is its id as a PRODUCER, a different thing.
|
|
17
|
+
|
|
18
|
+
This lives in the published contract so that a PRODUCER can read the identity of whoever subscribes
|
|
19
|
+
to it with the very same rule the core applies, instead of each one inventing its own.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.consumerIdOf = exports.stampConsumerId = exports.consumerIdFor = exports.KWIRTH_CONSUMER_ID = void 0;
|
|
23
|
+
/** The property the core stamps a consumer instance with. A plain string key, so any producer can read it. */
|
|
24
|
+
exports.KWIRTH_CONSUMER_ID = '__kwirthConsumerId';
|
|
25
|
+
const consumerIdFor = (type, id) => `${type}:${id}`;
|
|
26
|
+
exports.consumerIdFor = consumerIdFor;
|
|
27
|
+
/** Stamps an instance with its qualified consumer id. The core's job; an extension has no reason to call it. */
|
|
28
|
+
const stampConsumerId = (instance, consumerId) => {
|
|
29
|
+
Object.defineProperty(instance, exports.KWIRTH_CONSUMER_ID, { value: consumerId, enumerable: false, configurable: true, writable: false });
|
|
30
|
+
};
|
|
31
|
+
exports.stampConsumerId = stampConsumerId;
|
|
32
|
+
/*
|
|
33
|
+
The qualified id of whoever subscribed. The stamp wins; without it, the two legacy shapes: a channel
|
|
34
|
+
names itself through getChannelData() (checked FIRST, because a pluvider also carries an `id`), and a
|
|
35
|
+
bare `id` is taken for a provider wired by a core that did not stamp yet.
|
|
36
|
+
|
|
37
|
+
Returns undefined when the consumer cannot name itself at all. The caller decides: a producer hands an
|
|
38
|
+
anonymous consumer only what is meant for everyone, and the core warns and carries on.
|
|
39
|
+
*/
|
|
40
|
+
const consumerIdOf = (consumer) => {
|
|
41
|
+
if (!consumer || typeof consumer !== 'object')
|
|
42
|
+
return undefined;
|
|
43
|
+
const stamped = consumer[exports.KWIRTH_CONSUMER_ID];
|
|
44
|
+
if (typeof stamped === 'string' && stamped.length > 0)
|
|
45
|
+
return stamped;
|
|
46
|
+
const c = consumer;
|
|
47
|
+
if (typeof c.getChannelData === 'function') {
|
|
48
|
+
const id = c.getChannelData()?.id;
|
|
49
|
+
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
50
|
+
}
|
|
51
|
+
return typeof c.id === 'string' && c.id.length > 0 ? (0, exports.consumerIdFor)('provider', c.id) : undefined;
|
|
52
|
+
};
|
|
53
|
+
exports.consumerIdOf = consumerIdOf;
|
package/dist/IExtension.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IExtensionExportOptions, IExtensionImportResult } from '@kwirthmagnify/kwirth-common';
|
|
2
|
+
import type { IProviderAccess } from './IProvider';
|
|
2
3
|
/**
|
|
3
4
|
* What an extension writes its log with. The core builds it knowing who the extension is, so the
|
|
4
5
|
* line comes out identified — '[prov] [ERRO] [longhorn] ...' — and the extension only writes the
|
|
@@ -17,7 +18,36 @@ export interface IExtensionLogger {
|
|
|
17
18
|
warning(message: unknown): void;
|
|
18
19
|
error(message: unknown): void;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* What an extension needs from the rest of the core. The same shape for every family: the `providers`
|
|
23
|
+
* list a channel or a provider already declares, now available to all eleven.
|
|
24
|
+
*/
|
|
25
|
+
export interface IExtensionRequirements {
|
|
26
|
+
/** Ids of the providers this extension consumes (never a pluvider 'plugin:<name>' id). */
|
|
27
|
+
providers?: string[];
|
|
28
|
+
}
|
|
20
29
|
export interface IExtension {
|
|
30
|
+
/**
|
|
31
|
+
* The providers this extension CONSUMES. The core instantiates them even if no channel asks for
|
|
32
|
+
* them, the same way it does for a channel's or a provider's requirements. The dependency stays
|
|
33
|
+
* SOFT: one that is not installed is a warning, and the consumer must survive its absence.
|
|
34
|
+
*
|
|
35
|
+
* OPTIONAL: an extension that consumes nothing leaves it out, and an older core ignores it.
|
|
36
|
+
*/
|
|
37
|
+
requirements?: IExtensionRequirements;
|
|
38
|
+
/**
|
|
39
|
+
* Called by the core once EVERY provider is registered and started, with an access already bound
|
|
40
|
+
* to this extension's identity. This is where an extension subscribes to the providers it consumes —
|
|
41
|
+
* never from its own start hook, because whether a producer exists at that point depends on the
|
|
42
|
+
* startup order, which the author cannot see.
|
|
43
|
+
*
|
|
44
|
+
* Any family may implement it: a sender that emails through SES reaching the cloud accounts, a
|
|
45
|
+
* homepage showing the state of an account, an IdP reading Cognito or B2C. Whoever subscribes MUST
|
|
46
|
+
* unsubscribe in its own stop hook, or the producer goes on handing events to a dead instance.
|
|
47
|
+
*
|
|
48
|
+
* OPTIONAL: an extension that consumes nothing leaves it out, and an older core never calls it.
|
|
49
|
+
*/
|
|
50
|
+
onProvidersReady?(access: IProviderAccess): void | Promise<void>;
|
|
21
51
|
exportConfig?(options: IExtensionExportOptions): Promise<unknown>;
|
|
22
52
|
importConfig?(config: unknown): Promise<IExtensionImportResult>;
|
|
23
53
|
}
|
package/dist/IFederation.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ export interface IClusterEndpoint {
|
|
|
6
6
|
id?: string;
|
|
7
7
|
}
|
|
8
8
|
export declare enum ERemoteConnState {
|
|
9
|
-
CONNECTED = "connected",// socket
|
|
10
|
-
HANDSHAKING = "handshaking",// socket
|
|
11
|
-
RECONNECTING = "reconnecting",//
|
|
9
|
+
CONNECTED = "connected",// socket open AND a valid instance captured → operational
|
|
10
|
+
HANDSHAKING = "handshaking",// socket open but with NO instance yet (remote channel starting / re-handshake)
|
|
11
|
+
RECONNECTING = "reconnecting",// no socket, retrying with backoff
|
|
12
12
|
DOWN = "down"
|
|
13
13
|
}
|
|
14
14
|
export interface IRemoteChannelHandlers {
|
package/dist/IFederation.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ERemoteConnState = void 0;
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// State of a remote connection managed by the core. Never string literals.
|
|
5
|
+
// Progression as it comes up: DOWN → RECONNECTING (looking for a socket) → HANDSHAKING (socket open,
|
|
6
|
+
// asking the remote channel for an instance) → CONNECTED (socket + instance = operational). CONNECTED is
|
|
7
|
+
// the ONLY state in which a command actually reaches the remote channel.
|
|
8
8
|
var ERemoteConnState;
|
|
9
9
|
(function (ERemoteConnState) {
|
|
10
10
|
ERemoteConnState["CONNECTED"] = "connected";
|
|
11
11
|
ERemoteConnState["HANDSHAKING"] = "handshaking";
|
|
12
12
|
ERemoteConnState["RECONNECTING"] = "reconnecting";
|
|
13
|
-
ERemoteConnState["DOWN"] = "down"; //
|
|
13
|
+
ERemoteConnState["DOWN"] = "down"; // connection closed / never established (terminal or no credentials)
|
|
14
14
|
})(ERemoteConnState || (exports.ERemoteConnState = ERemoteConnState = {}));
|
package/dist/IIdpConnector.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare enum EIdpConnectorKind {
|
|
|
4
4
|
OIDC = "oidc",
|
|
5
5
|
OAUTH2 = "oauth2"
|
|
6
6
|
}
|
|
7
|
-
/** @deprecated
|
|
7
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
8
8
|
export type IdpFieldType = TConfigFieldType;
|
|
9
9
|
export type IIdpConfigFieldDef = IConfigFieldDef;
|
|
10
10
|
export interface IIdpIdentity {
|
package/dist/IIdpConnector.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
|
|
3
|
+
The Identity Provider (IdP) connector interface for Kwirth.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
A connector is PURE LOGIC (with no routes of its own): it builds the IdP's authorization URL and
|
|
6
|
+
processes the callback, returning the verified identity. The pre-login HTTP flow and the issuing of
|
|
7
|
+
the AccessKey live in Kwirth's core, never in the connector.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
It lives in common-back so that connectors packaged separately (idps/<id>/) can implement it by
|
|
10
|
+
importing '@kwirthmagnify/kwirth-common-back', just like ISender/IProvider.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.EIdpConnectorKind = void 0;
|
package/dist/ILogin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TConfigFieldType, IConfigFieldDef } from '@kwirthmagnify/kwirth-common';
|
|
2
|
-
/** @deprecated
|
|
2
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
3
3
|
export type LoginFieldType = TConfigFieldType;
|
|
4
|
-
/**
|
|
4
|
+
/** A login's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
|
|
5
5
|
export type ILoginFieldDef = IConfigFieldDef;
|
package/dist/IPluvider.d.ts
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
import { IProviderSubscriber, IProviderSubscriptionHelp } from './IProvider';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* What the extension manager and provider-debug show about a pluvider. The consumer of a pluvider is
|
|
4
|
+
* ANOTHER TEAM, so there has to be something to show without reading the code.
|
|
5
5
|
*/
|
|
6
6
|
export interface IPluviderData {
|
|
7
|
-
/**
|
|
7
|
+
/** What it produces, in one line. */
|
|
8
8
|
description: string;
|
|
9
|
-
/**
|
|
9
|
+
/** Name of the type of event it emits (e.g. 'IAgoraAlert'), to orient the consumer. */
|
|
10
10
|
eventTypeName?: string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* plugins
|
|
15
|
-
*
|
|
13
|
+
* A plugin that ALSO produces: it exposes in-process the information it already generates, so other
|
|
14
|
+
* plugins can subscribe to it. It is not a second extension packaged inside the plugin: it is
|
|
15
|
+
* implemented by the channel's SAME class, on the same instance and the same data.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* 'apiKeyApi'
|
|
20
|
-
* 'requiresApiKeyApi'
|
|
17
|
+
* It does NOT extend IProvider, on purpose. A pluvider does not go through the provider machinery — it
|
|
18
|
+
* is not put into 'clusterInfo.providers', which is what the loops that mount routers, write
|
|
19
|
+
* 'apiKeyApi' or set 'started' walk over — so it has no 'id', 'router', 'routerAlias',
|
|
20
|
+
* 'providesRouter', 'requiresApiKeyApi' or 'apiKeyApi'.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* Nor does the author write the id: the core composes it as '<PLUVIDER_ID_PREFIX><channelId>', so
|
|
23
|
+
* nobody gets the prefix wrong.
|
|
24
24
|
*
|
|
25
|
-
* 'TSub'
|
|
26
|
-
*
|
|
25
|
+
* 'TSub' is the shape of the subscription filter. As with providers, each pluvider decides whether it
|
|
26
|
+
* filters and with what shape; if it does not filter, the generic is left unspecified.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
28
|
+
* Example:
|
|
29
29
|
*
|
|
30
30
|
* class AgoraChannel implements IChannel, IPluvider<IAgoraAlertSubscription> { … }
|
|
31
31
|
*/
|
|
32
32
|
export interface IPluvider<TSub = unknown> {
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* The pluvider's metadata. Its PRESENCE is the declaration: a channel implementing this method
|
|
35
|
+
* offers itself as a producer, and the core registers it. No flags, no duck-typing detection.
|
|
36
36
|
*/
|
|
37
37
|
getPluviderData(): IPluviderData;
|
|
38
38
|
addSubscriber(c: IProviderSubscriber, data: TSub): Promise<void>;
|
|
39
39
|
removeSubscriber(c: IProviderSubscriber): Promise<void>;
|
|
40
40
|
updateSubscription?(c: IProviderSubscriber, data: TSub): Promise<void>;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Starts production. The core calls it in the pluviders phase, that is BEFORE 'startChannel()':
|
|
43
|
+
* the background work lives on this side and the front end hooks in afterwards.
|
|
44
44
|
*/
|
|
45
45
|
startProvider(): Promise<void>;
|
|
46
46
|
stopProvider(): Promise<void>;
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Mandatory, unlike its namesake on IProvider, which is optional. A provider is usually consumed
|
|
49
|
+
* by whoever wrote it; a pluvider is consumed by outsiders, and without this they have no way of
|
|
50
|
+
* knowing what to write in the subscription or what they are going to receive.
|
|
51
51
|
*/
|
|
52
52
|
getSubscriptionHelp(): IProviderSubscriptionHelp;
|
|
53
53
|
}
|
package/dist/IProvider.d.ts
CHANGED
|
@@ -8,8 +8,13 @@ export interface IProviderSubscriber {
|
|
|
8
8
|
processProviderEvent(providerId: string, obj: any): void;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* How a
|
|
12
|
-
* ends — the producer and
|
|
11
|
+
* How a consumer subscribes to a producer. The core hands one of these out already bound to the two
|
|
12
|
+
* ends — the producer and whoever asked for it — with 'clusterInfo.getProvider(id, this)'.
|
|
13
|
+
*
|
|
14
|
+
* The consumer is usually a channel, but it does NOT have to be: a PROVIDER may consume another
|
|
15
|
+
* provider, which is how a provider that owns shared configuration — cloud credentials, say — reaches
|
|
16
|
+
* the providers that need it. A provider subscribes from onProvidersReady(), never from
|
|
17
|
+
* startProvider(); see that method for why.
|
|
13
18
|
*
|
|
14
19
|
* It exists because a channel used to receive the provider OBJECT and call 'addSubscriber' on it,
|
|
15
20
|
* which meant the core could be bypassed without doing anything wrong, and therefore that its
|
|
@@ -40,9 +45,9 @@ export interface IProviderHandle {
|
|
|
40
45
|
unsubscribe(subscriber: IProviderSubscriber): unknown;
|
|
41
46
|
}
|
|
42
47
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
48
|
+
* Persistence the core injects into the provider (the same mechanism channels receive).
|
|
49
|
+
* The 'secret' boolean decides the destination: true -> Kubernetes Secret, false -> ConfigMap.
|
|
50
|
+
* The 'Common' variants write to the store shared between extensions.
|
|
46
51
|
*/
|
|
47
52
|
export interface IProviderStorage {
|
|
48
53
|
writeStorage(id: string, secret: boolean, data: any): Promise<void>;
|
|
@@ -51,9 +56,9 @@ export interface IProviderStorage {
|
|
|
51
56
|
readStorageCommon(id: string, secret: boolean): Promise<any>;
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
59
|
+
* A field of the subscription payload, described so a consumer can draw a form instead of demanding
|
|
60
|
+
* hand-written JSON. Declaring them only makes sense when the payload is flat; when it is nested
|
|
61
|
+
* (otel, for instance, with 'spaces'), 'usage' and 'example' are enough.
|
|
57
62
|
*/
|
|
58
63
|
export interface IProviderSubscriptionField {
|
|
59
64
|
name: string;
|
|
@@ -62,68 +67,83 @@ export interface IProviderSubscriptionField {
|
|
|
62
67
|
description: string;
|
|
63
68
|
}
|
|
64
69
|
/**
|
|
65
|
-
*
|
|
66
|
-
* addSubscriber.
|
|
67
|
-
*
|
|
70
|
+
* Help a provider publishes about HOW TO SUBSCRIBE to it, that is, about the 'data' argument of
|
|
71
|
+
* addSubscriber. Not to be confused with the 'schema' a provider exports from its back.js, which
|
|
72
|
+
* describes the provider's own configuration (configure/configRouter).
|
|
68
73
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
74
|
+
* provider-debug consumes it to explain to the user what to write, but any channel that offers a
|
|
75
|
+
* choice of provider can use it.
|
|
71
76
|
*/
|
|
72
77
|
export interface IProviderSubscriptionHelp {
|
|
73
|
-
/**
|
|
78
|
+
/** How it is used, in prose: what it delivers, what it takes to receive anything, gotchas. */
|
|
74
79
|
usage: string;
|
|
75
|
-
/**
|
|
80
|
+
/** Example payload, ready to pass to addSubscriber as it is. */
|
|
76
81
|
example: Record<string, unknown>;
|
|
77
|
-
/**
|
|
82
|
+
/** Field-by-field description. Optional: only for flat payloads. */
|
|
78
83
|
fields?: IProviderSubscriptionField[];
|
|
79
84
|
}
|
|
80
85
|
/**
|
|
81
|
-
*
|
|
82
|
-
* IConfigFieldDef,
|
|
86
|
+
* A field of the provider's OWN configuration (not of the subscription). It is the common contract
|
|
87
|
+
* IConfigFieldDef, the same one senders, webhooks, idps and logins use.
|
|
83
88
|
*/
|
|
84
89
|
export type IProviderFieldDef = IConfigFieldDef;
|
|
85
90
|
/**
|
|
86
|
-
*
|
|
91
|
+
* What a provider needs from the rest of the core. Same shape as the 'providers' list of a channel's
|
|
92
|
+
* requirements, so both read alike; see IProvider.requirements.
|
|
93
|
+
*/
|
|
94
|
+
export interface IProviderRequirements {
|
|
95
|
+
/** Ids of the providers this one consumes (never a pluvider 'plugin:<name>' id). */
|
|
96
|
+
providers: string[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* What the core hands ANY extension that consumes providers, in its onProvidersReady(). It is already
|
|
100
|
+
* bound to the consumer's identity — the core stamps the instance and builds this for it — so the
|
|
101
|
+
* extension names nobody: it asks for a producer by id and gets a handle, or undefined if it is not
|
|
102
|
+
* installed (a SOFT dependency, to be survived).
|
|
103
|
+
*
|
|
104
|
+
* It is the same door for every family: a sender that emails through SES, a webhook, a homepage that
|
|
105
|
+
* shows the state of a cloud account, an IdP that reads Cognito or B2C — all of them reach a provider
|
|
106
|
+
* through this and nothing else, so the core's registry of who consumes what stays true.
|
|
107
|
+
*/
|
|
108
|
+
export interface IProviderAccess {
|
|
109
|
+
getProvider(providerId: string): IProviderHandle | undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* What a provider can tell about itself.
|
|
87
113
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
114
|
+
* It exists so kwirth can say whether something is being consumed or emitting to nobody, which is one
|
|
115
|
+
* of the few questions NOBODY can answer from the outside: each provider keeps its subscribers in its
|
|
116
|
+
* own structure, and until now there was no way to ask it.
|
|
91
117
|
*
|
|
92
|
-
* ⚠️
|
|
93
|
-
*
|
|
94
|
-
*
|
|
118
|
+
* ⚠️ Only the NUMBER, not who they are: 'IProviderSubscriber' is a single-method interface and carries
|
|
119
|
+
* no identity, so a provider has nothing to identify them with. Drawing the graph of who consumes whom
|
|
120
|
+
* will require widening that contract, and that is a separate decision.
|
|
95
121
|
*/
|
|
96
122
|
export interface IProviderStats {
|
|
97
|
-
/**
|
|
123
|
+
/** How many subscribers it has RIGHT NOW. Zero means it is emitting to nobody. */
|
|
98
124
|
subscribers: number;
|
|
99
125
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* "no informa", igual que el resto.
|
|
126
|
+
* DELIVERIES made since the provider started: one per call to processProviderEvent, not one per
|
|
127
|
+
* event produced. OPTIONAL: whoever does not keep it is shown as "not reported", like the rest.
|
|
103
128
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
129
|
+
* Deliveries are counted instead of events on purpose. A provider that produces a thousand events
|
|
130
|
+
* and filters them all out is moving nothing, and the number that is useful to whoever operates is
|
|
131
|
+
* the work that ACTUALLY HAPPENS. Besides, the place to increment is unambiguous — right where the
|
|
132
|
+
* subscriber is already called — and that means wiring it in sixteen providers does not depend on
|
|
133
|
+
* interpreting each one's code.
|
|
108
134
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
135
|
+
* It is a RUNNING TOTAL, not a rate: whoever reads it subtracts two readings and divides by time.
|
|
136
|
+
* The provider must know nothing about windows or averages — that would force keeping history in
|
|
137
|
+
* the hot path, which is exactly what must not happen.
|
|
112
138
|
*
|
|
113
|
-
* ⚠️
|
|
114
|
-
*
|
|
115
|
-
*
|
|
139
|
+
* ⚠️ The increment goes RIGHT NEXT to the subscriber call, and it is an integer. No per-event
|
|
140
|
+
* timestamps, no growing arrays, no new objects: what hurts in Node is not the counter, it is the
|
|
141
|
+
* garbage it generates.
|
|
116
142
|
*/
|
|
117
143
|
events?: number;
|
|
118
|
-
/**
|
|
144
|
+
/** Delivery errors, under the same criterion: a running total, and cheap. */
|
|
119
145
|
errors?: number;
|
|
120
146
|
}
|
|
121
|
-
/**
|
|
122
|
-
* What a provider writes its log with. It is the common extension logger — the need turned out to be
|
|
123
|
-
* the same for senders, so it lives in IExtension. Kept as a name of its own because it is already
|
|
124
|
-
* published and providers compile against it.
|
|
125
|
-
*/
|
|
126
|
-
export type IProviderLogger = IExtensionLogger;
|
|
127
147
|
/**
|
|
128
148
|
* Interface that all provider plugins must implement.
|
|
129
149
|
* Use 'any' for clusterInfo to avoid pulling in kubernetes/docker dependencies.
|
|
@@ -136,41 +156,40 @@ export interface IProvider extends IExtension {
|
|
|
136
156
|
removeSubscriber(c: IProviderSubscriber): Promise<void>;
|
|
137
157
|
updateSubscription?(c: IProviderSubscriber, data: any): Promise<void>;
|
|
138
158
|
/**
|
|
139
|
-
* @deprecated
|
|
140
|
-
*
|
|
141
|
-
*
|
|
159
|
+
* @deprecated The core no longer feeds this method: a provider owns its own configuration and
|
|
160
|
+
* serves it through 'configRouter'. It is kept for compatibility with third-party providers that
|
|
161
|
+
* still use the core-managed config.
|
|
142
162
|
*/
|
|
143
163
|
configure?(config: Record<string, unknown>): void;
|
|
144
164
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* payload escribir.
|
|
165
|
+
* Subscription help. OPTIONAL: whoever writes a provider adds it if they want to. Without it the
|
|
166
|
+
* consumer keeps working, it simply has nothing to show the user about which payload to write.
|
|
148
167
|
*/
|
|
149
168
|
getSubscriptionHelp?(): IProviderSubscriptionHelp;
|
|
150
169
|
/**
|
|
151
|
-
*
|
|
152
|
-
* ISender.getConfigNames).
|
|
153
|
-
*
|
|
154
|
-
*
|
|
170
|
+
* Names of the configurations the provider has defined (the equivalent of
|
|
171
|
+
* ISender.getConfigNames). OPTIONAL: it only makes sense on a provider that owns its
|
|
172
|
+
* configuration. The extension manager uses it to show how many there are on the card, just as it
|
|
173
|
+
* does with senders. It exposes no values, only names.
|
|
155
174
|
*/
|
|
156
175
|
getConfigNames?(): string[];
|
|
157
176
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
177
|
+
* Configuration schema of the provider itself, which kwirth uses to draw a generic form.
|
|
178
|
+
* This is the STANDARD way to declare it, the same as ISender.getConfigSchema and IWebhook.
|
|
160
179
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
180
|
+
* A provider nobody subscribes to and that exposes no router is NEVER instantiated, so in that
|
|
181
|
+
* case there is nobody to ask: for those, also export a 'schema' constant with the same array from
|
|
182
|
+
* the back.js, which the core reads at install time without instantiating anything.
|
|
164
183
|
*/
|
|
165
184
|
getConfigSchema?(): IProviderFieldDef[];
|
|
166
185
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
186
|
+
* What the provider knows about itself right now. OPTIONAL, like the rest of this block: whoever
|
|
187
|
+
* does not implement it is shown as "not reported", which is different from zero — a zero would be
|
|
188
|
+
* a claim nobody can back up.
|
|
170
189
|
*
|
|
171
|
-
* ⚠️
|
|
172
|
-
*
|
|
173
|
-
*
|
|
190
|
+
* ⚠️ It has to be CHEAP: return what you already have, do not compute it. It is called when
|
|
191
|
+
* somebody opens a status screen, but a provider does not know how often, and walking structures
|
|
192
|
+
* here turns a query into work for everyone.
|
|
174
193
|
*/
|
|
175
194
|
getStats?(): IProviderStats;
|
|
176
195
|
/**
|
|
@@ -183,31 +202,61 @@ export interface IProvider extends IExtension {
|
|
|
183
202
|
* Why it exists: channels get a 'backChannelObject' to log with, providers got nothing, so the
|
|
184
203
|
* only thing left to them was 'console.log'. That comes out with no timestamp, no level and no
|
|
185
204
|
* component — an error from a provider looks exactly like an informational line, and nothing can
|
|
186
|
-
* be filtered. With this, a provider's line reads '[
|
|
205
|
+
* be filtered. With this, a provider's line reads '[prov] [ERRO] [longhorn] ...' and the
|
|
187
206
|
* provider does not even have to write its own id: the core puts it there.
|
|
188
207
|
*/
|
|
189
|
-
setLogger?(logger:
|
|
208
|
+
setLogger?(logger: IExtensionLogger): void;
|
|
209
|
+
/**
|
|
210
|
+
* Called once, after EVERY provider and pluvider is registered and started. This is where a
|
|
211
|
+
* provider that CONSUMES another one subscribes to it.
|
|
212
|
+
*
|
|
213
|
+
* ⚠️ Do not subscribe from startProvider(). Whether the producer is registered by then depends on
|
|
214
|
+
* which startup loop instantiated it and on the order within that loop, so it would work or not
|
|
215
|
+
* for reasons you cannot see from your own code.
|
|
216
|
+
*
|
|
217
|
+
* 🔴 Whatever you subscribe to here, unsubscribe in stopProvider(). Skipping it does not leak one
|
|
218
|
+
* object: the producer keeps feeding an instance nobody uses any more, and every hot reload
|
|
219
|
+
* leaves another ghost behind holding whatever that instance held.
|
|
220
|
+
*
|
|
221
|
+
* OPTIONAL, like the rest of this block: a provider that does not implement it is never called,
|
|
222
|
+
* and an older core that does not know about it simply never calls anyone.
|
|
223
|
+
*/
|
|
224
|
+
onProvidersReady?(): void | Promise<void>;
|
|
225
|
+
/**
|
|
226
|
+
* The providers this provider CONSUMES. The core instantiates them even if no channel asks for
|
|
227
|
+
* them, the same way it instantiates the ones a channel lists in its own requirements.
|
|
228
|
+
*
|
|
229
|
+
* Without it, a producer nobody else asks for and that exposes no router is never instantiated,
|
|
230
|
+
* and getProvider() hands the consumer 'undefined' in onProvidersReady(). The dependency stays
|
|
231
|
+
* SOFT: one that is not installed is a warning, and the consumer must survive its absence.
|
|
232
|
+
*
|
|
233
|
+
* Pluvider ids ('plugin:<name>') are not listed here: a pluvider exists when its plugin is
|
|
234
|
+
* installed, the core cannot create it.
|
|
235
|
+
*
|
|
236
|
+
* OPTIONAL: a provider that consumes nothing leaves it out, and an older core ignores it.
|
|
237
|
+
*/
|
|
238
|
+
requirements?: IProviderRequirements;
|
|
190
239
|
startProvider(): Promise<void>;
|
|
191
240
|
stopProvider(): Promise<void>;
|
|
192
241
|
router: any;
|
|
193
242
|
routerAlias: string | undefined;
|
|
194
243
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
244
|
+
* The provider wants the body of the requests to its public router RAW (a Buffer), untouched by
|
|
245
|
+
* the core's global bodyParser.
|
|
197
246
|
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* webhooks
|
|
247
|
+
* Needed for anything that is not plain JSON: ndjson, msgpack, protobuf, or verifying a signature
|
|
248
|
+
* over the exact bytes that arrived. Without this, an extension that INGESTS receives the body
|
|
249
|
+
* already parsed — and with the global parser's limit — which is exactly what the core solved for
|
|
250
|
+
* webhooks by mounting them in front.
|
|
202
251
|
*
|
|
203
|
-
*
|
|
252
|
+
* It defaults to false: providers that read 'req.body' as an object today are unaffected.
|
|
204
253
|
*/
|
|
205
254
|
readonly rawBody?: boolean;
|
|
206
255
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* '/core/providerconfig/<providerId>'.
|
|
210
|
-
*
|
|
256
|
+
* The provider's management router (its own configuration). The core ALWAYS mounts it behind
|
|
257
|
+
* accessKey validation, just as it does with a channel's endpoints, at the route
|
|
258
|
+
* '/core/providerconfig/<providerId>'. It is a separate path from 'router', which is public and may
|
|
259
|
+
* receive external traffic (OTLP, third-party POSTs) and therefore cannot demand an accessKey.
|
|
211
260
|
*/
|
|
212
261
|
configRouter?: any;
|
|
213
262
|
apiKeyApi: any | undefined;
|
package/dist/ISender.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ISenderMessage, ISenderConfig, ISenderAccess, ISenderStoredConfig, ISenderResult, TConfigFieldType, IConfigFieldDef, IExtensionNodeMeta } from '@kwirthmagnify/kwirth-common';
|
|
2
2
|
import { IExtension, IExtensionLogger } from './IExtension';
|
|
3
3
|
export { ISenderMessage, ISenderConfig, ISenderAccess, ISenderStoredConfig, ISenderResult };
|
|
4
|
-
/** @deprecated
|
|
4
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
5
5
|
export type SenderFieldType = TConfigFieldType;
|
|
6
|
-
/**
|
|
6
|
+
/** A sender's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
|
|
7
7
|
export type ISenderFieldDef = IConfigFieldDef;
|
|
8
|
-
/** @deprecated
|
|
8
|
+
/** @deprecated use IExtensionNodeMeta, common to every extension. */
|
|
9
9
|
export type ISenderNodeMeta = IExtensionNodeMeta;
|
|
10
10
|
export interface ISender extends IExtension {
|
|
11
11
|
readonly id: string;
|
package/dist/IWebhook.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { IWebhookEvent, IWebhookConfig, IWebhookAccess, IWebhookConsumer, IWebhookStoredConfig, TConfigFieldType, IConfigFieldDef, IExtensionNodeMeta } from '@kwirthmagnify/kwirth-common';
|
|
2
2
|
import { IExtension } from './IExtension';
|
|
3
3
|
export { IWebhookEvent, IWebhookConfig, IWebhookAccess, IWebhookConsumer, IWebhookStoredConfig };
|
|
4
|
-
/** @deprecated
|
|
4
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
5
5
|
export type WebhookFieldType = TConfigFieldType;
|
|
6
|
-
/**
|
|
6
|
+
/** A webhook's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
|
|
7
7
|
export type IWebhookFieldDef = IConfigFieldDef;
|
|
8
|
-
/** @deprecated
|
|
8
|
+
/** @deprecated use IExtensionNodeMeta, common to every extension. */
|
|
9
9
|
export type IWebhookNodeMeta = IExtensionNodeMeta;
|
|
10
10
|
export interface IWebhook extends IExtension {
|
|
11
11
|
readonly id: string;
|
|
@@ -7,9 +7,9 @@ export interface ICrdInformerHandlers {
|
|
|
7
7
|
onError?: (err: any) => void;
|
|
8
8
|
}
|
|
9
9
|
export declare function createCrdInformer(clusterInfo: any, apiGroup: string, apiVersion: string, plural: string, handlers: ICrdInformerHandlers): any;
|
|
10
|
-
/**
|
|
11
|
-
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses')
|
|
12
|
-
*
|
|
10
|
+
/** Generic informer for ANY resource (core or group): the caller supplies the `watchPath` (e.g.
|
|
11
|
+
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') and the `listFn` that returns {items}.
|
|
12
|
+
* Reused by createCrdInformer and by consumers of core resources (exposure, for instance). */
|
|
13
13
|
export declare function createInformer(clusterInfo: any, watchPath: string, listFn: () => Promise<{
|
|
14
14
|
items: any[];
|
|
15
15
|
}>, handlers: ICrdInformerHandlers): any;
|
package/dist/KubernetesTools.js
CHANGED
|
@@ -127,9 +127,9 @@ function createCrdInformer(clusterInfo, apiGroup, apiVersion, plural, handlers)
|
|
|
127
127
|
.then((res) => res);
|
|
128
128
|
return createInformer(clusterInfo, path, listFunction, handlers);
|
|
129
129
|
}
|
|
130
|
-
/**
|
|
131
|
-
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses')
|
|
132
|
-
*
|
|
130
|
+
/** Generic informer for ANY resource (core or group): the caller supplies the `watchPath` (e.g.
|
|
131
|
+
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') and the `listFn` that returns {items}.
|
|
132
|
+
* Reused by createCrdInformer and by consumers of core resources (exposure, for instance). */
|
|
133
133
|
function createInformer(clusterInfo, watchPath, listFn, handlers) {
|
|
134
134
|
const informer = k8s.makeInformer(clusterInfo.kubeConfig, watchPath, listFn);
|
|
135
135
|
if (handlers.onAdd)
|
package/dist/github.js
CHANGED
|
@@ -16,9 +16,9 @@ async function ghGet(base, resource, accessToken) {
|
|
|
16
16
|
async function githubIdentityFromToken(apiBaseUrl, accessToken) {
|
|
17
17
|
const base = apiBaseUrl.replace(/\/+$/, '');
|
|
18
18
|
const user = await ghGet(base, '/user', accessToken);
|
|
19
|
-
// /user/emails
|
|
19
|
+
// /user/emails can fail when the user:email scope is missing; in that case we fall back to the public email
|
|
20
20
|
const emails = await ghGet(base, '/user/emails', accessToken).catch(() => []);
|
|
21
|
-
//
|
|
21
|
+
// we prefer the primary email; failing that, the first verified one; failing that, the first there is
|
|
22
22
|
const chosen = emails.find(e => e.primary) ?? emails.find(e => e.verified) ?? emails[0];
|
|
23
23
|
return {
|
|
24
24
|
email: chosen?.email ?? user.email ?? '',
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __exportStar(require("./ISender"), exports);
|
|
|
24
24
|
__exportStar(require("./IWebhook"), exports);
|
|
25
25
|
__exportStar(require("./IIdpConnector"), exports);
|
|
26
26
|
__exportStar(require("./IExtension"), exports);
|
|
27
|
+
__exportStar(require("./Consumer"), exports);
|
|
27
28
|
__exportStar(require("./oidc"), exports);
|
|
28
29
|
__exportStar(require("./oauth2"), exports);
|
|
29
30
|
__exportStar(require("./github"), exports);
|
package/dist/oauth2.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.oauth2ConfigSchema = oauth2ConfigSchema;
|
|
4
4
|
exports.oauth2BuildAuthorizationUrl = oauth2BuildAuthorizationUrl;
|
|
5
5
|
exports.oauth2HandleCallback = oauth2HandleCallback;
|
|
6
|
-
//
|
|
6
|
+
// base schema of an OAuth2 IdP (the connector adds its URLs where applicable; clientSecret 'password' → masked)
|
|
7
7
|
function oauth2ConfigSchema() {
|
|
8
8
|
return [
|
|
9
9
|
{ name: 'clientId', label: 'Client ID', type: 'text', required: true },
|
|
@@ -26,7 +26,7 @@ function oauth2BuildAuthorizationUrl(config, ctx, ep) {
|
|
|
26
26
|
}
|
|
27
27
|
return url.toString();
|
|
28
28
|
}
|
|
29
|
-
//
|
|
29
|
+
// exchanges the 'code' for an access_token (back-channel) and delegates userinfo to fetchIdentity(accessToken).
|
|
30
30
|
async function oauth2HandleCallback(config, ctx, ep, fetchIdentity) {
|
|
31
31
|
const body = new URLSearchParams();
|
|
32
32
|
body.set('grant_type', 'authorization_code');
|
|
@@ -36,7 +36,7 @@ async function oauth2HandleCallback(config, ctx, ep, fetchIdentity) {
|
|
|
36
36
|
body.set('redirect_uri', ctx.redirectUri);
|
|
37
37
|
if (ep.usePkce)
|
|
38
38
|
body.set('code_verifier', ctx.codeVerifier);
|
|
39
|
-
// Accept: application/json →
|
|
39
|
+
// Accept: application/json → some IdPs (GitHub) return form-urlencoded without this header
|
|
40
40
|
const res = await fetch(ep.tokenEndpoint, {
|
|
41
41
|
method: 'POST',
|
|
42
42
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
|
package/dist/oidc.js
CHANGED
|
@@ -40,7 +40,7 @@ exports.oidcBuildAuthorizationUrl = oidcBuildAuthorizationUrl;
|
|
|
40
40
|
exports.oidcHandleCallback = oidcHandleCallback;
|
|
41
41
|
const openid_client_1 = require("openid-client");
|
|
42
42
|
const jose = __importStar(require("jose"));
|
|
43
|
-
//
|
|
43
|
+
// standard config schema of an OIDC IdP (clientSecret is 'password' → masked in the UI)
|
|
44
44
|
function oidcConfigSchema() {
|
|
45
45
|
return [
|
|
46
46
|
{ name: 'clientId', label: 'Client ID', type: 'text', required: true },
|
|
@@ -62,7 +62,7 @@ async function makeClient(config, redirectUri, defaultIssuer) {
|
|
|
62
62
|
});
|
|
63
63
|
return { issuer, client };
|
|
64
64
|
}
|
|
65
|
-
//
|
|
65
|
+
// maps the id_token claims to Kwirth's identity (email fallback + assumed verified)
|
|
66
66
|
function mapOidcIdentity(claims, opts) {
|
|
67
67
|
const emailClaims = opts?.emailClaims ?? ['email'];
|
|
68
68
|
let email = '';
|
|
@@ -80,7 +80,7 @@ function mapOidcIdentity(claims, opts) {
|
|
|
80
80
|
sub: claims.sub !== undefined ? String(claims.sub) : undefined
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
//
|
|
83
|
+
// is the tenant (tid) allowed? (an empty allowlist means any tenant)
|
|
84
84
|
function tenantAllowed(tid, allowed) {
|
|
85
85
|
if (!allowed || allowed.length === 0)
|
|
86
86
|
return true;
|
|
@@ -103,9 +103,10 @@ async function oidcBuildAuthorizationUrl(config, ctx, defaultIssuer) {
|
|
|
103
103
|
async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
|
|
104
104
|
const { issuer, client } = await makeClient(config, ctx.redirectUri, defaultIssuer);
|
|
105
105
|
if (opts?.multiTenant) {
|
|
106
|
-
// openid-client
|
|
107
|
-
//
|
|
108
|
-
//
|
|
106
|
+
// openid-client validates iss LITERALLY, and in multi-tenant the discovered issuer carries the
|
|
107
|
+
// {tenantid} placeholder; so we do the raw exchange (grant, without validating the id_token) and
|
|
108
|
+
// validate the id_token by hand with jose: signature (the issuer's JWKS) + iss against the
|
|
109
|
+
// token's concrete tid.
|
|
109
110
|
const tokenSet = await client.grant({
|
|
110
111
|
grant_type: 'authorization_code',
|
|
111
112
|
code: ctx.code,
|
|
@@ -114,7 +115,7 @@ async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
|
|
|
114
115
|
});
|
|
115
116
|
if (!tokenSet.id_token)
|
|
116
117
|
throw new Error('OIDC multi-tenant: token response has no id_token');
|
|
117
|
-
const tid = tokenSet.claims().tid; //
|
|
118
|
+
const tid = tokenSet.claims().tid; // unverified; only to learn the tenant
|
|
118
119
|
if (!tid)
|
|
119
120
|
throw new Error('OIDC multi-tenant: id_token has no tid claim');
|
|
120
121
|
if (!tenantAllowed(tid, opts.allowedTenants))
|
|
@@ -130,8 +131,8 @@ async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
|
|
|
130
131
|
});
|
|
131
132
|
return mapOidcIdentity(payload, opts);
|
|
132
133
|
}
|
|
133
|
-
// single-tenant:
|
|
134
|
-
//
|
|
134
|
+
// single-tenant: we pass the callback's raw params (they include iss for RFC 9207, and state); the
|
|
135
|
+
// core already validates state, but openid-client demands checks.state when the param is present.
|
|
135
136
|
const params = ctx.params ?? { code: ctx.code };
|
|
136
137
|
const checks = { code_verifier: ctx.codeVerifier };
|
|
137
138
|
if (params.state !== undefined)
|