@eventuras/fides-auth 0.12.0 → 0.14.0
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/heartbeat.d.ts +20 -2
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +37 -3
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/oauth-logging.d.ts +15 -0
- package/dist/oauth-logging.d.ts.map +1 -1
- package/dist/oauth-logging.js +63 -1
- package/dist/oauth-logging.js.map +1 -1
- package/dist/oauth.d.ts +2 -2
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +9 -6
- package/dist/oauth.js.map +1 -1
- package/dist/oidc-discovery.d.ts +42 -0
- package/dist/oidc-discovery.d.ts.map +1 -0
- package/dist/oidc-discovery.js +82 -0
- package/dist/oidc-discovery.js.map +1 -0
- package/dist/providers/vipps/client.d.ts +6 -3
- package/dist/providers/vipps/client.d.ts.map +1 -1
- package/dist/providers/vipps/index.js +8 -5
- package/dist/providers/vipps/index.js.map +1 -1
- package/dist/server/heartbeat-handler.d.ts +10 -3
- package/dist/server/heartbeat-handler.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +182 -53
- package/dist/server/index.js.map +1 -1
- package/dist/server/oidc-callback.d.ts.map +1 -1
- package/dist/server/oidc-logout.d.ts.map +1 -1
- package/dist/server/session.d.ts +75 -3
- package/dist/server/session.d.ts.map +1 -1
- package/dist/session-cookies.d.ts +25 -0
- package/dist/session-cookies.d.ts.map +1 -1
- package/dist/session-cookies.js +35 -14
- package/dist/session-cookies.js.map +1 -1
- package/dist/session-events.d.ts +149 -0
- package/dist/session-events.d.ts.map +1 -0
- package/dist/session-events.js +96 -0
- package/dist/session-events.js.map +1 -0
- package/dist/silent-login.d.ts.map +1 -1
- package/dist/silent-login.js +2 -1
- package/dist/silent-login.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/discovery-cache.md +53 -0
- package/docs/session-events.md +207 -0
- package/package.json +15 -5
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { OAuthConfig } from './oauth';
|
|
2
|
+
import * as openid from 'openid-client';
|
|
3
|
+
/** Options for {@link configureDiscoveryCache}. */
|
|
4
|
+
export interface DiscoveryCacheOptions {
|
|
5
|
+
/**
|
|
6
|
+
* How long a discovered configuration stays reusable, in milliseconds.
|
|
7
|
+
* Pass `0` to disable caching and discover on every call. Must be finite and
|
|
8
|
+
* non-negative; anything else throws rather than quietly disabling the cache.
|
|
9
|
+
* @default 300_000 (5 minutes)
|
|
10
|
+
*/
|
|
11
|
+
ttlMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Adjusts the discovery cache. Existing entries are dropped so the new setting
|
|
15
|
+
* takes effect immediately.
|
|
16
|
+
*
|
|
17
|
+
* @throws TypeError if `ttlMs` is not a finite, non-negative number. Rejecting
|
|
18
|
+
* loudly matters here because the alternative is silent: `NaN` — what
|
|
19
|
+
* `Number(process.env.DISCOVERY_TTL)` yields when the variable is unset — would
|
|
20
|
+
* otherwise turn the cache off and restore a discovery round-trip on every
|
|
21
|
+
* token operation, with nothing to show for it in the logs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function configureDiscoveryCache(options: DiscoveryCacheOptions): void;
|
|
24
|
+
/**
|
|
25
|
+
* Drops every cached configuration.
|
|
26
|
+
*
|
|
27
|
+
* Call this after changing provider configuration that discovery would not
|
|
28
|
+
* otherwise pick up until the TTL expires. Tests should call it between cases,
|
|
29
|
+
* since the cache is module state.
|
|
30
|
+
*/
|
|
31
|
+
export declare function clearDiscoveryCache(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Discovers the provider's OpenID configuration, reusing a recent result.
|
|
34
|
+
*
|
|
35
|
+
* Every fides-auth flow goes through here, so this is also the single place
|
|
36
|
+
* where the client authentication method is decided.
|
|
37
|
+
*
|
|
38
|
+
* @param config - OAuth configuration identifying the provider and client.
|
|
39
|
+
* @returns The openid-client `Configuration` for this provider and client.
|
|
40
|
+
*/
|
|
41
|
+
export declare function discoverConfiguration(config: OAuthConfig): Promise<openid.Configuration>;
|
|
42
|
+
//# sourceMappingURL=oidc-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-discovery.d.ts","sourceRoot":"","sources":["../src/oidc-discovery.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAiC3C,mDAAmD;AACnD,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAU5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAWD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAgC/B"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createLogger } from "./logger.js";
|
|
2
|
+
import * as openid from "openid-client";
|
|
3
|
+
//#region src/oidc-discovery.ts
|
|
4
|
+
var logger = createLogger({ namespace: "fides-auth:oidc-discovery" });
|
|
5
|
+
/**
|
|
6
|
+
* Default lifetime of a cached configuration.
|
|
7
|
+
*
|
|
8
|
+
* Discovery documents change rarely — a new endpoint, a signing-key rollover —
|
|
9
|
+
* and openid-client refetches JWKS on its own schedule, so this only bounds how
|
|
10
|
+
* long a *metadata* change takes to be noticed. Five minutes keeps that window
|
|
11
|
+
* short while removing essentially every discovery request from the hot path.
|
|
12
|
+
*/
|
|
13
|
+
var DEFAULT_TTL_MS = 3e5;
|
|
14
|
+
var cache = /* @__PURE__ */ new Map();
|
|
15
|
+
var ttlMs = DEFAULT_TTL_MS;
|
|
16
|
+
/**
|
|
17
|
+
* Adjusts the discovery cache. Existing entries are dropped so the new setting
|
|
18
|
+
* takes effect immediately.
|
|
19
|
+
*
|
|
20
|
+
* @throws TypeError if `ttlMs` is not a finite, non-negative number. Rejecting
|
|
21
|
+
* loudly matters here because the alternative is silent: `NaN` — what
|
|
22
|
+
* `Number(process.env.DISCOVERY_TTL)` yields when the variable is unset — would
|
|
23
|
+
* otherwise turn the cache off and restore a discovery round-trip on every
|
|
24
|
+
* token operation, with nothing to show for it in the logs.
|
|
25
|
+
*/
|
|
26
|
+
function configureDiscoveryCache(options) {
|
|
27
|
+
if (options.ttlMs !== void 0) {
|
|
28
|
+
if (!Number.isFinite(options.ttlMs) || options.ttlMs < 0) throw new TypeError(`configureDiscoveryCache: ttlMs must be a finite number >= 0, got ${options.ttlMs}`);
|
|
29
|
+
ttlMs = options.ttlMs;
|
|
30
|
+
}
|
|
31
|
+
cache.clear();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Drops every cached configuration.
|
|
35
|
+
*
|
|
36
|
+
* Call this after changing provider configuration that discovery would not
|
|
37
|
+
* otherwise pick up until the TTL expires. Tests should call it between cases,
|
|
38
|
+
* since the cache is module state.
|
|
39
|
+
*/
|
|
40
|
+
function clearDiscoveryCache() {
|
|
41
|
+
cache.clear();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Issuer and client identify a configuration; the secret is checked separately.
|
|
45
|
+
* JSON-encoded rather than concatenated so no separator can be forged by a
|
|
46
|
+
* value that happens to contain it.
|
|
47
|
+
*/
|
|
48
|
+
function cacheKey(config) {
|
|
49
|
+
return JSON.stringify([config.issuer, config.clientId]);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Discovers the provider's OpenID configuration, reusing a recent result.
|
|
53
|
+
*
|
|
54
|
+
* Every fides-auth flow goes through here, so this is also the single place
|
|
55
|
+
* where the client authentication method is decided.
|
|
56
|
+
*
|
|
57
|
+
* @param config - OAuth configuration identifying the provider and client.
|
|
58
|
+
* @returns The openid-client `Configuration` for this provider and client.
|
|
59
|
+
*/
|
|
60
|
+
async function discoverConfiguration(config) {
|
|
61
|
+
const key = cacheKey(config);
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const cached = cache.get(key);
|
|
64
|
+
if (cached && cached.expiresAt > now && cached.clientSecret === config.clientSecret) return cached.configuration;
|
|
65
|
+
logger.debug({ issuer: config.issuer }, "Discovering OpenID configuration");
|
|
66
|
+
const configuration = openid.discovery(new URL(config.issuer), config.clientId, config.clientSecret, openid.ClientSecretPost(config.clientSecret));
|
|
67
|
+
if (ttlMs > 0) {
|
|
68
|
+
cache.set(key, {
|
|
69
|
+
configuration,
|
|
70
|
+
expiresAt: now + ttlMs,
|
|
71
|
+
clientSecret: config.clientSecret
|
|
72
|
+
});
|
|
73
|
+
configuration.catch(() => {
|
|
74
|
+
if (cache.get(key)?.configuration === configuration) cache.delete(key);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return configuration;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { clearDiscoveryCache, configureDiscoveryCache, discoverConfiguration };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=oidc-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-discovery.js","names":[],"sources":["../src/oidc-discovery.ts"],"sourcesContent":["// oidc-discovery.ts\n//\n// One place where fides-auth turns an OAuthConfig into an openid-client\n// Configuration, and one cache in front of it.\n//\n// Discovery was previously performed inline at every call site, which meant an\n// HTTP GET to the provider's /.well-known/openid-configuration before *every*\n// token operation. On the heartbeat path that is one extra round-trip per user\n// every few minutes: it doubles the latency of a refresh, doubles the load on\n// the provider, and doubles the surface on which a refresh can fail — a\n// discovery blip is indistinguishable from a token-endpoint blip to the caller.\n//\n// Reusing a Configuration is openid-client's intended usage; its own examples\n// build one at startup and keep it.\n\nimport * as openid from 'openid-client';\n\nimport { createLogger } from './logger';\nimport type { OAuthConfig } from './oauth';\n\nconst logger = createLogger({ namespace: 'fides-auth:oidc-discovery' });\n\n/**\n * Default lifetime of a cached configuration.\n *\n * Discovery documents change rarely — a new endpoint, a signing-key rollover —\n * and openid-client refetches JWKS on its own schedule, so this only bounds how\n * long a *metadata* change takes to be noticed. Five minutes keeps that window\n * short while removing essentially every discovery request from the hot path.\n */\nconst DEFAULT_TTL_MS = 5 * 60_000;\n\ninterface CacheEntry {\n /**\n * Held as the promise, not the resolved value, so concurrent callers share one\n * round-trip. Caching only the result would let a burst of requests each start\n * their own discovery before the first finished — precisely what a fleet of\n * heartbeats produces.\n */\n configuration: Promise<openid.Configuration>;\n expiresAt: number;\n /**\n * The secret this entry was built with, compared on lookup so a rotated client\n * secret invalidates the entry without the secret going into the cache key.\n */\n clientSecret: string;\n}\n\nconst cache = new Map<string, CacheEntry>();\nlet ttlMs = DEFAULT_TTL_MS;\n\n/** Options for {@link configureDiscoveryCache}. */\nexport interface DiscoveryCacheOptions {\n /**\n * How long a discovered configuration stays reusable, in milliseconds.\n * Pass `0` to disable caching and discover on every call. Must be finite and\n * non-negative; anything else throws rather than quietly disabling the cache.\n * @default 300_000 (5 minutes)\n */\n ttlMs?: number;\n}\n\n/**\n * Adjusts the discovery cache. Existing entries are dropped so the new setting\n * takes effect immediately.\n *\n * @throws TypeError if `ttlMs` is not a finite, non-negative number. Rejecting\n * loudly matters here because the alternative is silent: `NaN` — what\n * `Number(process.env.DISCOVERY_TTL)` yields when the variable is unset — would\n * otherwise turn the cache off and restore a discovery round-trip on every\n * token operation, with nothing to show for it in the logs.\n */\nexport function configureDiscoveryCache(options: DiscoveryCacheOptions): void {\n if (options.ttlMs !== undefined) {\n if (!Number.isFinite(options.ttlMs) || options.ttlMs < 0) {\n throw new TypeError(\n `configureDiscoveryCache: ttlMs must be a finite number >= 0, got ${options.ttlMs}`,\n );\n }\n ttlMs = options.ttlMs;\n }\n cache.clear();\n}\n\n/**\n * Drops every cached configuration.\n *\n * Call this after changing provider configuration that discovery would not\n * otherwise pick up until the TTL expires. Tests should call it between cases,\n * since the cache is module state.\n */\nexport function clearDiscoveryCache(): void {\n cache.clear();\n}\n\n/**\n * Issuer and client identify a configuration; the secret is checked separately.\n * JSON-encoded rather than concatenated so no separator can be forged by a\n * value that happens to contain it.\n */\nfunction cacheKey(config: OAuthConfig): string {\n return JSON.stringify([config.issuer, config.clientId]);\n}\n\n/**\n * Discovers the provider's OpenID configuration, reusing a recent result.\n *\n * Every fides-auth flow goes through here, so this is also the single place\n * where the client authentication method is decided.\n *\n * @param config - OAuth configuration identifying the provider and client.\n * @returns The openid-client `Configuration` for this provider and client.\n */\nexport async function discoverConfiguration(\n config: OAuthConfig,\n): Promise<openid.Configuration> {\n const key = cacheKey(config);\n const now = Date.now();\n const cached = cache.get(key);\n\n if (cached && cached.expiresAt > now && cached.clientSecret === config.clientSecret) {\n return cached.configuration;\n }\n\n logger.debug({ issuer: config.issuer }, 'Discovering OpenID configuration');\n\n const configuration = openid.discovery(\n new URL(config.issuer),\n config.clientId,\n config.clientSecret,\n // openid-client's own default is client_secret_post; stating it keeps the\n // method visible at the one place that would grow a `clientAuth` option.\n openid.ClientSecretPost(config.clientSecret),\n );\n\n if (ttlMs > 0) {\n cache.set(key, { configuration, expiresAt: now + ttlMs, clientSecret: config.clientSecret });\n\n // Never cache a failure: a provider that was briefly unreachable must be\n // retried on the next call, not remembered as broken for the whole TTL.\n // Guarded so a slow failure cannot evict a newer entry that replaced it.\n configuration.catch(() => {\n if (cache.get(key)?.configuration === configuration) cache.delete(key);\n });\n }\n\n return configuration;\n}\n"],"mappings":";;;AAoBA,IAAM,SAAS,aAAa,EAAE,WAAW,4BAA4B,CAAC;;;;;;;;;AAUtE,IAAM,iBAAiB;AAkBvB,IAAM,wBAAQ,IAAI,IAAwB;AAC1C,IAAI,QAAQ;;;;;;;;;;;AAuBZ,SAAgB,wBAAwB,SAAsC;CAC5E,IAAI,QAAQ,UAAU,KAAA,GAAW;EAC/B,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,KAAK,QAAQ,QAAQ,GACrD,MAAM,IAAI,UACR,oEAAoE,QAAQ,OAC9E;EAEF,QAAQ,QAAQ;CAClB;CACA,MAAM,MAAM;AACd;;;;;;;;AASA,SAAgB,sBAA4B;CAC1C,MAAM,MAAM;AACd;;;;;;AAOA,SAAS,SAAS,QAA6B;CAC7C,OAAO,KAAK,UAAU,CAAC,OAAO,QAAQ,OAAO,QAAQ,CAAC;AACxD;;;;;;;;;;AAWA,eAAsB,sBACpB,QAC+B;CAC/B,MAAM,MAAM,SAAS,MAAM;CAC3B,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,SAAS,MAAM,IAAI,GAAG;CAE5B,IAAI,UAAU,OAAO,YAAY,OAAO,OAAO,iBAAiB,OAAO,cACrE,OAAO,OAAO;CAGhB,OAAO,MAAM,EAAE,QAAQ,OAAO,OAAO,GAAG,kCAAkC;CAE1E,MAAM,gBAAgB,OAAO,UAC3B,IAAI,IAAI,OAAO,MAAM,GACrB,OAAO,UACP,OAAO,cAGP,OAAO,iBAAiB,OAAO,YAAY,CAC7C;CAEA,IAAI,QAAQ,GAAG;EACb,MAAM,IAAI,KAAK;GAAE;GAAe,WAAW,MAAM;GAAO,cAAc,OAAO;EAAa,CAAC;EAK3F,cAAc,YAAY;GACxB,IAAI,MAAM,IAAI,GAAG,CAAC,EAAE,kBAAkB,eAAe,MAAM,OAAO,GAAG;EACvE,CAAC;CACH;CAEA,OAAO;AACT"}
|
|
@@ -15,11 +15,14 @@ import * as openid from 'openid-client';
|
|
|
15
15
|
export declare class VippsLoginClient {
|
|
16
16
|
private readonly config;
|
|
17
17
|
private readonly oauthConfig;
|
|
18
|
-
private configPromise;
|
|
19
18
|
constructor(config: VippsLoginConfig);
|
|
20
19
|
/**
|
|
21
|
-
* Get OpenID Configuration
|
|
22
|
-
*
|
|
20
|
+
* Get the OpenID Configuration, shared with the rest of fides-auth.
|
|
21
|
+
*
|
|
22
|
+
* Previously memoized on the instance, which cached rejections too: one failed
|
|
23
|
+
* discovery — a restart during a provider blip — left every later call
|
|
24
|
+
* rejecting for the lifetime of the client. {@link discoverConfiguration}
|
|
25
|
+
* caches successes with a TTL and drops failures.
|
|
23
26
|
*/
|
|
24
27
|
private getConfig;
|
|
25
28
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/vipps/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/vipps/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,SAAS,CAAC;AAOjB;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAE9B,MAAM,EAAE,gBAAgB;IAwBpC;;;;;;;OAOG;YACW,SAAS;IAIvB;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,WAAW,CAAC;IAK9C;;;;;OAKG;IACG,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;IAMnE;;;;;;;OAOG;IACG,qBAAqB,CACzB,WAAW,EAAE,MAAM,GAAG,GAAG,EACzB,YAAY,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC;IAgCxC;;;;;OAKG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAsD9D;;;;;OAKG;IACG,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC;CAsBjF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLogger } from "../../logger.js";
|
|
2
|
+
import { discoverConfiguration } from "../../oidc-discovery.js";
|
|
2
3
|
import { buildAuthorizationUrl, buildPKCEOptions } from "../../oauth.js";
|
|
3
4
|
import * as openid from "openid-client";
|
|
4
5
|
//#region src/providers/vipps/types.ts
|
|
@@ -64,7 +65,6 @@ var logger = createLogger({
|
|
|
64
65
|
var VippsLoginClient = class {
|
|
65
66
|
config;
|
|
66
67
|
oauthConfig;
|
|
67
|
-
configPromise = null;
|
|
68
68
|
constructor(config) {
|
|
69
69
|
this.config = {
|
|
70
70
|
...config,
|
|
@@ -84,12 +84,15 @@ var VippsLoginClient = class {
|
|
|
84
84
|
}, "VippsLoginClient initialized");
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
* Get OpenID Configuration
|
|
88
|
-
*
|
|
87
|
+
* Get the OpenID Configuration, shared with the rest of fides-auth.
|
|
88
|
+
*
|
|
89
|
+
* Previously memoized on the instance, which cached rejections too: one failed
|
|
90
|
+
* discovery — a restart during a provider blip — left every later call
|
|
91
|
+
* rejecting for the lifetime of the client. {@link discoverConfiguration}
|
|
92
|
+
* caches successes with a TTL and drops failures.
|
|
89
93
|
*/
|
|
90
94
|
async getConfig() {
|
|
91
|
-
|
|
92
|
-
return this.configPromise;
|
|
95
|
+
return discoverConfiguration(this.oauthConfig);
|
|
93
96
|
}
|
|
94
97
|
/**
|
|
95
98
|
* Build PKCE options for authorization flow
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/providers/vipps/types.ts","../../../src/providers/vipps/client.ts"],"sourcesContent":["/**\n * Vipps Login Provider Types\n *\n * Type definitions for Vipps Login (OIDC) integration.\n *\n * @see https://developer.vippsmobilepay.com/docs/APIs/login-api/\n */\n\n/**\n * Configuration for Vipps Login client\n */\nexport interface VippsLoginConfig {\n /** Vipps OIDC issuer URL (e.g., 'https://apitest.vipps.no/access-management-1.0/access') */\n issuer: string;\n /** OAuth Client ID from Vipps */\n clientId: string;\n /** OAuth Client Secret from Vipps */\n clientSecret: string;\n /** OAuth redirect URI (callback URL) */\n redirectUri: string;\n /** OpenID Connect scopes (default: 'openid name phoneNumber address email') */\n scope?: string;\n /** Vipps subscription key (Ocp-Apim-Subscription-Key) - required for userinfo endpoint */\n subscriptionKey?: string;\n /** Merchant serial number - required for userinfo endpoint */\n merchantSerialNumber?: string;\n}\n\n/**\n * Vipps address from userinfo endpoint\n */\nexport interface VippsAddress {\n /** Street address */\n street_address?: string;\n /** Postal code */\n postal_code?: string;\n /** Region/city */\n region?: string;\n /** Country code (e.g., 'NO') */\n country?: string;\n /** Formatted address string */\n formatted?: string;\n /** Address type (e.g., 'home', 'work', 'other') */\n address_type?: string;\n}\n\n/**\n * User information returned from Vipps Login userinfo endpoint\n *\n * @see https://developer.vippsmobilepay.com/docs/APIs/login-api/login-api-guide/#userinfo-endpoint\n */\nexport interface VippsUserInfo {\n /** Subject - unique Vipps user identifier (UUID) */\n sub: string;\n /** Email address */\n email?: string;\n /** Email verification status */\n email_verified?: boolean;\n /** Given name (first name) */\n given_name?: string;\n /** Family name (last name) */\n family_name?: string;\n /** Full name */\n name?: string;\n /** Phone number (with country code, e.g., '4712345678') */\n phone_number?: string;\n /** Phone number verification status */\n phone_number_verified?: boolean;\n /** Birth date (ISO 8601 format, e.g., '1990-01-15') */\n birthdate?: string;\n /** Physical addresses (array) */\n addresses?: VippsAddress[];\n /** Other claims (deprecated) */\n other_addresses?: VippsAddress[];\n}\n\n/**\n * Available Vipps Login scopes\n */\nexport const VippsLoginScopes = {\n /** Required: OpenID Connect base scope */\n OpenId: 'openid',\n /** User's name (given_name, family_name, name) */\n Name: 'name',\n /** Phone number */\n PhoneNumber: 'phoneNumber',\n /** Physical address */\n Address: 'address',\n /** Birth date */\n BirthDate: 'birthDate',\n /** Email address */\n Email: 'email',\n} as const;\n\n/**\n * Default scopes for Vipps Login\n */\nexport const defaultVippsLoginScope = [\n VippsLoginScopes.OpenId,\n VippsLoginScopes.Name,\n VippsLoginScopes.PhoneNumber,\n VippsLoginScopes.Address,\n VippsLoginScopes.Email,\n].join(' ');\n\n/**\n * Vipps API environments\n */\nexport const VippsEnvironments = {\n /** Test environment */\n Test: 'https://apitest.vipps.no',\n /** Production environment */\n Production: 'https://api.vipps.no',\n} as const;\n\n/**\n * Get Vipps OIDC issuer URL for environment\n */\nexport function getVippsIssuer(environment: string): string {\n // Note: Vipps metadata returns issuer with trailing slash\n return `${environment}/access-management-1.0/access/`;\n}\n","/**\n * Vipps Login Client\n *\n * Client for Vipps Login (OIDC) authentication flow.\n * Implements OAuth/OIDC operations with PKCE for Vipps.\n */\n\nimport * as openid from 'openid-client';\n\nimport { createLogger } from '../../logger';\nimport {\n buildAuthorizationUrl,\n buildPKCEOptions,\n type OAuthConfig,\n type PKCEOptions,\n} from '../../oauth';\nimport {\n defaultVippsLoginScope,\n type VippsLoginConfig,\n type VippsUserInfo,\n} from './types';\n\nconst logger = createLogger({\n namespace: 'fides-auth:vipps',\n context: { module: 'VippsLoginClient' },\n});\n\n/**\n * Vipps Login Client\n *\n * Handles Vipps Login (OIDC) authentication flow using PKCE.\n */\nexport class VippsLoginClient {\n private readonly config: VippsLoginConfig;\n private readonly oauthConfig: OAuthConfig;\n private configPromise: Promise<openid.Configuration> | null = null;\n\n constructor(config: VippsLoginConfig) {\n this.config = {\n ...config,\n scope: config.scope ?? defaultVippsLoginScope,\n };\n\n this.oauthConfig = {\n issuer: config.issuer,\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n redirect_uri: config.redirectUri,\n scope: this.config.scope!,\n };\n\n logger.info(\n {\n issuer: config.issuer,\n redirectUri: config.redirectUri,\n scope: this.config.scope,\n },\n 'VippsLoginClient initialized'\n );\n }\n\n /**\n * Get OpenID Configuration (cached)\n * This ensures we use the same Configuration instance across all operations\n */\n private async getConfig(): Promise<openid.Configuration> {\n if (!this.configPromise) {\n this.configPromise = openid.discovery(\n new URL(this.oauthConfig.issuer),\n this.oauthConfig.clientId,\n this.oauthConfig.clientSecret,\n openid.ClientSecretPost(this.oauthConfig.clientSecret)\n );\n }\n return this.configPromise;\n }\n\n /**\n * Build PKCE options for authorization flow\n *\n * @returns PKCE options including code_verifier, code_challenge, and state\n */\n async buildPKCEOptions(): Promise<PKCEOptions> {\n logger.debug('Building PKCE options');\n return buildPKCEOptions(this.oauthConfig);\n }\n\n /**\n * Build authorization URL for Vipps Login\n *\n * @param pkceOptions - PKCE options from buildPKCEOptions()\n * @returns Authorization URL to redirect user to\n */\n async buildAuthorizationUrl(pkceOptions: PKCEOptions): Promise<URL> {\n logger.debug('Building authorization URL');\n const config = await this.getConfig();\n return buildAuthorizationUrl(config, pkceOptions);\n }\n\n /**\n * Exchange authorization code for tokens\n *\n * @param callbackUrl - The full callback URL with code parameter from Vipps\n * @param codeVerifier - PKCE code verifier from initial request\n * @param expectedState - Expected state value for validation (optional)\n * @returns Token response with access_token, id_token, and refresh_token\n */\n async exchangeCodeForTokens(\n callbackUrl: string | URL,\n codeVerifier: string,\n expectedState?: string\n ): Promise<openid.TokenEndpointResponse> {\n logger.debug('Exchanging authorization code for tokens');\n\n try {\n const config = await this.getConfig();\n\n // Convert to URL if string\n const currentUrl = typeof callbackUrl === 'string' ? new URL(callbackUrl) : callbackUrl;\n\n const tokens = await openid.authorizationCodeGrant(config, currentUrl, {\n pkceCodeVerifier: codeVerifier,\n expectedState, // Pass the expected state for validation\n idTokenExpected: true,\n });\n\n logger.info(\n {\n hasAccessToken: !!tokens.access_token,\n hasRefreshToken: !!tokens.refresh_token,\n hasIdToken: !!tokens.id_token,\n expiresIn: tokens.expires_in,\n },\n 'Successfully exchanged code for tokens'\n );\n\n return tokens;\n } catch (error) {\n logger.error({ error }, 'Failed to exchange code for tokens');\n throw error;\n }\n }\n\n /**\n * Get user information from Vipps userinfo endpoint\n *\n * @param accessToken - Access token from token exchange\n * @returns Vipps user information\n */\n async getUserInfo(accessToken: string): Promise<VippsUserInfo> {\n logger.debug('Fetching user info from Vipps');\n\n try {\n const config = await this.getConfig();\n const userinfoUrl = config.serverMetadata().userinfo_endpoint;\n\n if (!userinfoUrl) {\n throw new Error('Userinfo endpoint not found in OpenID configuration');\n }\n\n const response = await fetch(userinfoUrl, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n ...(this.config.subscriptionKey && {\n 'Ocp-Apim-Subscription-Key': this.config.subscriptionKey,\n }),\n ...(this.config.merchantSerialNumber && {\n 'Merchant-Serial-Number': this.config.merchantSerialNumber,\n }),\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.error(\n {\n status: response.status,\n error: errorText,\n },\n 'Failed to fetch user info'\n );\n throw new Error(`Failed to fetch user info: ${response.status} - ${errorText}`);\n }\n\n const userInfo = (await response.json()) as VippsUserInfo;\n\n logger.info(\n {\n sub: userInfo.sub,\n hasEmail: !!userInfo.email,\n hasPhone: !!userInfo.phone_number,\n hasAddress: !!userInfo.addresses?.length,\n },\n 'Successfully fetched user info'\n );\n\n return userInfo;\n } catch (error) {\n logger.error({ error }, 'Error fetching user info');\n throw error;\n }\n }\n\n /**\n * Refresh access token using refresh token\n *\n * @param refreshToken - Refresh token from initial token exchange\n * @returns New token response\n */\n async refreshTokens(refreshToken: string): Promise<openid.TokenEndpointResponse> {\n logger.debug('Refreshing access token');\n\n try {\n const config = await this.getConfig();\n const tokens = await openid.refreshTokenGrant(config, refreshToken);\n\n logger.info(\n {\n hasAccessToken: !!tokens.access_token,\n hasRefreshToken: !!tokens.refresh_token,\n expiresIn: tokens.expires_in,\n },\n 'Successfully refreshed tokens'\n );\n\n return tokens;\n } catch (error) {\n logger.error({ error }, 'Failed to refresh tokens');\n throw error;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/providers/vipps/types.ts","../../../src/providers/vipps/client.ts"],"sourcesContent":["/**\n * Vipps Login Provider Types\n *\n * Type definitions for Vipps Login (OIDC) integration.\n *\n * @see https://developer.vippsmobilepay.com/docs/APIs/login-api/\n */\n\n/**\n * Configuration for Vipps Login client\n */\nexport interface VippsLoginConfig {\n /** Vipps OIDC issuer URL (e.g., 'https://apitest.vipps.no/access-management-1.0/access') */\n issuer: string;\n /** OAuth Client ID from Vipps */\n clientId: string;\n /** OAuth Client Secret from Vipps */\n clientSecret: string;\n /** OAuth redirect URI (callback URL) */\n redirectUri: string;\n /** OpenID Connect scopes (default: 'openid name phoneNumber address email') */\n scope?: string;\n /** Vipps subscription key (Ocp-Apim-Subscription-Key) - required for userinfo endpoint */\n subscriptionKey?: string;\n /** Merchant serial number - required for userinfo endpoint */\n merchantSerialNumber?: string;\n}\n\n/**\n * Vipps address from userinfo endpoint\n */\nexport interface VippsAddress {\n /** Street address */\n street_address?: string;\n /** Postal code */\n postal_code?: string;\n /** Region/city */\n region?: string;\n /** Country code (e.g., 'NO') */\n country?: string;\n /** Formatted address string */\n formatted?: string;\n /** Address type (e.g., 'home', 'work', 'other') */\n address_type?: string;\n}\n\n/**\n * User information returned from Vipps Login userinfo endpoint\n *\n * @see https://developer.vippsmobilepay.com/docs/APIs/login-api/login-api-guide/#userinfo-endpoint\n */\nexport interface VippsUserInfo {\n /** Subject - unique Vipps user identifier (UUID) */\n sub: string;\n /** Email address */\n email?: string;\n /** Email verification status */\n email_verified?: boolean;\n /** Given name (first name) */\n given_name?: string;\n /** Family name (last name) */\n family_name?: string;\n /** Full name */\n name?: string;\n /** Phone number (with country code, e.g., '4712345678') */\n phone_number?: string;\n /** Phone number verification status */\n phone_number_verified?: boolean;\n /** Birth date (ISO 8601 format, e.g., '1990-01-15') */\n birthdate?: string;\n /** Physical addresses (array) */\n addresses?: VippsAddress[];\n /** Other claims (deprecated) */\n other_addresses?: VippsAddress[];\n}\n\n/**\n * Available Vipps Login scopes\n */\nexport const VippsLoginScopes = {\n /** Required: OpenID Connect base scope */\n OpenId: 'openid',\n /** User's name (given_name, family_name, name) */\n Name: 'name',\n /** Phone number */\n PhoneNumber: 'phoneNumber',\n /** Physical address */\n Address: 'address',\n /** Birth date */\n BirthDate: 'birthDate',\n /** Email address */\n Email: 'email',\n} as const;\n\n/**\n * Default scopes for Vipps Login\n */\nexport const defaultVippsLoginScope = [\n VippsLoginScopes.OpenId,\n VippsLoginScopes.Name,\n VippsLoginScopes.PhoneNumber,\n VippsLoginScopes.Address,\n VippsLoginScopes.Email,\n].join(' ');\n\n/**\n * Vipps API environments\n */\nexport const VippsEnvironments = {\n /** Test environment */\n Test: 'https://apitest.vipps.no',\n /** Production environment */\n Production: 'https://api.vipps.no',\n} as const;\n\n/**\n * Get Vipps OIDC issuer URL for environment\n */\nexport function getVippsIssuer(environment: string): string {\n // Note: Vipps metadata returns issuer with trailing slash\n return `${environment}/access-management-1.0/access/`;\n}\n","/**\n * Vipps Login Client\n *\n * Client for Vipps Login (OIDC) authentication flow.\n * Implements OAuth/OIDC operations with PKCE for Vipps.\n */\n\nimport * as openid from 'openid-client';\n\nimport { createLogger } from '../../logger';\nimport {\n buildAuthorizationUrl,\n buildPKCEOptions,\n type OAuthConfig,\n type PKCEOptions,\n} from '../../oauth';\nimport { discoverConfiguration } from '../../oidc-discovery';\nimport {\n defaultVippsLoginScope,\n type VippsLoginConfig,\n type VippsUserInfo,\n} from './types';\n\nconst logger = createLogger({\n namespace: 'fides-auth:vipps',\n context: { module: 'VippsLoginClient' },\n});\n\n/**\n * Vipps Login Client\n *\n * Handles Vipps Login (OIDC) authentication flow using PKCE.\n */\nexport class VippsLoginClient {\n private readonly config: VippsLoginConfig;\n private readonly oauthConfig: OAuthConfig;\n\n constructor(config: VippsLoginConfig) {\n this.config = {\n ...config,\n scope: config.scope ?? defaultVippsLoginScope,\n };\n\n this.oauthConfig = {\n issuer: config.issuer,\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n redirect_uri: config.redirectUri,\n scope: this.config.scope!,\n };\n\n logger.info(\n {\n issuer: config.issuer,\n redirectUri: config.redirectUri,\n scope: this.config.scope,\n },\n 'VippsLoginClient initialized'\n );\n }\n\n /**\n * Get the OpenID Configuration, shared with the rest of fides-auth.\n *\n * Previously memoized on the instance, which cached rejections too: one failed\n * discovery — a restart during a provider blip — left every later call\n * rejecting for the lifetime of the client. {@link discoverConfiguration}\n * caches successes with a TTL and drops failures.\n */\n private async getConfig(): Promise<openid.Configuration> {\n return discoverConfiguration(this.oauthConfig);\n }\n\n /**\n * Build PKCE options for authorization flow\n *\n * @returns PKCE options including code_verifier, code_challenge, and state\n */\n async buildPKCEOptions(): Promise<PKCEOptions> {\n logger.debug('Building PKCE options');\n return buildPKCEOptions(this.oauthConfig);\n }\n\n /**\n * Build authorization URL for Vipps Login\n *\n * @param pkceOptions - PKCE options from buildPKCEOptions()\n * @returns Authorization URL to redirect user to\n */\n async buildAuthorizationUrl(pkceOptions: PKCEOptions): Promise<URL> {\n logger.debug('Building authorization URL');\n const config = await this.getConfig();\n return buildAuthorizationUrl(config, pkceOptions);\n }\n\n /**\n * Exchange authorization code for tokens\n *\n * @param callbackUrl - The full callback URL with code parameter from Vipps\n * @param codeVerifier - PKCE code verifier from initial request\n * @param expectedState - Expected state value for validation (optional)\n * @returns Token response with access_token, id_token, and refresh_token\n */\n async exchangeCodeForTokens(\n callbackUrl: string | URL,\n codeVerifier: string,\n expectedState?: string\n ): Promise<openid.TokenEndpointResponse> {\n logger.debug('Exchanging authorization code for tokens');\n\n try {\n const config = await this.getConfig();\n\n // Convert to URL if string\n const currentUrl = typeof callbackUrl === 'string' ? new URL(callbackUrl) : callbackUrl;\n\n const tokens = await openid.authorizationCodeGrant(config, currentUrl, {\n pkceCodeVerifier: codeVerifier,\n expectedState, // Pass the expected state for validation\n idTokenExpected: true,\n });\n\n logger.info(\n {\n hasAccessToken: !!tokens.access_token,\n hasRefreshToken: !!tokens.refresh_token,\n hasIdToken: !!tokens.id_token,\n expiresIn: tokens.expires_in,\n },\n 'Successfully exchanged code for tokens'\n );\n\n return tokens;\n } catch (error) {\n logger.error({ error }, 'Failed to exchange code for tokens');\n throw error;\n }\n }\n\n /**\n * Get user information from Vipps userinfo endpoint\n *\n * @param accessToken - Access token from token exchange\n * @returns Vipps user information\n */\n async getUserInfo(accessToken: string): Promise<VippsUserInfo> {\n logger.debug('Fetching user info from Vipps');\n\n try {\n const config = await this.getConfig();\n const userinfoUrl = config.serverMetadata().userinfo_endpoint;\n\n if (!userinfoUrl) {\n throw new Error('Userinfo endpoint not found in OpenID configuration');\n }\n\n const response = await fetch(userinfoUrl, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n ...(this.config.subscriptionKey && {\n 'Ocp-Apim-Subscription-Key': this.config.subscriptionKey,\n }),\n ...(this.config.merchantSerialNumber && {\n 'Merchant-Serial-Number': this.config.merchantSerialNumber,\n }),\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.error(\n {\n status: response.status,\n error: errorText,\n },\n 'Failed to fetch user info'\n );\n throw new Error(`Failed to fetch user info: ${response.status} - ${errorText}`);\n }\n\n const userInfo = (await response.json()) as VippsUserInfo;\n\n logger.info(\n {\n sub: userInfo.sub,\n hasEmail: !!userInfo.email,\n hasPhone: !!userInfo.phone_number,\n hasAddress: !!userInfo.addresses?.length,\n },\n 'Successfully fetched user info'\n );\n\n return userInfo;\n } catch (error) {\n logger.error({ error }, 'Error fetching user info');\n throw error;\n }\n }\n\n /**\n * Refresh access token using refresh token\n *\n * @param refreshToken - Refresh token from initial token exchange\n * @returns New token response\n */\n async refreshTokens(refreshToken: string): Promise<openid.TokenEndpointResponse> {\n logger.debug('Refreshing access token');\n\n try {\n const config = await this.getConfig();\n const tokens = await openid.refreshTokenGrant(config, refreshToken);\n\n logger.info(\n {\n hasAccessToken: !!tokens.access_token,\n hasRefreshToken: !!tokens.refresh_token,\n expiresIn: tokens.expires_in,\n },\n 'Successfully refreshed tokens'\n );\n\n return tokens;\n } catch (error) {\n logger.error({ error }, 'Failed to refresh tokens');\n throw error;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -13,9 +13,16 @@ export interface HeartbeatHandlerConfig {
|
|
|
13
13
|
/**
|
|
14
14
|
* Handles a heartbeat request — refresh the active session if there is one.
|
|
15
15
|
*
|
|
16
|
-
* Returns 200 with `{ accessTokenExpiresAt }` on success, 401
|
|
17
|
-
*
|
|
18
|
-
* rate-limited.
|
|
16
|
+
* Returns 200 with `{ accessTokenExpiresAt }` on success, 401 with `{ reason }`
|
|
17
|
+
* when the session is genuinely over, 503 when the provider could not be
|
|
18
|
+
* reached, 405 for non-POST methods, and 429 when rate-limited.
|
|
19
|
+
*
|
|
20
|
+
* The 401/503 split matters: a 401 tells the client to log the user out, so it
|
|
21
|
+
* must be reserved for the cases where the session really is finished. A network
|
|
22
|
+
* blip or a provider 5xx says nothing about the session's validity — answering
|
|
23
|
+
* 401 there logs out users whose sessions were fine, which is precisely the
|
|
24
|
+
* "logged out every few minutes" symptom. The client already retries non-401
|
|
25
|
+
* failures with backoff.
|
|
19
26
|
*/
|
|
20
27
|
export declare function handleHeartbeat(request: Request, config: HeartbeatHandlerConfig): Promise<Response>;
|
|
21
28
|
//# sourceMappingURL=heartbeat-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-handler.d.ts","sourceRoot":"","sources":["../../src/server/heartbeat-handler.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"heartbeat-handler.d.ts","sourceRoot":"","sources":["../../src/server/heartbeat-handler.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAKlD,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,WAAW,EAAE,WAAW,CAAC;IAEzB,wCAAwC;IACxC,OAAO,EAAE,WAAW,CAAC;IAErB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7C,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAkDnB"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { CookieStore } from './cookie-store';
|
|
2
|
-
export { persistSession, readSession, refreshSessionInStore, readIdToken, clearSession, } from './session';
|
|
2
|
+
export { persistSession, readSession, tryReadSession, refreshSessionInStore, tryRefreshSessionInStore, readIdToken, clearSession, type ClearSessionOptions, type PersistSessionOptions, type ReadSessionResult, type RefreshSessionResult, } from './session';
|
|
3
3
|
export { handleOidcLogin, type OidcLoginConfig } from './oidc-login';
|
|
4
4
|
export { handleOidcCallback, type OidcCallbackConfig } from './oidc-callback';
|
|
5
5
|
export { handleOidcLogout, type OidcLogoutConfig } from './oidc-logout';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EACL,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,WAAW,EACX,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EACL,cAAc,EACd,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,wBAAwB,EACxB,WAAW,EACX,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC"}
|