@shushed/helpers 0.0.21 → 0.0.22

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
@@ -1,5 +1,10 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
2
  import { DeepRedact } from '@hackylabs/deep-redact';
3
+ import { DocumentReference, Firestore } from '@google-cloud/firestore';
4
+ import { PubSub, Topic, Subscription } from '@google-cloud/pubsub';
5
+ import * as _google_cloud_scheduler_build_protos_protos from '@google-cloud/scheduler/build/protos/protos';
6
+ import { CloudSchedulerClient } from '@google-cloud/scheduler';
7
+ import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
3
8
 
4
9
  declare const schema$l: {
5
10
  readonly $schema: "http://json-schema.org/draft-07/schema#";
@@ -28691,11 +28696,162 @@ declare function validate<T>(payload: T, schema: JSONSchemaType<T>, options: {
28691
28696
  declare const sanitizeToString: DeepRedact;
28692
28697
  declare const sanitize: DeepRedact;
28693
28698
 
28699
+ type Opts = {
28700
+ workflowId: string;
28701
+ triggerId: string;
28702
+ url: string;
28703
+ envName: string;
28704
+ } | {
28705
+ workflowId: string;
28706
+ triggerId: string;
28707
+ url: string;
28708
+ };
28709
+ declare class Runtime {
28710
+ readonly workflowId: string;
28711
+ readonly triggerId: string;
28712
+ readonly envName: string;
28713
+ readonly systemEnvName: string;
28714
+ readonly runtimeUrl: string;
28715
+ constructor(opts: Opts);
28716
+ }
28717
+
28718
+ declare class EnvEngine extends Runtime {
28719
+ docRef: DocumentReference;
28720
+ private store;
28721
+ constructor(opts: Opts, firestore: Firestore);
28722
+ set(envs: Array<{
28723
+ name: string;
28724
+ value: string;
28725
+ }>): Promise<undefined>;
28726
+ private initializeIfNeeded;
28727
+ get<T extends ReadonlyArray<string>>(keys: T): Promise<{
28728
+ [K in T[number]]: string;
28729
+ }>;
28730
+ get(key: string): Promise<string>;
28731
+ }
28732
+
28733
+ declare class PubSubHelper extends Runtime {
28734
+ private pubSub;
28735
+ constructor(opts: Opts, pubSub: PubSub);
28736
+ createOrUpdate(opts: {
28737
+ topicName: string;
28738
+ dlq: boolean;
28739
+ retryLimit: number;
28740
+ retryDelay: number;
28741
+ ackDeadline: number;
28742
+ }): Promise<{
28743
+ topic: Topic;
28744
+ dlqTopic: Topic | null;
28745
+ dlqSubscription: Subscription | null;
28746
+ subscription: Subscription;
28747
+ }>;
28748
+ private findTopic;
28749
+ private ensureTopicExists;
28750
+ private getNameFromFullyQualifiedName;
28751
+ private ensureSubscribtionExists;
28752
+ private ensureDLQSubscriptionExists;
28753
+ deleteSubscription(subscriptionURN: string): Promise<any>;
28754
+ }
28755
+
28756
+ declare class SchedulerHelper extends Runtime {
28757
+ private extraQuery;
28758
+ private pubSubTarget;
28759
+ private pubSubProjectId;
28760
+ private schedulerLocationId;
28761
+ private schedulerClient;
28762
+ constructor(opts: Opts & {
28763
+ extraQuery: Record<string, string>;
28764
+ serviceAccount: string;
28765
+ defaultServiceAccount: string;
28766
+ pubSubTarget?: Topic | null;
28767
+ schedulerLocationId?: string | null;
28768
+ }, schedulerClient: CloudSchedulerClient);
28769
+ get locationIds(): string[];
28770
+ getTarget(): {
28771
+ pubsubTarget: {
28772
+ topicName: string;
28773
+ data: Buffer<ArrayBuffer>;
28774
+ };
28775
+ httpTarget?: undefined;
28776
+ } | {
28777
+ httpTarget: {
28778
+ uri: string;
28779
+ httpMethod: "POST";
28780
+ headers: {
28781
+ "Content-Type": string;
28782
+ };
28783
+ oidcToken: {
28784
+ serviceAccountEmail: string;
28785
+ audience: string;
28786
+ };
28787
+ };
28788
+ pubsubTarget?: undefined;
28789
+ };
28790
+ create({ schedule, timezone }: {
28791
+ schedule: string;
28792
+ timezone: string;
28793
+ }): Promise<{
28794
+ status: _google_cloud_scheduler_build_protos_protos.google.rpc.IStatus | null | undefined;
28795
+ state: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.Job.State | "STATE_UNSPECIFIED" | "ENABLED" | "PAUSED" | "DISABLED" | "UPDATE_FAILED" | null | undefined;
28796
+ name: string | null | undefined;
28797
+ httpTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IHttpTarget | null | undefined;
28798
+ pubsubTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IPubsubTarget | null | undefined;
28799
+ }>;
28800
+ update({ schedule, timezone }: {
28801
+ schedule: string;
28802
+ timezone: string;
28803
+ }): Promise<{
28804
+ status: _google_cloud_scheduler_build_protos_protos.google.rpc.IStatus | null | undefined;
28805
+ state: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.Job.State | "STATE_UNSPECIFIED" | "ENABLED" | "PAUSED" | "DISABLED" | "UPDATE_FAILED" | null | undefined;
28806
+ name: string | null | undefined;
28807
+ httpTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IHttpTarget | null | undefined;
28808
+ pubsubTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IPubsubTarget | null | undefined;
28809
+ }>;
28810
+ delete(): Promise<any>;
28811
+ }
28812
+
28813
+ declare class Secrets extends Runtime {
28814
+ private secretsManagerClient;
28815
+ constructor(opts: Opts, secretsManagerClient: SecretManagerServiceClient);
28816
+ getSecret(name: string): Promise<string>;
28817
+ }
28818
+
28819
+ declare class JWKSHelper extends Runtime {
28820
+ private firestore;
28821
+ constructor(opts: Opts, firestore: Firestore);
28822
+ validateAuthorizationHeader(authorizationHeader: string, expectedServiceAccount?: string | null): Promise<boolean>;
28823
+ getKey(keyId: string): Promise<{
28824
+ kid: string;
28825
+ } | undefined>;
28826
+ private fetchJWKS;
28827
+ }
28828
+
28829
+ declare const isPubSubRequest: (headers: Record<string, string>) => boolean;
28830
+ declare const isCronMessage: (headers: Record<string, string>) => boolean;
28831
+ declare const validateGoogleAuth: (opts: Opts & {
28832
+ serviceAccount?: string | null;
28833
+ }, headers: Record<string, string>, firestore: Firestore) => Promise<void>;
28834
+
28835
+ type index_EnvEngine = EnvEngine;
28836
+ declare const index_EnvEngine: typeof EnvEngine;
28837
+ type index_JWKSHelper = JWKSHelper;
28838
+ declare const index_JWKSHelper: typeof JWKSHelper;
28839
+ type index_PubSubHelper = PubSubHelper;
28840
+ declare const index_PubSubHelper: typeof PubSubHelper;
28841
+ type index_Runtime = Runtime;
28842
+ declare const index_Runtime: typeof Runtime;
28843
+ type index_SchedulerHelper = SchedulerHelper;
28844
+ declare const index_SchedulerHelper: typeof SchedulerHelper;
28845
+ type index_Secrets = Secrets;
28846
+ declare const index_Secrets: typeof Secrets;
28847
+ declare const index_isCronMessage: typeof isCronMessage;
28848
+ declare const index_isPubSubRequest: typeof isPubSubRequest;
28694
28849
  declare const index_sanitize: typeof sanitize;
28695
28850
  declare const index_sanitizeToString: typeof sanitizeToString;
28696
28851
  declare const index_validate: typeof validate;
28852
+ declare const index_validateGoogleAuth: typeof validateGoogleAuth;
28697
28853
  declare namespace index {
28698
- export { index_sanitize as sanitize, index_sanitizeToString as sanitizeToString, index_validate as validate };
28854
+ export { index_EnvEngine as EnvEngine, index_JWKSHelper as JWKSHelper, index_PubSubHelper as PubSubHelper, index_Runtime as Runtime, index_SchedulerHelper as SchedulerHelper, index_Secrets as Secrets, index_isCronMessage as isCronMessage, index_isPubSubRequest as isPubSubRequest, index_sanitize as sanitize, index_sanitizeToString as sanitizeToString, index_validate as validate, index_validateGoogleAuth as validateGoogleAuth };
28699
28855
  }
28700
28856
 
28701
28857
  export { index as lib, index$9 as schema, index$1 as types };
package/dist/index.js CHANGED
@@ -280,9 +280,18 @@ var shipped_exports2 = {};
280
280
  // src-public/index.ts
281
281
  var src_public_exports = {};
282
282
  __export(src_public_exports, {
283
+ EnvEngine: () => env_default,
284
+ JWKSHelper: () => jwks_default,
285
+ PubSubHelper: () => pubsub_default,
286
+ Runtime: () => Runtime,
287
+ SchedulerHelper: () => scheduler_default,
288
+ Secrets: () => secret_default,
289
+ isCronMessage: () => isCronMessage,
290
+ isPubSubRequest: () => isPubSubRequest,
283
291
  sanitize: () => sanitize,
284
292
  sanitizeToString: () => sanitizeToString,
285
- validate: () => validate
293
+ validate: () => validate,
294
+ validateGoogleAuth: () => validateGoogleAuth
286
295
  });
287
296
 
288
297
  // src-public/validate.ts
@@ -336,6 +345,513 @@ var sanitize = new import_deep_redact.DeepRedact({
336
345
  remove: false,
337
346
  replaceStringByLength: true
338
347
  });
348
+
349
+ // src-public/runtime.ts
350
+ var DEFAULT_BRANCH = "migratedprod";
351
+ var Runtime = class {
352
+ workflowId;
353
+ triggerId;
354
+ envName;
355
+ systemEnvName;
356
+ runtimeUrl;
357
+ constructor(opts) {
358
+ this.workflowId = opts.workflowId;
359
+ this.triggerId = opts.triggerId;
360
+ this.systemEnvName = typeof opts.envName !== "undefined" ? opts.envName : (() => {
361
+ const hostId = opts.url.replace(/^(?:https?:)?\/\//i, "").split("/")[0].split(".")[0].toLowerCase();
362
+ let branch = DEFAULT_BRANCH;
363
+ if (hostId.includes("-")) {
364
+ branch = hostId.split("-")[0];
365
+ }
366
+ return branch;
367
+ })();
368
+ this.runtimeUrl = "https://" + new URL("https://" + opts.url.replace(/^(?:https?:)?\/\//i, "").split("/")[0]).host;
369
+ this.envName = this.systemEnvName === DEFAULT_BRANCH ? "prod" : this.systemEnvName;
370
+ }
371
+ };
372
+
373
+ // src-public/env.ts
374
+ var EnvEngine = class extends Runtime {
375
+ docRef;
376
+ store = {};
377
+ constructor(opts, firestore) {
378
+ super(opts);
379
+ this.docRef = firestore?.doc(`${this.workflowId}/${this.triggerId}/${this.envName}`);
380
+ }
381
+ async set(envs) {
382
+ await this.initializeIfNeeded();
383
+ const obj = envs.filter((env) => env.name).reduce(
384
+ (prev, curr) => ({ ...prev, [curr.name]: curr.value }),
385
+ {}
386
+ );
387
+ if (this.docRef) {
388
+ try {
389
+ await this.docRef.set(
390
+ obj,
391
+ { merge: true }
392
+ );
393
+ } catch (err) {
394
+ throw new Error(`Failed to set the ${envs.map((x) => x.name).join(", ")}. Error: ${err.message}`);
395
+ }
396
+ }
397
+ Object.assign(this.store, obj);
398
+ return void 0;
399
+ }
400
+ async initializeIfNeeded() {
401
+ if (!this.store) {
402
+ if (this.docRef) {
403
+ const docSnapshot = await this.docRef.get();
404
+ const data = docSnapshot.data();
405
+ this.store = {};
406
+ for (const k in data) {
407
+ const v = data[k];
408
+ this.store[k] = typeof v !== "string" ? `${v === null || typeof v === "undefined" ? "" : `${v}`}` : v;
409
+ }
410
+ } else {
411
+ this.store = {};
412
+ }
413
+ }
414
+ }
415
+ async get(keys) {
416
+ await this.initializeIfNeeded();
417
+ if (typeof keys === "string") {
418
+ return this.store?.[keys] || "";
419
+ }
420
+ return keys.reduce((acc, k) => {
421
+ const v = this.store?.[k];
422
+ acc[k] = typeof v !== "string" ? `${v === null || typeof v === "undefined" ? "" : `${v}`}` : v;
423
+ return acc;
424
+ }, {});
425
+ }
426
+ };
427
+ var env_default = EnvEngine;
428
+
429
+ // src-public/pubsub.ts
430
+ var PubSubHelper = class extends Runtime {
431
+ constructor(opts, pubSub) {
432
+ super(opts);
433
+ this.pubSub = pubSub;
434
+ }
435
+ async createOrUpdate(opts) {
436
+ const subscriptionName = `${this.envName === "prod" ? "" : `${this.envName}---`}${this.triggerId}`;
437
+ const topic = await this.ensureTopicExists(opts.topicName);
438
+ let dlqTopic = null;
439
+ let dlqSubscription = null;
440
+ if (opts.dlq) {
441
+ const dlqTopicName = `${opts.topicName}-dlq`;
442
+ const dlqSubscriptionName = `${subscriptionName}-dlq`;
443
+ dlqTopic = await this.ensureTopicExists(dlqTopicName);
444
+ dlqSubscription = await this.ensureDLQSubscriptionExists(dlqSubscriptionName, {
445
+ topic: dlqTopic
446
+ });
447
+ }
448
+ const subscription = await this.ensureSubscribtionExists(subscriptionName, {
449
+ topic,
450
+ dlqTopic,
451
+ retryMinDelay: opts.retryDelay,
452
+ retryMaxDelay: opts.retryDelay,
453
+ retryLimit: opts.retryLimit,
454
+ endpointUrl: this.runtimeUrl + `/executeWorkflow/${this.workflowId}/${this.triggerId}`,
455
+ ackDeadline: opts.ackDeadline
456
+ });
457
+ return {
458
+ topic,
459
+ dlqTopic,
460
+ dlqSubscription,
461
+ subscription
462
+ };
463
+ }
464
+ async findTopic(topicName) {
465
+ const [topics] = await this.pubSub.getTopics();
466
+ return topics.find((topic) => this.getNameFromFullyQualifiedName(topic.name) === topicName);
467
+ }
468
+ async ensureTopicExists(topicName) {
469
+ let topic = await this.findTopic(topicName);
470
+ if (!topic) {
471
+ await this.pubSub.createTopic(topicName);
472
+ topic = await this.findTopic(topicName);
473
+ }
474
+ if (!topic) {
475
+ throw new Error(`Invariant: While creating topic - topic is not found`);
476
+ }
477
+ return topic;
478
+ }
479
+ getNameFromFullyQualifiedName(fullyQName) {
480
+ const name = fullyQName.split("/");
481
+ return name[name.length - 1];
482
+ }
483
+ async ensureSubscribtionExists(subscriptionName, opts) {
484
+ let [subscriptions] = await opts.topic.getSubscriptions();
485
+ let subscription = subscriptions.find((subscription2) => this.getNameFromFullyQualifiedName(subscription2.name) === subscriptionName);
486
+ let endpointHasChanged = false;
487
+ let retryLimitHasChanged = false;
488
+ let retryDelayHasChanged = false;
489
+ let shouldRecreate = false;
490
+ let dlqHasChanged = false;
491
+ if (subscription) {
492
+ const [subscriptionMetaData] = await subscription.getMetadata();
493
+ endpointHasChanged = subscriptionMetaData.pushConfig?.pushEndpoint != opts.endpointUrl;
494
+ retryDelayHasChanged = subscriptionMetaData.retryPolicy?.minimumBackoff?.seconds != opts.retryMinDelay;
495
+ retryLimitHasChanged = subscriptionMetaData.deadLetterPolicy?.maxDeliveryAttempts != opts.retryLimit;
496
+ dlqHasChanged = !subscriptionMetaData.deadLetterPolicy && !!opts.dlqTopic || !!subscriptionMetaData.deadLetterPolicy?.deadLetterTopic && !opts.dlqTopic;
497
+ shouldRecreate = endpointHasChanged || retryDelayHasChanged || retryLimitHasChanged || dlqHasChanged;
498
+ }
499
+ if (subscription && shouldRecreate) {
500
+ await subscription.delete();
501
+ }
502
+ if (!subscription || shouldRecreate) {
503
+ await opts.topic.createSubscription(subscriptionName, Object.assign({
504
+ ackDeadlineSeconds: opts.ackDeadline || 90,
505
+ pushConfig: {
506
+ pushEndpoint: opts.endpointUrl,
507
+ oidcToken: {
508
+ serviceAccountEmail: `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`,
509
+ audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
510
+ }
511
+ },
512
+ retryPolicy: {
513
+ minimumBackoff: { seconds: opts.retryMinDelay },
514
+ maximumBackoff: { seconds: opts.retryMaxDelay }
515
+ }
516
+ }, opts.dlqTopic ? {
517
+ deadLetterPolicy: {
518
+ deadLetterTopic: opts.dlqTopic.name,
519
+ maxDeliveryAttempts: opts.retryLimit
520
+ }
521
+ } : {}));
522
+ [subscriptions] = await opts.topic.getSubscriptions();
523
+ subscription = subscriptions.find((subscription2) => this.getNameFromFullyQualifiedName(subscription2.name) === subscriptionName);
524
+ }
525
+ if (!subscription) {
526
+ throw new Error(`Invariant: While creating subscription - subscription is not found`);
527
+ }
528
+ return subscription;
529
+ }
530
+ async ensureDLQSubscriptionExists(subscriptionName, opts) {
531
+ let [subscriptions] = await opts.topic.getSubscriptions();
532
+ let subscription = subscriptions.find((subscription2) => this.getNameFromFullyQualifiedName(subscription2.name) === subscriptionName);
533
+ if (!subscription) {
534
+ await opts.topic.createSubscription(subscriptionName);
535
+ [subscriptions] = await opts.topic.getSubscriptions();
536
+ subscription = subscriptions.find((subscription2) => this.getNameFromFullyQualifiedName(subscription2.name) === subscriptionName);
537
+ }
538
+ if (!subscription) {
539
+ throw new Error(`Invariant: While creating subscription - subscription is not found`);
540
+ }
541
+ return subscription;
542
+ }
543
+ async deleteSubscription(subscriptionURN) {
544
+ const sub = this.pubSub.subscription(subscriptionURN);
545
+ return sub.delete().catch((err) => {
546
+ if (err.code === 5) {
547
+ return err.message;
548
+ }
549
+ throw err;
550
+ });
551
+ }
552
+ };
553
+ var pubsub_default = PubSubHelper;
554
+
555
+ // src-public/scheduler.ts
556
+ var SchedulerHelper = class extends Runtime {
557
+ extraQuery;
558
+ pubSubTarget;
559
+ pubSubProjectId;
560
+ schedulerLocationId;
561
+ schedulerClient;
562
+ constructor(opts, schedulerClient) {
563
+ super(opts);
564
+ this.extraQuery = opts.extraQuery;
565
+ this.pubSubTarget = opts.pubSubTarget || null;
566
+ this.pubSubProjectId = this.pubSubTarget?.name?.split("/")[1] || null;
567
+ this.schedulerLocationId = opts.schedulerLocationId || "europe-west2";
568
+ this.schedulerClient = schedulerClient;
569
+ }
570
+ get locationIds() {
571
+ return [
572
+ "asia-east1",
573
+ "asia-northeast1",
574
+ "asia-southeast1",
575
+ "europe-west1",
576
+ "us-central1",
577
+ "us-east1",
578
+ "us-east4",
579
+ "us-west1"
580
+ ];
581
+ }
582
+ getTarget() {
583
+ if (this.pubSubTarget) {
584
+ return {
585
+ pubsubTarget: {
586
+ topicName: this.pubSubTarget.name,
587
+ data: Buffer.from(JSON.stringify({
588
+ source: `${this.workflowId}/${this.triggerId}/${this.systemEnvName}`
589
+ }), "utf-8")
590
+ }
591
+ };
592
+ }
593
+ return {
594
+ httpTarget: {
595
+ uri: `${this.runtimeUrl}/executeWorkflow/${this.workflowId}/${this.triggerId}?${new URLSearchParams(this.extraQuery).toString()}`,
596
+ httpMethod: "POST",
597
+ headers: {
598
+ "Content-Type": "application/json"
599
+ },
600
+ oidcToken: {
601
+ serviceAccountEmail: `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`,
602
+ audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
603
+ }
604
+ }
605
+ };
606
+ }
607
+ async create({ schedule, timezone }) {
608
+ let parent;
609
+ let name;
610
+ if (this.pubSubProjectId) {
611
+ parent = `projects/${this.pubSubProjectId}/locations/${this.schedulerLocationId}`;
612
+ name = `${parent}/jobs/buildship-workflow-${this.triggerId}-${this.systemEnvName}`;
613
+ } else {
614
+ let locationId = process.env.SERVICE_REGION;
615
+ locationId = this.locationIds.includes(locationId) ? locationId : "us-central1";
616
+ parent = `projects/${process.env.GCLOUD_PROJECT}/locations/${locationId}`;
617
+ name = `${parent}/jobs/rowy-workflow-${this.triggerId}-${this.systemEnvName}`;
618
+ }
619
+ try {
620
+ const response = await this.schedulerClient.createJob({
621
+ parent,
622
+ job: {
623
+ name,
624
+ schedule,
625
+ timeZone: timezone,
626
+ ...this.getTarget()
627
+ }
628
+ });
629
+ return {
630
+ status: response[0].status,
631
+ state: response[0].state,
632
+ name: response[0].name,
633
+ httpTarget: response[0].httpTarget,
634
+ pubsubTarget: response[0].pubsubTarget
635
+ };
636
+ } catch (error) {
637
+ if (error.code === 6) {
638
+ return this.update(
639
+ { schedule, timezone }
640
+ );
641
+ } else {
642
+ throw error;
643
+ }
644
+ }
645
+ }
646
+ async update({ schedule, timezone }) {
647
+ let parent;
648
+ let name;
649
+ if (this.pubSubProjectId) {
650
+ parent = `projects/${this.pubSubProjectId}/locations/${this.schedulerLocationId}`;
651
+ name = `${parent}/jobs/buildship-workflow-${this.triggerId}-${this.systemEnvName}`;
652
+ } else {
653
+ let locationId = process.env.SERVICE_REGION;
654
+ locationId = this.locationIds.includes(locationId) ? locationId : "us-central1";
655
+ parent = `projects/${process.env.GCLOUD_PROJECT}/locations/${locationId}`;
656
+ name = `${parent}/jobs/rowy-workflow-${this.triggerId}-${this.systemEnvName}`;
657
+ }
658
+ try {
659
+ const response = await this.schedulerClient.updateJob({
660
+ job: {
661
+ ...this.getTarget(),
662
+ name,
663
+ schedule,
664
+ timeZone: timezone
665
+ }
666
+ });
667
+ return {
668
+ status: response[0].status,
669
+ state: response[0].state,
670
+ name: response[0].name,
671
+ httpTarget: response[0].httpTarget,
672
+ pubsubTarget: response[0].pubsubTarget
673
+ };
674
+ } catch {
675
+ const response = await this.schedulerClient.createJob({
676
+ parent,
677
+ job: {
678
+ name,
679
+ schedule,
680
+ timeZone: timezone,
681
+ ...this.getTarget()
682
+ }
683
+ });
684
+ return {
685
+ status: response[0].status,
686
+ state: response[0].state,
687
+ name: response[0].name,
688
+ httpTarget: response[0].httpTarget,
689
+ pubsubTarget: response[0].pubsubTarget
690
+ };
691
+ }
692
+ }
693
+ async delete() {
694
+ let parent;
695
+ let name;
696
+ if (this.pubSubProjectId) {
697
+ parent = `projects/${this.pubSubProjectId}/locations/${this.schedulerLocationId}`;
698
+ name = `${parent}/jobs/buildship-workflow-${this.triggerId}-${this.systemEnvName}`;
699
+ } else {
700
+ let locationId = process.env.SERVICE_REGION;
701
+ locationId = this.locationIds.includes(locationId) ? locationId : "us-central1";
702
+ parent = `projects/${process.env.GCLOUD_PROJECT}/locations/${locationId}`;
703
+ name = `${parent}/jobs/rowy-workflow-${this.triggerId}-${this.systemEnvName}`;
704
+ }
705
+ const request = {
706
+ name
707
+ };
708
+ let response;
709
+ try {
710
+ response = await this.schedulerClient.deleteJob(request);
711
+ } catch (err) {
712
+ if (err.code === 5) {
713
+ return err.message;
714
+ }
715
+ throw err;
716
+ }
717
+ return response;
718
+ }
719
+ };
720
+ var scheduler_default = SchedulerHelper;
721
+
722
+ // src-public/secret.ts
723
+ var Secrets = class extends Runtime {
724
+ constructor(opts, secretsManagerClient) {
725
+ super(opts);
726
+ this.secretsManagerClient = secretsManagerClient;
727
+ }
728
+ async getSecret(name) {
729
+ try {
730
+ const [secretVersion] = await this.secretsManagerClient.accessSecretVersion({
731
+ name: `projects/${process.env.GCLOUD_PROJECT}/secrets/${this.systemEnvName}---${name}/versions/latest`
732
+ });
733
+ const payload = secretVersion.payload?.data?.toString() || "";
734
+ return payload;
735
+ } catch (err) {
736
+ throw new Error(`Failed to get secret ${name}. Error: ${err.message}`);
737
+ }
738
+ }
739
+ };
740
+ var secret_default = Secrets;
741
+
742
+ // src-public/jwks.ts
743
+ var import_jose = require("jose");
744
+ var CACHE_TABLE = "cache-global";
745
+ var JWKSHelper = class extends Runtime {
746
+ constructor(opts, firestore) {
747
+ super(opts);
748
+ this.firestore = firestore;
749
+ }
750
+ async validateAuthorizationHeader(authorizationHeader, expectedServiceAccount = null) {
751
+ const splitted = authorizationHeader.split(".");
752
+ const keyMeta = JSON.parse(Buffer.from(splitted[0], "base64").toString("utf-8")) || {};
753
+ const meta = JSON.parse(Buffer.from(splitted[1], "base64").toString("utf-8")) || {};
754
+ if (!meta.email_verified) {
755
+ throw new Error("Authorization header - Email not verified");
756
+ }
757
+ if (expectedServiceAccount === null) {
758
+ expectedServiceAccount = `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`;
759
+ }
760
+ if (meta.email !== expectedServiceAccount) {
761
+ throw new Error(`Authorization header - Wrong service account, got ${meta?.email}, expected ${expectedServiceAccount}`);
762
+ }
763
+ if (!keyMeta.kid || !keyMeta.alg) {
764
+ throw new Error("Malformed Authorization Header. KID is missing");
765
+ }
766
+ const key = await this.getKey(keyMeta.kid);
767
+ if (!key) {
768
+ throw new Error("Incorrect Authorization Header. The key has not been found in the Google JWKS set");
769
+ }
770
+ const importedKey = await (0, import_jose.importJWK)(key, keyMeta.alg);
771
+ let validationCorrect = true;
772
+ try {
773
+ await (0, import_jose.jwtVerify)(authorizationHeader, importedKey, {
774
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
775
+ clockTolerance: 0,
776
+ audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
777
+ });
778
+ } catch (err) {
779
+ validationCorrect = false;
780
+ }
781
+ if (!validationCorrect) {
782
+ throw new Error("Incorrect Authorization Header. The JWT is not valid");
783
+ }
784
+ return validationCorrect;
785
+ }
786
+ async getKey(keyId) {
787
+ const jwks = typeof global.jwks !== "undefined" ? global.jwks : null;
788
+ let parsedJwks = null;
789
+ if (jwks) {
790
+ try {
791
+ parsedJwks = JSON.parse(jwks);
792
+ } catch (err) {
793
+ err.message.toString();
794
+ }
795
+ }
796
+ let keyData = parsedJwks?.keys?.find((x) => x.kid === keyId);
797
+ let cachedItem;
798
+ if (!keyData) {
799
+ cachedItem = await this.firestore.collection(CACHE_TABLE).doc("google-jwks").get();
800
+ if (cachedItem.exists && cachedItem.data()?.expires_at.toDate() > /* @__PURE__ */ new Date()) {
801
+ keyData = cachedItem.data()?.value;
802
+ }
803
+ }
804
+ keyData = parsedJwks?.keys.find((x) => x.kid === keyId);
805
+ if (!keyData) {
806
+ const result = await this.fetchJWKS();
807
+ parsedJwks = result.jwks;
808
+ if (cachedItem) {
809
+ cachedItem.ref.set({
810
+ value: parsedJwks,
811
+ expires_at: result.expiresAt
812
+ }, { merge: true });
813
+ }
814
+ keyData = parsedJwks?.keys.find((x) => x.kid === keyId);
815
+ }
816
+ if (parsedJwks && cachedItem) {
817
+ global.jwks = JSON.stringify(parsedJwks);
818
+ }
819
+ return keyData;
820
+ }
821
+ async fetchJWKS() {
822
+ const date = /* @__PURE__ */ new Date();
823
+ let expiresAt = new Date(Date.now() + 1e3 * 60 * 60);
824
+ const response = await fetch("https://www.googleapis.com/oauth2/v3/certs");
825
+ const maxAgePair = response.headers.get("Cache-Control")?.split(",").map((x) => x.trim().split("=")).find((x) => x[0] === "max-age");
826
+ if (maxAgePair && maxAgePair.length === 2) {
827
+ expiresAt = new Date(date.getTime() + parseInt(maxAgePair[1], 10) * 1e3);
828
+ }
829
+ const jwks = await response.json();
830
+ return {
831
+ expiresAt,
832
+ jwks
833
+ };
834
+ }
835
+ };
836
+ var jwks_default = JWKSHelper;
837
+
838
+ // src-public/utils.ts
839
+ var isPubSubRequest = (headers) => (headers["User-Agent"] || headers["user-agent"]) === "CloudPubSub-Google" || (headers["User-Agent"] || headers["user-agent"] || "").indexOf("APIs-Google") !== -1;
840
+ var isCronMessage = (headers) => (headers["User-Agent"] || headers["user-agent"]) === "Google-Cloud-Scheduler";
841
+ var validateGoogleAuth = async (opts, headers, firestore) => {
842
+ const token = headers.authorization?.split(" ")[1];
843
+ if (!token) {
844
+ throw new Error(
845
+ "Missing authorization header or invalid format, needs to be in format: Bearer <IdToken>"
846
+ );
847
+ }
848
+ try {
849
+ const jwksHelper = new jwks_default(opts, firestore);
850
+ await jwksHelper.validateAuthorizationHeader(token, opts.serviceAccount || null);
851
+ } catch (err) {
852
+ throw new Error("Invalid authorization header " + err.message);
853
+ }
854
+ };
339
855
  // Annotate the CommonJS export names for ESM import in node:
340
856
  0 && (module.exports = {
341
857
  lib,
package/dist/package.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "@hackylabs/deep-redact": "^2.2.1",
9
9
  "ajv": "^8.17.1",
10
10
  "ajv-formats": "^3.0.1",
11
+ "jose": "^6.0.11",
11
12
  "lodash.clonedeep": "^4.5.0"
12
13
  },
13
14
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shushed/helpers",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "author": "",
5
5
  "license": "UNLICENSED",
6
6
  "description": "",
@@ -8,6 +8,7 @@
8
8
  "@hackylabs/deep-redact": "^2.2.1",
9
9
  "ajv": "^8.17.1",
10
10
  "ajv-formats": "^3.0.1",
11
+ "jose": "^6.0.11",
11
12
  "lodash.clonedeep": "^4.5.0"
12
13
  },
13
14
  "files": [