@platform-x/shp-auth-client 0.0.1
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/README.md +304 -0
- package/dist/cache.d.ts +10 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +28 -0
- package/dist/cache.js.map +1 -0
- package/dist/config-provider.d.ts +17 -0
- package/dist/config-provider.d.ts.map +1 -0
- package/dist/config-provider.js +54 -0
- package/dist/config-provider.js.map +1 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +117 -0
- package/dist/errors.js.map +1 -0
- package/dist/extract-site-name.d.ts +15 -0
- package/dist/extract-site-name.d.ts.map +1 -0
- package/dist/extract-site-name.js +28 -0
- package/dist/extract-site-name.js.map +1 -0
- package/dist/graphql-client.d.ts +14 -0
- package/dist/graphql-client.d.ts.map +1 -0
- package/dist/graphql-client.js +53 -0
- package/dist/graphql-client.js.map +1 -0
- package/dist/hep-secret-provider.d.ts +18 -0
- package/dist/hep-secret-provider.d.ts.map +1 -0
- package/dist/hep-secret-provider.js +31 -0
- package/dist/hep-secret-provider.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/keycloak-auth.d.ts +50 -0
- package/dist/keycloak-auth.d.ts.map +1 -0
- package/dist/keycloak-auth.js +92 -0
- package/dist/keycloak-auth.js.map +1 -0
- package/dist/keycloak-secrets-provisioner.d.ts +44 -0
- package/dist/keycloak-secrets-provisioner.d.ts.map +1 -0
- package/dist/keycloak-secrets-provisioner.js +94 -0
- package/dist/keycloak-secrets-provisioner.js.map +1 -0
- package/dist/resilience.d.ts +26 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +84 -0
- package/dist/resilience.js.map +1 -0
- package/dist/tenant-config.d.ts +23 -0
- package/dist/tenant-config.d.ts.map +1 -0
- package/dist/tenant-config.js +136 -0
- package/dist/tenant-config.js.map +1 -0
- package/dist/tls-trust.d.ts +2 -0
- package/dist/tls-trust.d.ts.map +1 -0
- package/dist/tls-trust.js +27 -0
- package/dist/tls-trust.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-site-name.js","sourceRoot":"","sources":["../src/extract-site-name.ts"],"names":[],"mappings":";;AAQA,0CAUC;AAOD,sCAEC;AAzBD;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,GAAY;IAC1C,MAAM,UAAU,GACb,GAAG,CAAC,OAAO,CAAC,UAAU,CAAY;QAClC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAY;QACnC,GAAG,CAAC,QAAQ,CAAC;IAEf,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,6BAA6B;IAC7B,OAAO,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAAC,QAAgB,EAAE,QAAgB,EAAE,GAAW;IAC3E,OAAO,GAAG,QAAQ,IAAI,QAAQ,IAAI,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import './tls-trust';
|
|
2
|
+
import { GraphQLClient, gql, ClientError } from 'graphql-request';
|
|
3
|
+
type ClientType = 'admin' | 'storefront';
|
|
4
|
+
/**
|
|
5
|
+
* Returns a cached GraphQL client for the given tenant and API type.
|
|
6
|
+
* admin → Shopify Admin API (uses adminApiAccessToken)
|
|
7
|
+
* storefront → Shopify Storefront API (uses storefrontPrivateToken)
|
|
8
|
+
*/
|
|
9
|
+
export declare function getGraphQLClient(siteName: string, type?: ClientType): Promise<GraphQLClient>;
|
|
10
|
+
/** Clears cached GraphQL clients (call after tenant config changes). */
|
|
11
|
+
export declare function clearGraphQLClientCache(siteName?: string): void;
|
|
12
|
+
/** Re-exports for convenience */
|
|
13
|
+
export { gql, ClientError };
|
|
14
|
+
//# sourceMappingURL=graphql-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql-client.d.ts","sourceRoot":"","sources":["../src/graphql-client.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAIlE,KAAK,UAAU,GAAG,OAAO,GAAG,YAAY,CAAC;AAQzC;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,UAAoB,GACzB,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAO/D;AAED,iCAAiC;AACjC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClientError = exports.gql = void 0;
|
|
4
|
+
exports.getGraphQLClient = getGraphQLClient;
|
|
5
|
+
exports.clearGraphQLClientCache = clearGraphQLClientCache;
|
|
6
|
+
require("./tls-trust");
|
|
7
|
+
const graphql_request_1 = require("graphql-request");
|
|
8
|
+
Object.defineProperty(exports, "gql", { enumerable: true, get: function () { return graphql_request_1.gql; } });
|
|
9
|
+
Object.defineProperty(exports, "ClientError", { enumerable: true, get: function () { return graphql_request_1.ClientError; } });
|
|
10
|
+
const tenant_config_1 = require("./tenant-config");
|
|
11
|
+
const clientCache = new Map();
|
|
12
|
+
function clientKey(siteName, type) {
|
|
13
|
+
return `${siteName}:${type}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns a cached GraphQL client for the given tenant and API type.
|
|
17
|
+
* admin → Shopify Admin API (uses adminApiAccessToken)
|
|
18
|
+
* storefront → Shopify Storefront API (uses storefrontPrivateToken)
|
|
19
|
+
*/
|
|
20
|
+
async function getGraphQLClient(siteName, type = 'admin') {
|
|
21
|
+
const key = clientKey(siteName, type);
|
|
22
|
+
if (clientCache.has(key))
|
|
23
|
+
return clientCache.get(key);
|
|
24
|
+
const config = await (0, tenant_config_1.getTenantConfig)(siteName);
|
|
25
|
+
const { storeDomain, storefrontApiVersion, adminApiAccessToken, storefrontPrivateToken } = config.shopify;
|
|
26
|
+
let url;
|
|
27
|
+
let headers;
|
|
28
|
+
if (type === 'admin') {
|
|
29
|
+
url = `https://${storeDomain}/admin/api/${storefrontApiVersion}/graphql.json`;
|
|
30
|
+
headers = { 'X-Shopify-Access-Token': adminApiAccessToken };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
url = `https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`;
|
|
34
|
+
// KIWI_SHOPIFY_STOREFRONT_PRIVATE_TOKEN is a private (server-side) Storefront
|
|
35
|
+
// token, which Shopify authenticates via this header — NOT the public-token
|
|
36
|
+
// header (`X-Shopify-Storefront-Access-Token`), which returns 401 for it.
|
|
37
|
+
headers = { 'Shopify-Storefront-Private-Token': storefrontPrivateToken };
|
|
38
|
+
}
|
|
39
|
+
const client = new graphql_request_1.GraphQLClient(url, { headers });
|
|
40
|
+
clientCache.set(key, client);
|
|
41
|
+
return client;
|
|
42
|
+
}
|
|
43
|
+
/** Clears cached GraphQL clients (call after tenant config changes). */
|
|
44
|
+
function clearGraphQLClientCache(siteName) {
|
|
45
|
+
if (siteName) {
|
|
46
|
+
clientCache.delete(clientKey(siteName, 'admin'));
|
|
47
|
+
clientCache.delete(clientKey(siteName, 'storefront'));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
clientCache.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=graphql-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql-client.js","sourceRoot":"","sources":["../src/graphql-client.ts"],"names":[],"mappings":";;;AAkBA,4CA4BC;AAGD,0DAOC;AAxDD,uBAAqB;AACrB,qDAAkE;AA0DzD,oFA1De,qBAAG,OA0Df;AAAE,4FA1De,6BAAW,OA0Df;AAxDzB,mDAAkD;AAIlD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAyB,CAAC;AAErD,SAAS,SAAS,CAAC,QAAgB,EAAE,IAAgB;IACnD,OAAO,GAAG,QAAQ,IAAI,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,OAAmB,OAAO;IAE1B,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAEvD,MAAM,MAAM,GAAG,MAAM,IAAA,+BAAe,EAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,GACtF,MAAM,CAAC,OAAO,CAAC;IAEjB,IAAI,GAAW,CAAC;IAChB,IAAI,OAA+B,CAAC;IAEpC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,GAAG,GAAG,WAAW,WAAW,cAAc,oBAAoB,eAAe,CAAC;QAC9E,OAAO,GAAG,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,CAAC;IAC9D,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,WAAW,WAAW,QAAQ,oBAAoB,eAAe,CAAC;QACxE,8EAA8E;QAC9E,4EAA4E;QAC5E,0EAA0E;QAC1E,OAAO,GAAG,EAAE,kCAAkC,EAAE,sBAAsB,EAAE,CAAC;IAC3E,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,+BAAa,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACnD,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wEAAwE;AACxE,SAAgB,uBAAuB,CAAC,QAAiB;IACvD,IAAI,QAAQ,EAAE,CAAC;QACb,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,WAAW,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ConfigProvider } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps hep-secret-access's secretManager() behind our ConfigProvider
|
|
4
|
+
* interface (see hep-delivery-service's secret_key.services.ts for the
|
|
5
|
+
* reference usage). secretManager() itself picks env | gsm | csi based on
|
|
6
|
+
* process.env.SECRET_MODE at construction time — constructed once here and
|
|
7
|
+
* reused, so its underlying client (e.g. GCP Secret Manager) isn't rebuilt
|
|
8
|
+
* on every read.
|
|
9
|
+
*
|
|
10
|
+
* hep-secret-access's own EnvProvider returns '' on a missing key rather than
|
|
11
|
+
* throwing; we throw instead, so a misconfigured secret fails loudly here
|
|
12
|
+
* too, regardless of which mode is active.
|
|
13
|
+
*/
|
|
14
|
+
export declare class HepSecretProvider implements ConfigProvider {
|
|
15
|
+
private readonly provider;
|
|
16
|
+
get(secretId: string): Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=hep-secret-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hep-secret-provider.d.ts","sourceRoot":"","sources":["../src/hep-secret-provider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAEtC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAO7C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HepSecretProvider = void 0;
|
|
4
|
+
const hep_secret_access_1 = require("hep-secret-access");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
/**
|
|
7
|
+
* Wraps hep-secret-access's secretManager() behind our ConfigProvider
|
|
8
|
+
* interface (see hep-delivery-service's secret_key.services.ts for the
|
|
9
|
+
* reference usage). secretManager() itself picks env | gsm | csi based on
|
|
10
|
+
* process.env.SECRET_MODE at construction time — constructed once here and
|
|
11
|
+
* reused, so its underlying client (e.g. GCP Secret Manager) isn't rebuilt
|
|
12
|
+
* on every read.
|
|
13
|
+
*
|
|
14
|
+
* hep-secret-access's own EnvProvider returns '' on a missing key rather than
|
|
15
|
+
* throwing; we throw instead, so a misconfigured secret fails loudly here
|
|
16
|
+
* too, regardless of which mode is active.
|
|
17
|
+
*/
|
|
18
|
+
class HepSecretProvider {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.provider = (0, hep_secret_access_1.secretManager)();
|
|
21
|
+
}
|
|
22
|
+
async get(secretId) {
|
|
23
|
+
const value = await this.provider.read(secretId);
|
|
24
|
+
if (!value) {
|
|
25
|
+
throw new errors_1.SecretNotFoundError(secretId);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.HepSecretProvider = HepSecretProvider;
|
|
31
|
+
//# sourceMappingURL=hep-secret-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hep-secret-provider.js","sourceRoot":"","sources":["../src/hep-secret-provider.ts"],"names":[],"mappings":";;;AAAA,yDAAkD;AAElD,qCAA+C;AAG/C;;;;;;;;;;;GAWG;AACH,MAAa,iBAAiB;IAA9B;QACmB,aAAQ,GAAG,IAAA,iCAAa,GAAE,CAAC;IAS9C,CAAC;IAPC,KAAK,CAAC,GAAG,CAAC,QAAgB;QACxB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,4BAAmB,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAVD,8CAUC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { getTenantConfig, clearTenantCache, setConfigProvider, getClientTenantDetails } from './tenant-config';
|
|
2
|
+
export { extractSiteName, buildSecretId } from './extract-site-name';
|
|
3
|
+
export { generateKeycloakSecrets } from './keycloak-secrets-provisioner';
|
|
4
|
+
export type { KeycloakSecretsInput, KeycloakSecretsResult } from './keycloak-secrets-provisioner';
|
|
5
|
+
export { createConfigProvider } from './config-provider';
|
|
6
|
+
export type { ProviderMode } from './config-provider';
|
|
7
|
+
export { HepSecretProvider } from './hep-secret-provider';
|
|
8
|
+
export { TTLCache } from './cache';
|
|
9
|
+
export { getGraphQLClient, clearGraphQLClientCache, gql, ClientError } from './graphql-client';
|
|
10
|
+
export { withRetry, withTimeout, CircuitBreaker } from './resilience';
|
|
11
|
+
export { TenantNotFoundError, SecretNotFoundError, TokenExpiredError, TokenInvalidError, UnauthorizedError, CrossTenantAccessError, ForbiddenError, ShopifyApiError, ShopifyUserError, TimeoutError, CircuitOpenError, globalErrorHandler, } from './errors';
|
|
12
|
+
export type { TenantConfig, ShopifyConfig, KeycloakConfig, ConfigProvider, TenantAuthInfo } from './types';
|
|
13
|
+
export { extractBearer, verifyKeycloakToken, keycloakConfigForTenant, requireKeycloakAuth, } from './keycloak-auth';
|
|
14
|
+
export type { KeycloakTokenPayload, KeycloakVerifyConfig } from './keycloak-auth';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC/G,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EACL,mBAAmB,EAAE,mBAAmB,EACxC,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EACvD,sBAAsB,EAAE,cAAc,EACtC,eAAe,EAAE,gBAAgB,EACjC,YAAY,EAAE,gBAAgB,EAC9B,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC3G,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireKeycloakAuth = exports.keycloakConfigForTenant = exports.verifyKeycloakToken = exports.extractBearer = exports.globalErrorHandler = exports.CircuitOpenError = exports.TimeoutError = exports.ShopifyUserError = exports.ShopifyApiError = exports.ForbiddenError = exports.CrossTenantAccessError = exports.UnauthorizedError = exports.TokenInvalidError = exports.TokenExpiredError = exports.SecretNotFoundError = exports.TenantNotFoundError = exports.CircuitBreaker = exports.withTimeout = exports.withRetry = exports.ClientError = exports.gql = exports.clearGraphQLClientCache = exports.getGraphQLClient = exports.TTLCache = exports.HepSecretProvider = exports.createConfigProvider = exports.generateKeycloakSecrets = exports.buildSecretId = exports.extractSiteName = exports.getClientTenantDetails = exports.setConfigProvider = exports.clearTenantCache = exports.getTenantConfig = void 0;
|
|
4
|
+
var tenant_config_1 = require("./tenant-config");
|
|
5
|
+
Object.defineProperty(exports, "getTenantConfig", { enumerable: true, get: function () { return tenant_config_1.getTenantConfig; } });
|
|
6
|
+
Object.defineProperty(exports, "clearTenantCache", { enumerable: true, get: function () { return tenant_config_1.clearTenantCache; } });
|
|
7
|
+
Object.defineProperty(exports, "setConfigProvider", { enumerable: true, get: function () { return tenant_config_1.setConfigProvider; } });
|
|
8
|
+
Object.defineProperty(exports, "getClientTenantDetails", { enumerable: true, get: function () { return tenant_config_1.getClientTenantDetails; } });
|
|
9
|
+
var extract_site_name_1 = require("./extract-site-name");
|
|
10
|
+
Object.defineProperty(exports, "extractSiteName", { enumerable: true, get: function () { return extract_site_name_1.extractSiteName; } });
|
|
11
|
+
Object.defineProperty(exports, "buildSecretId", { enumerable: true, get: function () { return extract_site_name_1.buildSecretId; } });
|
|
12
|
+
var keycloak_secrets_provisioner_1 = require("./keycloak-secrets-provisioner");
|
|
13
|
+
Object.defineProperty(exports, "generateKeycloakSecrets", { enumerable: true, get: function () { return keycloak_secrets_provisioner_1.generateKeycloakSecrets; } });
|
|
14
|
+
var config_provider_1 = require("./config-provider");
|
|
15
|
+
Object.defineProperty(exports, "createConfigProvider", { enumerable: true, get: function () { return config_provider_1.createConfigProvider; } });
|
|
16
|
+
var hep_secret_provider_1 = require("./hep-secret-provider");
|
|
17
|
+
Object.defineProperty(exports, "HepSecretProvider", { enumerable: true, get: function () { return hep_secret_provider_1.HepSecretProvider; } });
|
|
18
|
+
var cache_1 = require("./cache");
|
|
19
|
+
Object.defineProperty(exports, "TTLCache", { enumerable: true, get: function () { return cache_1.TTLCache; } });
|
|
20
|
+
var graphql_client_1 = require("./graphql-client");
|
|
21
|
+
Object.defineProperty(exports, "getGraphQLClient", { enumerable: true, get: function () { return graphql_client_1.getGraphQLClient; } });
|
|
22
|
+
Object.defineProperty(exports, "clearGraphQLClientCache", { enumerable: true, get: function () { return graphql_client_1.clearGraphQLClientCache; } });
|
|
23
|
+
Object.defineProperty(exports, "gql", { enumerable: true, get: function () { return graphql_client_1.gql; } });
|
|
24
|
+
Object.defineProperty(exports, "ClientError", { enumerable: true, get: function () { return graphql_client_1.ClientError; } });
|
|
25
|
+
var resilience_1 = require("./resilience");
|
|
26
|
+
Object.defineProperty(exports, "withRetry", { enumerable: true, get: function () { return resilience_1.withRetry; } });
|
|
27
|
+
Object.defineProperty(exports, "withTimeout", { enumerable: true, get: function () { return resilience_1.withTimeout; } });
|
|
28
|
+
Object.defineProperty(exports, "CircuitBreaker", { enumerable: true, get: function () { return resilience_1.CircuitBreaker; } });
|
|
29
|
+
var errors_1 = require("./errors");
|
|
30
|
+
Object.defineProperty(exports, "TenantNotFoundError", { enumerable: true, get: function () { return errors_1.TenantNotFoundError; } });
|
|
31
|
+
Object.defineProperty(exports, "SecretNotFoundError", { enumerable: true, get: function () { return errors_1.SecretNotFoundError; } });
|
|
32
|
+
Object.defineProperty(exports, "TokenExpiredError", { enumerable: true, get: function () { return errors_1.TokenExpiredError; } });
|
|
33
|
+
Object.defineProperty(exports, "TokenInvalidError", { enumerable: true, get: function () { return errors_1.TokenInvalidError; } });
|
|
34
|
+
Object.defineProperty(exports, "UnauthorizedError", { enumerable: true, get: function () { return errors_1.UnauthorizedError; } });
|
|
35
|
+
Object.defineProperty(exports, "CrossTenantAccessError", { enumerable: true, get: function () { return errors_1.CrossTenantAccessError; } });
|
|
36
|
+
Object.defineProperty(exports, "ForbiddenError", { enumerable: true, get: function () { return errors_1.ForbiddenError; } });
|
|
37
|
+
Object.defineProperty(exports, "ShopifyApiError", { enumerable: true, get: function () { return errors_1.ShopifyApiError; } });
|
|
38
|
+
Object.defineProperty(exports, "ShopifyUserError", { enumerable: true, get: function () { return errors_1.ShopifyUserError; } });
|
|
39
|
+
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return errors_1.TimeoutError; } });
|
|
40
|
+
Object.defineProperty(exports, "CircuitOpenError", { enumerable: true, get: function () { return errors_1.CircuitOpenError; } });
|
|
41
|
+
Object.defineProperty(exports, "globalErrorHandler", { enumerable: true, get: function () { return errors_1.globalErrorHandler; } });
|
|
42
|
+
var keycloak_auth_1 = require("./keycloak-auth");
|
|
43
|
+
Object.defineProperty(exports, "extractBearer", { enumerable: true, get: function () { return keycloak_auth_1.extractBearer; } });
|
|
44
|
+
Object.defineProperty(exports, "verifyKeycloakToken", { enumerable: true, get: function () { return keycloak_auth_1.verifyKeycloakToken; } });
|
|
45
|
+
Object.defineProperty(exports, "keycloakConfigForTenant", { enumerable: true, get: function () { return keycloak_auth_1.keycloakConfigForTenant; } });
|
|
46
|
+
Object.defineProperty(exports, "requireKeycloakAuth", { enumerable: true, get: function () { return keycloak_auth_1.requireKeycloakAuth; } });
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAA+G;AAAtG,gHAAA,eAAe,OAAA;AAAE,iHAAA,gBAAgB,OAAA;AAAE,kHAAA,iBAAiB,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AACrF,yDAAqE;AAA5D,oHAAA,eAAe,OAAA;AAAE,kHAAA,aAAa,OAAA;AACvC,+EAAyE;AAAhE,uIAAA,uBAAuB,OAAA;AAEhC,qDAAyD;AAAhD,uHAAA,oBAAoB,OAAA;AAE7B,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AACjB,mDAA+F;AAAtF,kHAAA,gBAAgB,OAAA;AAAE,yHAAA,uBAAuB,OAAA;AAAE,qGAAA,GAAG,OAAA;AAAE,6GAAA,WAAW,OAAA;AACpE,2CAAsE;AAA7D,uGAAA,SAAS,OAAA;AAAE,yGAAA,WAAW,OAAA;AAAE,4GAAA,cAAc,OAAA;AAC/C,mCAOkB;AANhB,6GAAA,mBAAmB,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AACxC,2GAAA,iBAAiB,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AACvD,gHAAA,sBAAsB,OAAA;AAAE,wGAAA,cAAc,OAAA;AACtC,yGAAA,eAAe,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AACjC,sGAAA,YAAY,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAC9B,4GAAA,kBAAkB,OAAA;AAGpB,iDAKyB;AAJvB,8GAAA,aAAa,OAAA;AACb,oHAAA,mBAAmB,OAAA;AACnB,wHAAA,uBAAuB,OAAA;AACvB,oHAAA,mBAAmB,OAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
export interface KeycloakTokenPayload {
|
|
3
|
+
sub: string;
|
|
4
|
+
preferred_username?: string;
|
|
5
|
+
email?: string;
|
|
6
|
+
realm_access?: {
|
|
7
|
+
roles: string[];
|
|
8
|
+
};
|
|
9
|
+
[claim: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface KeycloakVerifyConfig {
|
|
12
|
+
issuer: string;
|
|
13
|
+
jwksUri: string;
|
|
14
|
+
/** Restrict verification to tokens whose `aud` matches. Omit to accept any audience. */
|
|
15
|
+
audience?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Extracts the bearer token from an `Authorization` header. Throws `UnauthorizedError` if missing/malformed. */
|
|
18
|
+
export declare function extractBearer(authorization?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Verifies a Keycloak-issued access token: RS256 signature (resolved against
|
|
21
|
+
* `config.jwksUri`), issuer, and — if given — audience. Throws
|
|
22
|
+
* `TokenExpiredError` / `TokenInvalidError` on failure.
|
|
23
|
+
*/
|
|
24
|
+
export declare function verifyKeycloakToken(token: string, config: KeycloakVerifyConfig): Promise<KeycloakTokenPayload>;
|
|
25
|
+
/**
|
|
26
|
+
* Builds a `KeycloakVerifyConfig` from a tenant's resolved config (see
|
|
27
|
+
* `getTenantConfig`). `audience` is passed through as-is — it isn't part of
|
|
28
|
+
* `KeycloakConfig` since it's a per-caller restriction, not a per-tenant secret.
|
|
29
|
+
*/
|
|
30
|
+
export declare function keycloakConfigForTenant(siteName: string, audience?: string): Promise<KeycloakVerifyConfig>;
|
|
31
|
+
/**
|
|
32
|
+
* Express middleware factory. Verifies the request's bearer token and
|
|
33
|
+
* attaches `{ sub, role }` to `req.user`, and the full decoded payload to
|
|
34
|
+
* `req.keycloakUser`. `config` may be a static `KeycloakVerifyConfig` (a
|
|
35
|
+
* service with its own fixed issuer/JWKS env vars), or a function — sync or
|
|
36
|
+
* async — to resolve one per request, e.g. `() => keycloakConfigForTenant('KIWI')`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function requireKeycloakAuth(config: KeycloakVerifyConfig | ((req: Request) => KeycloakVerifyConfig | Promise<KeycloakVerifyConfig>)): (req: Request, _res: Response, next: NextFunction) => void;
|
|
39
|
+
declare global {
|
|
40
|
+
namespace Express {
|
|
41
|
+
interface Request {
|
|
42
|
+
user?: {
|
|
43
|
+
sub: string;
|
|
44
|
+
role?: string;
|
|
45
|
+
};
|
|
46
|
+
keycloakUser?: KeycloakTokenPayload;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=keycloak-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak-auth.d.ts","sourceRoot":"","sources":["../src/keycloak-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAO/D,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACnC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA8B9G;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAGhH;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,oBAAoB,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC,IAE/F,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAYhE;AAED,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC;YACtC,YAAY,CAAC,EAAE,oBAAoB,CAAC;SACrC;KACF;CACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractBearer = extractBearer;
|
|
7
|
+
exports.verifyKeycloakToken = verifyKeycloakToken;
|
|
8
|
+
exports.keycloakConfigForTenant = keycloakConfigForTenant;
|
|
9
|
+
exports.requireKeycloakAuth = requireKeycloakAuth;
|
|
10
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
11
|
+
const jwks_rsa_1 = __importDefault(require("jwks-rsa"));
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
|
+
const tenant_config_1 = require("./tenant-config");
|
|
14
|
+
// One jwks-rsa client per distinct JWKS URI, reused across calls/requests —
|
|
15
|
+
// it has its own internal signing-key cache + rate limiter.
|
|
16
|
+
const jwksClients = new Map();
|
|
17
|
+
function getJwksClient(jwksUri) {
|
|
18
|
+
let client = jwksClients.get(jwksUri);
|
|
19
|
+
if (!client) {
|
|
20
|
+
client = (0, jwks_rsa_1.default)({ jwksUri, cache: true, rateLimit: true });
|
|
21
|
+
jwksClients.set(jwksUri, client);
|
|
22
|
+
}
|
|
23
|
+
return client;
|
|
24
|
+
}
|
|
25
|
+
/** Extracts the bearer token from an `Authorization` header. Throws `UnauthorizedError` if missing/malformed. */
|
|
26
|
+
function extractBearer(authorization) {
|
|
27
|
+
if (!authorization?.startsWith('Bearer ')) {
|
|
28
|
+
throw new errors_1.UnauthorizedError('Missing or malformed Authorization header');
|
|
29
|
+
}
|
|
30
|
+
return authorization.slice('Bearer '.length);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Verifies a Keycloak-issued access token: RS256 signature (resolved against
|
|
34
|
+
* `config.jwksUri`), issuer, and — if given — audience. Throws
|
|
35
|
+
* `TokenExpiredError` / `TokenInvalidError` on failure.
|
|
36
|
+
*/
|
|
37
|
+
function verifyKeycloakToken(token, config) {
|
|
38
|
+
const client = getJwksClient(config.jwksUri);
|
|
39
|
+
const getSigningKey = (header, callback) => {
|
|
40
|
+
client.getSigningKey(header.kid, (err, key) => {
|
|
41
|
+
if (err || !key) {
|
|
42
|
+
callback(err ?? new Error('Unable to resolve signing key'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
callback(null, key.getPublicKey());
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
jsonwebtoken_1.default.verify(token, getSigningKey, { algorithms: ['RS256'], issuer: config.issuer, audience: config.audience }, (err, decoded) => {
|
|
50
|
+
if (err) {
|
|
51
|
+
reject(err.name === 'TokenExpiredError' ? new errors_1.TokenExpiredError() : new errors_1.TokenInvalidError(err.message));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!decoded || typeof decoded !== 'object') {
|
|
55
|
+
reject(new errors_1.TokenInvalidError());
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
resolve(decoded);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds a `KeycloakVerifyConfig` from a tenant's resolved config (see
|
|
64
|
+
* `getTenantConfig`). `audience` is passed through as-is — it isn't part of
|
|
65
|
+
* `KeycloakConfig` since it's a per-caller restriction, not a per-tenant secret.
|
|
66
|
+
*/
|
|
67
|
+
async function keycloakConfigForTenant(siteName, audience) {
|
|
68
|
+
const { keycloak } = await (0, tenant_config_1.getTenantConfig)(siteName);
|
|
69
|
+
return { issuer: keycloak.issuer, jwksUri: keycloak.jwksUri, audience };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Express middleware factory. Verifies the request's bearer token and
|
|
73
|
+
* attaches `{ sub, role }` to `req.user`, and the full decoded payload to
|
|
74
|
+
* `req.keycloakUser`. `config` may be a static `KeycloakVerifyConfig` (a
|
|
75
|
+
* service with its own fixed issuer/JWKS env vars), or a function — sync or
|
|
76
|
+
* async — to resolve one per request, e.g. `() => keycloakConfigForTenant('KIWI')`.
|
|
77
|
+
*/
|
|
78
|
+
function requireKeycloakAuth(config) {
|
|
79
|
+
return (req, _res, next) => {
|
|
80
|
+
Promise.resolve()
|
|
81
|
+
.then(async () => {
|
|
82
|
+
const token = extractBearer(req.header('authorization'));
|
|
83
|
+
const resolvedConfig = typeof config === 'function' ? await config(req) : config;
|
|
84
|
+
const payload = await verifyKeycloakToken(token, resolvedConfig);
|
|
85
|
+
req.user = { sub: payload.sub, role: payload.realm_access?.roles?.[0] };
|
|
86
|
+
req.keycloakUser = payload;
|
|
87
|
+
})
|
|
88
|
+
.then(() => next())
|
|
89
|
+
.catch(next);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=keycloak-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak-auth.js","sourceRoot":"","sources":["../src/keycloak-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface KeycloakSecretsInput {
|
|
2
|
+
/** Tenant site name, e.g. 'KIWI'. Uppercased/lowercased as needed internally. */
|
|
3
|
+
siteName: string;
|
|
4
|
+
/** Keycloak server origin, e.g. 'https://dev.idp.hcl-x.com' (no trailing slash needed). */
|
|
5
|
+
idpBaseUrl: string;
|
|
6
|
+
/** Shopify's external-IdP OAuth callback URL for this store (app-specific, not part of the Keycloak realm). */
|
|
7
|
+
callbackUrl: string;
|
|
8
|
+
/** Shopify's post-logout redirect URL for this store (app-specific, not part of the Keycloak realm). */
|
|
9
|
+
postLogoutRedirectUri: string;
|
|
10
|
+
}
|
|
11
|
+
export interface KeycloakSecretsResult {
|
|
12
|
+
siteName: string;
|
|
13
|
+
/** Secret ids that were created/updated — never the values, so callers don't accidentally log them. */
|
|
14
|
+
secretIds: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates/updates the 7 `${SITENAME}_KEYCLOAK_*` secrets (BASE_URL, REALM,
|
|
18
|
+
* CLIENT_ID, CLIENT_SECRET, DISCOVERY_URL, JWKS_URI, ISSUER) plus the 2
|
|
19
|
+
* `${SITENAME}_SHOPIFY_*` redirect-url secrets (CALLBACK_URL,
|
|
20
|
+
* POST_LOGOUT_REDIRECT_URI) that `getTenantConfig`'s `fetchKeycloakConfig`
|
|
21
|
+
* reads.
|
|
22
|
+
*
|
|
23
|
+
* `@platform-x/hep-authentication-provider`'s `AuthenticationManager` (the
|
|
24
|
+
* same package + version hep-prelem-registration depends on) is used to
|
|
25
|
+
* verify the site is a real, authenticatable tenant before writing anything —
|
|
26
|
+
* `iamAuthenticator` throws/returns falsy for a site missing from
|
|
27
|
+
* AUTH_TENANTS_INFO or with an unsupported auth_provider. It requires
|
|
28
|
+
* `initIAMSecrets` to have registered a secret-key service first (this
|
|
29
|
+
* function does so itself, scoped to just AUTH_TENANTS_INFO). Its public API
|
|
30
|
+
* doesn't expose the resolved realm/client_id/client_secret though (they're
|
|
31
|
+
* private fields on the KeyClockEndUserConnector it builds), so those values
|
|
32
|
+
* are read the same way `getClientTenantDetails` already does — straight from
|
|
33
|
+
* the AUTH_TENANTS_INFO secret, which is the one source both draw from.
|
|
34
|
+
*
|
|
35
|
+
* The remaining fields are either derived from Keycloak's standard endpoint
|
|
36
|
+
* layout (DISCOVERY_URL/JWKS_URI/ISSUER — same templates
|
|
37
|
+
* hep-authentication-provider and shp-auth-service's env.ts already use for
|
|
38
|
+
* the `kiwi` realm) or supplied by the caller (CALLBACK_URL/
|
|
39
|
+
* POST_LOGOUT_REDIRECT_URI — Shopify-side external-IdP redirect URLs, not
|
|
40
|
+
* Keycloak realm data, so they're written under the `SHOPIFY` provider
|
|
41
|
+
* segment instead and can't be resolved automatically).
|
|
42
|
+
*/
|
|
43
|
+
export declare function generateKeycloakSecrets(input: KeycloakSecretsInput): Promise<KeycloakSecretsResult>;
|
|
44
|
+
//# sourceMappingURL=keycloak-secrets-provisioner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak-secrets-provisioner.d.ts","sourceRoot":"","sources":["../src/keycloak-secrets-provisioner.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB,+GAA+G;IAC/G,WAAW,EAAE,MAAM,CAAC;IACpB,wGAAwG;IACxG,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,uGAAuG;IACvG,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+DzG"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateKeycloakSecrets = generateKeycloakSecrets;
|
|
4
|
+
const hep_authentication_provider_1 = require("@platform-x/hep-authentication-provider");
|
|
5
|
+
const hep_secret_access_1 = require("hep-secret-access");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
const extract_site_name_1 = require("./extract-site-name");
|
|
8
|
+
const tenant_config_1 = require("./tenant-config");
|
|
9
|
+
/**
|
|
10
|
+
* Creates/updates the 7 `${SITENAME}_KEYCLOAK_*` secrets (BASE_URL, REALM,
|
|
11
|
+
* CLIENT_ID, CLIENT_SECRET, DISCOVERY_URL, JWKS_URI, ISSUER) plus the 2
|
|
12
|
+
* `${SITENAME}_SHOPIFY_*` redirect-url secrets (CALLBACK_URL,
|
|
13
|
+
* POST_LOGOUT_REDIRECT_URI) that `getTenantConfig`'s `fetchKeycloakConfig`
|
|
14
|
+
* reads.
|
|
15
|
+
*
|
|
16
|
+
* `@platform-x/hep-authentication-provider`'s `AuthenticationManager` (the
|
|
17
|
+
* same package + version hep-prelem-registration depends on) is used to
|
|
18
|
+
* verify the site is a real, authenticatable tenant before writing anything —
|
|
19
|
+
* `iamAuthenticator` throws/returns falsy for a site missing from
|
|
20
|
+
* AUTH_TENANTS_INFO or with an unsupported auth_provider. It requires
|
|
21
|
+
* `initIAMSecrets` to have registered a secret-key service first (this
|
|
22
|
+
* function does so itself, scoped to just AUTH_TENANTS_INFO). Its public API
|
|
23
|
+
* doesn't expose the resolved realm/client_id/client_secret though (they're
|
|
24
|
+
* private fields on the KeyClockEndUserConnector it builds), so those values
|
|
25
|
+
* are read the same way `getClientTenantDetails` already does — straight from
|
|
26
|
+
* the AUTH_TENANTS_INFO secret, which is the one source both draw from.
|
|
27
|
+
*
|
|
28
|
+
* The remaining fields are either derived from Keycloak's standard endpoint
|
|
29
|
+
* layout (DISCOVERY_URL/JWKS_URI/ISSUER — same templates
|
|
30
|
+
* hep-authentication-provider and shp-auth-service's env.ts already use for
|
|
31
|
+
* the `kiwi` realm) or supplied by the caller (CALLBACK_URL/
|
|
32
|
+
* POST_LOGOUT_REDIRECT_URI — Shopify-side external-IdP redirect URLs, not
|
|
33
|
+
* Keycloak realm data, so they're written under the `SHOPIFY` provider
|
|
34
|
+
* segment instead and can't be resolved automatically).
|
|
35
|
+
*/
|
|
36
|
+
async function generateKeycloakSecrets(input) {
|
|
37
|
+
const siteName = input.siteName.toUpperCase();
|
|
38
|
+
const gsm = (0, hep_secret_access_1.secretManager)();
|
|
39
|
+
// iamAuthenticator() reads AUTH_TENANTS_INFO through a process-global secret
|
|
40
|
+
// service the consuming app must register once via initIAMSecrets (see
|
|
41
|
+
// hep-prelem-registration's secreteKeys.services.ts) — without it, it throws
|
|
42
|
+
// "IAM secrets not initialized" rather than resolving the tenant. Register a
|
|
43
|
+
// minimal one here, scoped to just the one secret iamAuthenticator reads,
|
|
44
|
+
// rather than hep-prelem-registration's readAllSecrets() (which lists every
|
|
45
|
+
// secret in the project — broader than this package's per-key philosophy;
|
|
46
|
+
// see README).
|
|
47
|
+
const rawAuthTenantsInfo = await gsm.read('AUTH_TENANTS_INFO');
|
|
48
|
+
(0, hep_authentication_provider_1.initIAMSecrets)({ getSecretKeys: () => Promise.resolve({ AUTH_TENANTS_INFO: rawAuthTenantsInfo }) });
|
|
49
|
+
const authManager = new hep_authentication_provider_1.AuthenticationManager();
|
|
50
|
+
// iamAuthenticator's return type is `any` upstream; treat it as unknown and
|
|
51
|
+
// only ever check truthiness — never read fields off it (see doc comment above).
|
|
52
|
+
const connector = await authManager.iamAuthenticator(siteName, true);
|
|
53
|
+
if (!connector) {
|
|
54
|
+
throw new errors_1.TenantNotFoundError(siteName);
|
|
55
|
+
}
|
|
56
|
+
const tenantInfo = await (0, tenant_config_1.getClientTenantDetails)(siteName);
|
|
57
|
+
const realm = tenantInfo?.tenant;
|
|
58
|
+
const clientInfo = tenantInfo?.clients?.[0];
|
|
59
|
+
if (!realm || !clientInfo?.client_id || !clientInfo?.client_secret) {
|
|
60
|
+
throw new errors_1.SecretNotFoundError(`AUTH_TENANTS_INFO entry for ${siteName} is missing tenant/client data`);
|
|
61
|
+
}
|
|
62
|
+
const baseUrl = input.idpBaseUrl.replace(/\/+$/, '');
|
|
63
|
+
const keycloakValues = {
|
|
64
|
+
BASE_URL: baseUrl,
|
|
65
|
+
REALM: realm,
|
|
66
|
+
CLIENT_ID: clientInfo.client_id,
|
|
67
|
+
CLIENT_SECRET: clientInfo.client_secret,
|
|
68
|
+
DISCOVERY_URL: `${baseUrl}/realms/${realm}/.well-known/openid-configuration`,
|
|
69
|
+
JWKS_URI: `${baseUrl}/realms/${realm}/protocol/openid-connect/certs`,
|
|
70
|
+
ISSUER: `${baseUrl}/realms/${realm}`,
|
|
71
|
+
};
|
|
72
|
+
// Shopify-side external-IdP redirect URLs — not Keycloak realm data, so
|
|
73
|
+
// written under the SHOPIFY provider segment (matches fetchKeycloakConfig).
|
|
74
|
+
const shopifyValues = {
|
|
75
|
+
CALLBACK_URL: input.callbackUrl,
|
|
76
|
+
POST_LOGOUT_REDIRECT_URI: input.postLogoutRedirectUri,
|
|
77
|
+
};
|
|
78
|
+
const secretIds = [];
|
|
79
|
+
for (const [key, value] of Object.entries(keycloakValues)) {
|
|
80
|
+
const secretId = (0, extract_site_name_1.buildSecretId)(siteName, 'KEYCLOAK', key);
|
|
81
|
+
await gsm.ensureSecret(secretId, { sitename: siteName.toLowerCase(), provider: 'keycloak' });
|
|
82
|
+
await gsm.addVersion(secretId, value);
|
|
83
|
+
secretIds.push(secretId);
|
|
84
|
+
}
|
|
85
|
+
for (const [key, value] of Object.entries(shopifyValues)) {
|
|
86
|
+
const secretId = (0, extract_site_name_1.buildSecretId)(siteName, 'SHOPIFY', key);
|
|
87
|
+
await gsm.ensureSecret(secretId, { sitename: siteName.toLowerCase(), provider: 'shopify' });
|
|
88
|
+
await gsm.addVersion(secretId, value);
|
|
89
|
+
secretIds.push(secretId);
|
|
90
|
+
}
|
|
91
|
+
(0, tenant_config_1.clearTenantCache)(siteName);
|
|
92
|
+
return { siteName, secretIds };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=keycloak-secrets-provisioner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak-secrets-provisioner.js","sourceRoot":"","sources":["../src/keycloak-secrets-provisioner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retries an async operation with exponential back-off.
|
|
3
|
+
*
|
|
4
|
+
* @param fn - Async function to execute
|
|
5
|
+
* @param retries - Maximum retry attempts (default 3)
|
|
6
|
+
* @param baseMs - Initial delay in ms (default 200); doubles each attempt
|
|
7
|
+
* @param shouldRetry - Optional predicate; returning false stops retrying early
|
|
8
|
+
*/
|
|
9
|
+
export declare function withRetry<T>(fn: () => Promise<T>, retries?: number, baseMs?: number, shouldRetry?: (err: unknown) => boolean): Promise<T>;
|
|
10
|
+
/** Wraps an async function with a timeout. Throws if it takes longer than ms. */
|
|
11
|
+
export declare function withTimeout<T>(fn: () => Promise<T>, ms?: number): Promise<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Simple circuit breaker. Opens after `failureThreshold` consecutive failures
|
|
14
|
+
* and resets after `resetAfterMs`.
|
|
15
|
+
*/
|
|
16
|
+
export declare class CircuitBreaker {
|
|
17
|
+
private readonly failureThreshold;
|
|
18
|
+
private readonly resetAfterMs;
|
|
19
|
+
private failures;
|
|
20
|
+
private openSince;
|
|
21
|
+
constructor(failureThreshold?: number, resetAfterMs?: number);
|
|
22
|
+
isOpen(): boolean;
|
|
23
|
+
call<T>(fn: () => Promise<T>): Promise<T>;
|
|
24
|
+
reset(): void;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=resilience.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience.d.ts","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,SAAI,EACX,MAAM,SAAM,EACZ,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GACtC,OAAO,CAAC,CAAC,CAAC,CAaZ;AAED,iFAAiF;AACjF,wBAAsB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAOhF;AAED;;;GAGG;AACH,qBAAa,cAAc;IAKvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAL/B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,SAAS,CAAuB;gBAGrB,gBAAgB,SAAI,EACpB,YAAY,SAAS;IAGxC,MAAM,IAAI,OAAO;IAUX,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAa/C,KAAK,IAAI,IAAI;CAId"}
|