@shushed/helpers 0.0.153 → 0.0.154

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/index.d.ts CHANGED
@@ -33553,11 +33553,12 @@ type PromisifiedFn<F extends (...args: any[]) => any> = (...args: Parameters<F>)
33553
33553
  declare function createHandlerDecorator<TSettings, TRequiredO extends object = {}, TRequiredH extends object = {}>(decoratorFactory: <C, O, H extends TRequiredH, H2 extends H, R>(settings: TSettings) => (handler: (c: C, o: O & TRequiredO, h: H2) => R) => (c: C, o: O & TRequiredO, h: H2) => R | Promise<R>): <C, O, H extends TRequiredH, H2 extends H, R>(settings: TSettings) => (handler: (c: C, o: O & TRequiredO, h: H2) => R) => (c: C, o: O & TRequiredO, h: H2) => R | Promise<R>;
33554
33554
  declare function getSecret(runtime: Runtime, name: string, accessToken: string): Promise<string>;
33555
33555
  declare const getAccessToken: () => Promise<string>;
33556
+ declare const getIdentityToken: (expectedAudience: string) => Promise<string>;
33556
33557
 
33557
33558
  type Level = 'env' | 'workflow' | 'trigger';
33558
33559
  type SetOptions = {
33559
33560
  ephemeralMs?: number;
33560
- ignoreStores?: Array<'redis'>;
33561
+ ignoreStores?: Array<'redis' | 'global'>;
33561
33562
  encrypted: boolean;
33562
33563
  encryptionKey?: string | undefined;
33563
33564
  };
@@ -33619,6 +33620,14 @@ declare class EnvEngine extends Runtime {
33619
33620
  [K in T[number]]: ValueDetails;
33620
33621
  }>;
33621
33622
  withGoogleAccessToken(): <T extends unknown>(fn: (value: string, requestCacheRefresh?: (() => void) | undefined) => T | Promise<T>) => Promise<T>;
33623
+ withGoogleIdentityToken(audience: {
33624
+ workflowId: string;
33625
+ triggerId: string;
33626
+ }): <T extends unknown>(fn: (value: string, requestCacheRefresh?: (() => void) | undefined) => T | Promise<T>) => Promise<T>;
33627
+ checkGoogleIdentiyToken(accessToken: string, audience?: {
33628
+ workflowId?: string;
33629
+ triggerId?: string;
33630
+ }, allowedServiceAccounts?: Array<string>): Promise<any>;
33622
33631
  withSecret(key: string): <T extends unknown>(fn: (value: string, requestCacheRefresh?: (() => void) | undefined) => T | Promise<T>) => Promise<T>;
33623
33632
  with<M extends string>(key: M, level?: Level, options?: SetOptions & {
33624
33633
  fetch?: () => Promise<string>;
@@ -34491,6 +34500,7 @@ declare const index_createHandlerDecorator: typeof createHandlerDecorator;
34491
34500
  declare const index_getAccessToken: typeof getAccessToken;
34492
34501
  declare const index_getEventTime: typeof getEventTime;
34493
34502
  declare const index_getFirestore: typeof getFirestore;
34503
+ declare const index_getIdentityToken: typeof getIdentityToken;
34494
34504
  declare const index_getSecret: typeof getSecret;
34495
34505
  declare const index_isCloudTask: typeof isCloudTask;
34496
34506
  declare const index_isCronMessage: typeof isCronMessage;
@@ -34504,7 +34514,7 @@ declare const index_slugify: typeof slugify;
34504
34514
  declare const index_validate: typeof validate;
34505
34515
  declare const index_validateGoogleAuth: typeof validateGoogleAuth;
34506
34516
  declare namespace index {
34507
- export { index_ActionHelper as ActionHelper, type index_ActionHelperOptions as ActionHelperOptions, index_AirtableHelper as AirtableHelper, index_BigQueryHelper as BigQueryHelper, index_CloudTasksHelper as CloudTasksHelper, index_DatoHelper as DatoHelper, index_EnvEngine as EnvEngine, type index_ILogging as ILogging, index_JWKSHelper as JWKSHelper, index_Logging as Logging, type index_Message as Message, type index_NodeOptions as NodeOptions, type index_OnExecuteBasicTriggerOpts as OnExecuteBasicTriggerOpts, type index_PromisifiedFn as PromisifiedFn, index_PubSubHelper as PubSubHelper, index_RateLimit as RateLimit, type index_ReceivedMessage as ReceivedMessage, index_RedisConnectionError as RedisConnectionError, index_Runtime as Runtime, index_SchedulerHelper as SchedulerHelper, index_Secrets as Secrets, index_TriggerHelper as TriggerHelper, type index_TriggerOnCreateOptions as TriggerOnCreateOptions, type index_TriggerOnExecuteOptions as TriggerOnExecuteOptions, index_createHandlerDecorator as createHandlerDecorator, index_getAccessToken as getAccessToken, index_getEventTime as getEventTime, index_getFirestore as getFirestore, index_getSecret as getSecret, index_isCloudTask as isCloudTask, index_isCronMessage as isCronMessage, index_isPubSubRequest as isPubSubRequest, index_isRespectfulNudge as isRespectfulNudge, index_parseDateOrDefault as parseDateOrDefault, index_sanitize as sanitize, index_sanitizeToString as sanitizeToString, index_shortHash as shortHash, index_slugify as slugify, index_validate as validate, index_validateGoogleAuth as validateGoogleAuth };
34517
+ export { index_ActionHelper as ActionHelper, type index_ActionHelperOptions as ActionHelperOptions, index_AirtableHelper as AirtableHelper, index_BigQueryHelper as BigQueryHelper, index_CloudTasksHelper as CloudTasksHelper, index_DatoHelper as DatoHelper, index_EnvEngine as EnvEngine, type index_ILogging as ILogging, index_JWKSHelper as JWKSHelper, index_Logging as Logging, type index_Message as Message, type index_NodeOptions as NodeOptions, type index_OnExecuteBasicTriggerOpts as OnExecuteBasicTriggerOpts, type index_PromisifiedFn as PromisifiedFn, index_PubSubHelper as PubSubHelper, index_RateLimit as RateLimit, type index_ReceivedMessage as ReceivedMessage, index_RedisConnectionError as RedisConnectionError, index_Runtime as Runtime, index_SchedulerHelper as SchedulerHelper, index_Secrets as Secrets, index_TriggerHelper as TriggerHelper, type index_TriggerOnCreateOptions as TriggerOnCreateOptions, type index_TriggerOnExecuteOptions as TriggerOnExecuteOptions, index_createHandlerDecorator as createHandlerDecorator, index_getAccessToken as getAccessToken, index_getEventTime as getEventTime, index_getFirestore as getFirestore, index_getIdentityToken as getIdentityToken, index_getSecret as getSecret, index_isCloudTask as isCloudTask, index_isCronMessage as isCronMessage, index_isPubSubRequest as isPubSubRequest, index_isRespectfulNudge as isRespectfulNudge, index_parseDateOrDefault as parseDateOrDefault, index_sanitize as sanitize, index_sanitizeToString as sanitizeToString, index_shortHash as shortHash, index_slugify as slugify, index_validate as validate, index_validateGoogleAuth as validateGoogleAuth };
34508
34518
  }
34509
34519
 
34510
34520
  export { index as lib, index$9 as schema, index$1 as types };
package/dist/index.js CHANGED
@@ -106142,6 +106142,7 @@ __export(src_public_exports, {
106142
106142
  getAccessToken: () => getAccessToken,
106143
106143
  getEventTime: () => getEventTime,
106144
106144
  getFirestore: () => getFirestore,
106145
+ getIdentityToken: () => getIdentityToken,
106145
106146
  getSecret: () => getSecret,
106146
106147
  isCloudTask: () => isCloudTask,
106147
106148
  isCronMessage: () => isCronMessage,
@@ -106546,9 +106547,32 @@ var getAccessToken = async () => {
106546
106547
  throw new Error(`Failed to obtain access token. Error: ${err.message}`);
106547
106548
  }
106548
106549
  };
106550
+ var getIdentityToken = async (expectedAudience) => {
106551
+ const metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?" + new URLSearchParams({
106552
+ audience: expectedAudience,
106553
+ format: "full"
106554
+ }).toString();
106555
+ try {
106556
+ const res = await fetch(metadataUrl, {
106557
+ headers: { "Metadata-Flavor": "Google" }
106558
+ });
106559
+ if (!res.ok) {
106560
+ throw new Error(`metadata token http ${res.status}`);
106561
+ }
106562
+ const json = await res.json();
106563
+ if (!json.access_token) {
106564
+ throw new Error("missing access_token in metadata response");
106565
+ }
106566
+ return json.access_token;
106567
+ } catch (err) {
106568
+ throw new Error(`Failed to obtain access token. Error: ${err.message}`);
106569
+ }
106570
+ };
106549
106571
 
106550
106572
  // src-public/env.ts
106551
106573
  var crypto3 = __toESM(require("crypto"));
106574
+ var import_crypto2 = require("crypto");
106575
+ var import_jose2 = require("jose");
106552
106576
 
106553
106577
  // src-public/redisClient.ts
106554
106578
  var import_redis = __toESM(require_dist4());
@@ -106652,7 +106676,7 @@ var EnvEngine = class extends Runtime {
106652
106676
  this._envCache[this.systemEnvName] = jsonEnv;
106653
106677
  }
106654
106678
  let cachedEnv = this._envCache[this.systemEnvName];
106655
- if (this._envCache["__mock_invalid"]) {
106679
+ if (cachedEnv["__mock_invalid"]) {
106656
106680
  cachedEnv = process.env;
106657
106681
  }
106658
106682
  options.encryptionKey = cachedEnv["SECRET_ENCRYPTION_KEY"];
@@ -106684,9 +106708,9 @@ var EnvEngine = class extends Runtime {
106684
106708
  );
106685
106709
  Object.assign(this.inMemoryRef[level], Object.fromEntries(Object.entries(objPlain).map(([k, v]) => [k, `${expirseAt}/${now}/${v}`])));
106686
106710
  if (options.ephemeralMs) {
106687
- Object.assign(this.globalInMemoryRef[level], Object.fromEntries(Object.entries(objEncrypted).map(([k, v]) => [k, `${expirseAt}/${now}/${v}`])));
106688
- }
106689
- if (options.ephemeralMs) {
106711
+ if (!options.ignoreStores?.includes("global")) {
106712
+ Object.assign(this.globalInMemoryRef[level], Object.fromEntries(Object.entries(objEncrypted).map(([k, v]) => [k, `${expirseAt}/${now}/${v}`])));
106713
+ }
106690
106714
  if (!options.ignoreStores?.includes("redis")) {
106691
106715
  (async () => {
106692
106716
  const redisClient = await getConnectedRedisClient(this.systemEnvName).catch(() => null);
@@ -106810,8 +106834,112 @@ var EnvEngine = class extends Runtime {
106810
106834
  }, {});
106811
106835
  return result;
106812
106836
  }
106837
+ /**
106838
+ * Get the Google Access Token to use with the Google Infrastructure (Schedule / Pubsub / Cloud Tasks etc.)
106839
+ * @param audience
106840
+ * @returns
106841
+ */
106813
106842
  withGoogleAccessToken() {
106814
- return this.with("google_access_token", "env", { encrypted: true, ephemeralMs: 10 * 60 * 1e3, fetch: () => getAccessToken() });
106843
+ const key = "google_access_token";
106844
+ return this.with(key, "env", { encrypted: true, ephemeralMs: 10 * 60 * 1e3, fetch: () => getAccessToken() });
106845
+ }
106846
+ /**
106847
+ * Get the Google Identity Token to use with other Buildship workflows
106848
+ * @param audience
106849
+ * @returns
106850
+ */
106851
+ withGoogleIdentityToken(audience) {
106852
+ const audienceNorm = {
106853
+ workflowId: audience ? audience.workflowId || this.workflowId : null,
106854
+ triggerId: audience ? audience.triggerId || this.triggerId : null
106855
+ };
106856
+ const expectedAudience = [process.env.GCLOUD_PROJECT, audienceNorm.triggerId, audienceNorm.workflowId].filter((x) => x !== null).join("-");
106857
+ return this.with(`google_identity_token_${expectedAudience}`, "env", { encrypted: true, ephemeralMs: 10 * 60 * 1e3, fetch: () => getIdentityToken(expectedAudience) });
106858
+ }
106859
+ async checkGoogleIdentiyToken(accessToken, audience, allowedServiceAccounts = [`runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`]) {
106860
+ if (!accessToken) {
106861
+ return false;
106862
+ }
106863
+ const defaultServiceAccount2 = `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`;
106864
+ const audienceNorm = {
106865
+ workflowId: audience ? audience.workflowId || this.workflowId : null,
106866
+ triggerId: audience ? audience.triggerId || this.triggerId : null
106867
+ };
106868
+ const expectedAudience = [process.env.GCLOUD_PROJECT, audienceNorm.triggerId, audienceNorm.workflowId].filter((x) => x !== null).join("-");
106869
+ const accessTokenInCache = await this.get("google_identity_token_" + expectedAudience, "env", { encrypted: true, isEphemeral: true, store: "global" });
106870
+ if (accessTokenInCache === accessToken && allowedServiceAccounts.includes(defaultServiceAccount2)) {
106871
+ return defaultServiceAccount2;
106872
+ }
106873
+ const splitted = accessToken.split(".");
106874
+ let keyMeta;
106875
+ let meta;
106876
+ try {
106877
+ keyMeta = JSON.parse(Buffer.from(splitted[0], "base64").toString("utf-8")) || {};
106878
+ meta = JSON.parse(Buffer.from(splitted[1], "base64").toString("utf-8")) || {};
106879
+ } catch (err) {
106880
+ this.logging.error(`Authorization header - Malformed Token. ${err.message}`);
106881
+ return false;
106882
+ }
106883
+ let tokenExpirationTime = meta.exp;
106884
+ if (!tokenExpirationTime) {
106885
+ this.logging.error(`Authorization header - Expiration time is missing`);
106886
+ return false;
106887
+ }
106888
+ tokenExpirationTime = tokenExpirationTime * 1e3;
106889
+ if (tokenExpirationTime < Date.now()) {
106890
+ this.logging.error(`Authorization header - Token expired`);
106891
+ return false;
106892
+ }
106893
+ if (!meta.email_verified) {
106894
+ this.logging.error(`Authorization header - Email not verified`);
106895
+ return false;
106896
+ }
106897
+ if (!allowedServiceAccounts.includes(meta.email)) {
106898
+ this.logging.error(`Authorization header - Wrong service account, got ${meta?.email}, expected one of ${allowedServiceAccounts.join(", ")}`);
106899
+ return false;
106900
+ }
106901
+ if (!keyMeta.kid || !keyMeta.alg) {
106902
+ this.logging.error(`Authorization header - Malformed Authorization Header. KID is missing`);
106903
+ return false;
106904
+ }
106905
+ if (meta.aud !== expectedAudience) {
106906
+ this.logging.error(`Authorization header - Wrong audience, got ${meta?.aud}, expected ${expectedAudience}`);
106907
+ return false;
106908
+ }
106909
+ const tokenSignature = (0, import_crypto2.createHash)("sha256").update(accessToken).digest("hex");
106910
+ const tokensServiceAccount = await this.get("google_identity_token_signature_" + tokenSignature, "env", { encrypted: false, isEphemeral: true, store: "global" });
106911
+ if (tokensServiceAccount && allowedServiceAccounts.includes(tokensServiceAccount)) {
106912
+ return true;
106913
+ }
106914
+ const publicKey = await this.with("google_public_keys", "env", {
106915
+ encrypted: false,
106916
+ ephemeralMs: 20 * 60 * 1e3,
106917
+ fetch: () => {
106918
+ return fetch("https://www.googleapis.com/oauth2/v3/certs").then((res) => res.text());
106919
+ }
106920
+ })(async (publicKeys, requestCacheRefresh) => {
106921
+ const jwks = JSON.parse(publicKeys);
106922
+ const keyInStorage = jwks?.keys.find((x) => x.kid === keyMeta.kid);
106923
+ if (!keyInStorage) {
106924
+ requestCacheRefresh?.();
106925
+ return false;
106926
+ }
106927
+ return keyInStorage;
106928
+ });
106929
+ try {
106930
+ await (0, import_jose2.jwtVerify)(accessToken, await (0, import_jose2.importJWK)(publicKey, keyMeta.alg), {
106931
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
106932
+ clockTolerance: 0,
106933
+ audience: expectedAudience
106934
+ });
106935
+ } catch (err) {
106936
+ return false;
106937
+ }
106938
+ await this.set([{
106939
+ name: "google_identity_token_signature_" + tokenSignature,
106940
+ value: meta.email
106941
+ }], "env", { encrypted: false, ephemeralMs: Math.min(tokenExpirationTime - Date.now(), 5 * 60 * 1e3), ignoreStores: ["redis"] });
106942
+ return meta.email;
106815
106943
  }
106816
106944
  withSecret(key) {
106817
106945
  return this.with("__secret_" + key, "env", {
@@ -107787,7 +107915,7 @@ var Secrets = class extends Runtime {
107787
107915
  var secret_default = Secrets;
107788
107916
 
107789
107917
  // src-public/cloudtasks.ts
107790
- var import_crypto2 = __toESM(require("crypto"));
107918
+ var import_crypto3 = __toESM(require("crypto"));
107791
107919
  async function gcpFetch(url, accessToken, options = {}) {
107792
107920
  let res;
107793
107921
  try {
@@ -107834,7 +107962,7 @@ var CloudTasksHelper = class extends Runtime {
107834
107962
  this.triggerId = opts.triggerId;
107835
107963
  }
107836
107964
  getQueueName(topicName) {
107837
- const infraPrefix = topicName.indexOf("---") !== -1 ? topicName : `${this.envName}---${topicName}-${import_crypto2.default.createHash("sha256").update(this.triggerId).digest("hex").slice(0, 8)}`;
107965
+ const infraPrefix = topicName.indexOf("---") !== -1 ? topicName : `${this.envName}---${topicName}-${import_crypto3.default.createHash("sha256").update(this.triggerId).digest("hex").slice(0, 8)}`;
107838
107966
  return `${infraPrefix}-${shortHash(this.triggerId)}`;
107839
107967
  }
107840
107968
  getQueuePath(topicName) {
@@ -107919,7 +108047,7 @@ var CloudTasksHelper = class extends Runtime {
107919
108047
  const createdTaskNames = [];
107920
108048
  for (let i = 0; i < repeat; i++) {
107921
108049
  const scheduledDate = scheduleSeconds ? new Date(Date.now() + 1e3 * scheduleSeconds * (i + 1)) : /* @__PURE__ */ new Date();
107922
- const taskId = opts.doNotCreateIfExists ? import_crypto2.default.createHash("sha256").update(opts.doNotCreateIfExists + Object.entries(opts.extraQuery || {}).sort().map((x) => x.join("=")).join("&")).digest("hex").slice(0, 8) : void 0;
108050
+ const taskId = opts.doNotCreateIfExists ? import_crypto3.default.createHash("sha256").update(opts.doNotCreateIfExists + Object.entries(opts.extraQuery || {}).sort().map((x) => x.join("=")).join("&")).digest("hex").slice(0, 8) : void 0;
107923
108051
  const scheduleTime = scheduleSeconds ? scheduledDate.toISOString() : void 0;
107924
108052
  const httpRequest = {
107925
108053
  httpMethod: opts.httpTarget.httpMethod || "POST",
@@ -109197,30 +109325,44 @@ var TriggerHelper = class {
109197
109325
  optionsEnchanced.req.throw(401, `Invalid Client IP`);
109198
109326
  return;
109199
109327
  }
109200
- matchingAPIKeysDetails = await envEngine.withSecret("triggers_api_keys")(async (secretApiKeys) => {
109201
- let apiKeys = [];
109202
- try {
109203
- apiKeys = JSON.parse(secretApiKeys);
109204
- } catch (err) {
109205
- throw new Error(`Could not parse triggers_api_keys secrets content, are you sure it is a valid JSON?. ${err.message}`);
109206
- }
109207
- if (apiKeys.length === 0 || !Array.isArray(apiKeys)) {
109208
- throw new Error("triggers_api_keys secrets content must be a JSON encoded array");
109328
+ if (authHeader.split(".").length === 3) {
109329
+ const allowedServiceAccounts = [defaultServiceAccount, ...this.actions.map((x) => x.actionOptions.routingConditions?.api?.roles).flat().filter((x) => x?.includes("@"))];
109330
+ const serviceAccount = await envEngine.checkGoogleIdentiyToken(authHeader.slice("Bearer ".length), void 0, allowedServiceAccounts);
109331
+ if (serviceAccount) {
109332
+ if (serviceAccount === defaultServiceAccount) {
109333
+ matchingAPIKeysDetails.roles.push("*");
109334
+ matchingAPIKeysDetails.roles.push("runtime");
109335
+ }
109336
+ matchingAPIKeysDetails.roles.push(serviceAccount);
109337
+ matchingAPIKeysDetails.flags.push("google_identity_token");
109209
109338
  }
109210
- const matchingApiKeys = apiKeys.filter((x) => authHeader.slice("Bearer ".length) === x.apiKey);
109211
- const allowedApiKeys = matchingApiKeys.filter((x) => isIpInCidrs(clientIp, x.allowedCIDRs));
109212
- return {
109213
- roles: allowedApiKeys.flatMap((x) => x.assumedRoles),
109214
- flags: allowedApiKeys.flatMap((x) => x.flags)
109215
- };
109216
- }).catch((err) => {
109217
- options.logging.error(`Possible issue with the secret. Check the secret configuration ${err.message}`);
109218
- return {
109219
- roles: [],
109220
- flags: []
109221
- };
109222
- });
109223
- if (matchingAPIKeysDetails.roles.length) {
109339
+ }
109340
+ if (!matchingAPIKeysDetails.roles.length) {
109341
+ matchingAPIKeysDetails = await envEngine.withSecret("triggers_api_keys")(async (secretApiKeys) => {
109342
+ let apiKeys = [];
109343
+ try {
109344
+ apiKeys = JSON.parse(secretApiKeys);
109345
+ } catch (err) {
109346
+ throw new Error(`Could not parse triggers_api_keys secrets content, are you sure it is a valid JSON?. ${err.message}`);
109347
+ }
109348
+ if (apiKeys.length === 0 || !Array.isArray(apiKeys)) {
109349
+ throw new Error("triggers_api_keys secrets content must be a JSON encoded array");
109350
+ }
109351
+ const matchingApiKeys = apiKeys.filter((x) => authHeader.slice("Bearer ".length) === x.apiKey);
109352
+ const allowedApiKeys = matchingApiKeys.filter((x) => isIpInCidrs(clientIp, x.allowedCIDRs));
109353
+ return {
109354
+ roles: allowedApiKeys.flatMap((x) => x.assumedRoles),
109355
+ flags: allowedApiKeys.flatMap((x) => x.flags)
109356
+ };
109357
+ }).catch((err) => {
109358
+ options.logging.error(`Possible issue with the secret. Check the secret configuration ${err.message}`);
109359
+ return {
109360
+ roles: [],
109361
+ flags: []
109362
+ };
109363
+ });
109364
+ }
109365
+ if (!matchingAPIKeysDetails.roles.length) {
109224
109366
  const isDeprecatedKeys = config.apiKey && authHeader.slice("Bearer ".length) === config.apiKey;
109225
109367
  matchingAPIKeysDetails.roles = isDeprecatedKeys ? ["system"] : [];
109226
109368
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shushed/helpers",
3
- "version": "0.0.153",
3
+ "version": "0.0.154",
4
4
  "author": "",
5
5
  "license": "UNLICENSED",
6
6
  "description": "",