@flowselections/core 1.0.12 → 1.0.13
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class CentralAuthConfigError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class ProviderNotConnectedError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class ProviderUpstreamError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class CentralAuthUnavailableError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function getProviderToken(provider: string, opts?: {
|
|
14
|
+
environment?: "staging" | "live";
|
|
15
|
+
}): Promise<{
|
|
16
|
+
accessToken: string;
|
|
17
|
+
expiresAt: Date;
|
|
18
|
+
}>;
|
|
19
|
+
//# sourceMappingURL=provider-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-token.d.ts","sourceRoot":"","sources":["../../src/core/server/provider-token.ts"],"names":[],"mappings":"AAAA,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAwGD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,CAAC,CAuBnD"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export class CentralAuthConfigError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "CentralAuthConfigError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class ProviderNotConnectedError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "ProviderNotConnectedError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class ProviderUpstreamError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "ProviderUpstreamError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class CentralAuthUnavailableError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "CentralAuthUnavailableError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const TIMEOUT_MS = 10000;
|
|
26
|
+
const EXPIRY_BUFFER_MS = 60000;
|
|
27
|
+
const cache = new Map();
|
|
28
|
+
function isFresh(token) {
|
|
29
|
+
return token.expiresAt.getTime() - EXPIRY_BUFFER_MS > Date.now();
|
|
30
|
+
}
|
|
31
|
+
function logAuthFailure(provider, environment, status) {
|
|
32
|
+
console.error("[central-auth] provider token request failed", { provider, environment, status });
|
|
33
|
+
}
|
|
34
|
+
async function fetchProviderToken(provider, environment) {
|
|
35
|
+
const baseUrl = process.env.CENTRAL_AUTH_URL;
|
|
36
|
+
const secret = process.env.CENTRAL_AUTH_SECRET;
|
|
37
|
+
if (!baseUrl || !secret) {
|
|
38
|
+
throw new CentralAuthConfigError("Missing CENTRAL_AUTH_URL or CENTRAL_AUTH_SECRET environment variable.");
|
|
39
|
+
}
|
|
40
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/api/token/${encodeURIComponent(provider)}`;
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
let timedOut = false;
|
|
43
|
+
const timeout = setTimeout(() => {
|
|
44
|
+
timedOut = true;
|
|
45
|
+
controller.abort();
|
|
46
|
+
}, TIMEOUT_MS);
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await fetch(url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${secret}`,
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({ environment }),
|
|
56
|
+
signal: controller.signal,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
logAuthFailure(provider, environment, timedOut ? "timeout" : "network");
|
|
61
|
+
throw new CentralAuthUnavailableError(`Central auth request failed (provider=${provider}, environment=${environment}, reason=${timedOut ? "timeout" : "network"}).`);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
}
|
|
66
|
+
if (response.ok) {
|
|
67
|
+
let body;
|
|
68
|
+
try {
|
|
69
|
+
body = (await response.json());
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
logAuthFailure(provider, environment, response.status);
|
|
73
|
+
throw new CentralAuthUnavailableError(`Central auth returned an unparseable response (provider=${provider}, environment=${environment}, status=${response.status}).`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
accessToken: body.access_token,
|
|
77
|
+
expiresAt: new Date(body.expires_at),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
logAuthFailure(provider, environment, response.status);
|
|
81
|
+
switch (response.status) {
|
|
82
|
+
case 401:
|
|
83
|
+
throw new CentralAuthConfigError(`Central auth rejected credentials (provider=${provider}, environment=${environment}, status=401).`);
|
|
84
|
+
case 409:
|
|
85
|
+
throw new ProviderNotConnectedError(`Provider not connected (provider=${provider}, environment=${environment}, status=409).`);
|
|
86
|
+
case 400:
|
|
87
|
+
throw new CentralAuthConfigError(`Invalid request to central auth (provider=${provider}, environment=${environment}, status=400).`);
|
|
88
|
+
case 502:
|
|
89
|
+
throw new ProviderUpstreamError(`Upstream error from central auth (provider=${provider}, environment=${environment}, status=502).`);
|
|
90
|
+
default:
|
|
91
|
+
throw new CentralAuthUnavailableError(`Central auth unavailable (provider=${provider}, environment=${environment}, status=${response.status}).`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export async function getProviderToken(provider, opts) {
|
|
95
|
+
const environment = opts?.environment ?? "live";
|
|
96
|
+
const key = `${provider}:${environment}`;
|
|
97
|
+
const cached = cache.get(key);
|
|
98
|
+
if (cached) {
|
|
99
|
+
const token = await cached;
|
|
100
|
+
if (isFresh(token))
|
|
101
|
+
return token;
|
|
102
|
+
// Only the caller that observes the still-current stale entry evicts it —
|
|
103
|
+
// a concurrent caller may have already replaced it with a fresh in-flight fetch.
|
|
104
|
+
if (cache.get(key) === cached)
|
|
105
|
+
cache.delete(key);
|
|
106
|
+
return getProviderToken(provider, opts);
|
|
107
|
+
}
|
|
108
|
+
// No `await` above this line: a synchronous burst of callers must all see
|
|
109
|
+
// this Map entry before any of them proceeds to fetch, so they share one fetch.
|
|
110
|
+
const promise = fetchProviderToken(provider, environment);
|
|
111
|
+
cache.set(key, promise);
|
|
112
|
+
promise.catch(() => {
|
|
113
|
+
if (cache.get(key) === promise)
|
|
114
|
+
cache.delete(key);
|
|
115
|
+
});
|
|
116
|
+
return promise;
|
|
117
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowselections/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server/index.d.ts",
|
|
16
|
+
"default": "./dist/server/index.js"
|
|
13
17
|
}
|
|
14
18
|
},
|
|
15
19
|
"files": ["dist"],
|