@almadar/integrations 2.37.0 → 2.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { g as IntegrationParams, I as IntegrationCallContext, d as IntegrationFactory } from '../BaseIntegration-CWdas-N_.js';
2
- import { o as IntegrationName, m as IntegrationActionName, n as IntegrationContracts, e as CredentialStore } from '../store-RNvxZO-q.js';
2
+ import { o as IntegrationName, m as IntegrationActionName, n as IntegrationContracts, e as CredentialStore } from '../store-DG5OGiuQ.js';
3
3
  import '@almadar/core';
4
4
 
5
5
  /**
@@ -133,8 +133,8 @@ var serviceCredentials = {
133
133
  { envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
134
134
  ],
135
135
  oauth: [
136
- { envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
137
- { envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
136
+ { envVar: "OAUTH_CLIENT_ID", required: true, description: "OAuth/OIDC client ID" },
137
+ { envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OAuth/OIDC client secret" },
138
138
  { envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
139
139
  { envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
140
140
  ],
@@ -3547,6 +3547,31 @@ var PROVIDER_AUTH_URLS = {
3547
3547
  var PROVIDER_ISSUERS = {
3548
3548
  google: "https://accounts.google.com"
3549
3549
  };
3550
+ var PROVIDER_SERVER_METADATA = {
3551
+ github: {
3552
+ issuer: "https://github.com",
3553
+ authorization_endpoint: "https://github.com/login/oauth/authorize",
3554
+ token_endpoint: "https://github.com/login/oauth/access_token"
3555
+ }
3556
+ };
3557
+ var OAuthGrantNotFoundError = class extends Error {
3558
+ constructor(state) {
3559
+ super(`Invalid or expired state token: ${state}`);
3560
+ this.name = "OAuthGrantNotFoundError";
3561
+ }
3562
+ };
3563
+ var OAuthSubjectMismatchError = class extends Error {
3564
+ constructor() {
3565
+ super("OAuth completion refused: the completing subject is not the one that started the authorization");
3566
+ this.name = "OAuthSubjectMismatchError";
3567
+ }
3568
+ };
3569
+ function assertSubject(bound, completer) {
3570
+ if (bound !== completer) throw new OAuthSubjectMismatchError();
3571
+ }
3572
+ function grantedScopes(scope) {
3573
+ return (scope ?? "").split(/[\s,]+/).filter((s) => s.length > 0);
3574
+ }
3550
3575
  var PENDING_GRANT_TTL_MS = 10 * 60 * 1e3;
3551
3576
  var InMemoryPendingGrantStore = class {
3552
3577
  constructor() {
@@ -3571,7 +3596,7 @@ var InMemoryPendingGrantStore = class {
3571
3596
  var OAuthIntegration = class extends BaseIntegration {
3572
3597
  constructor(config) {
3573
3598
  super(config);
3574
- /** Maps state token -> provider for pending MOCK authorization flows */
3599
+ /** Maps state token -> pending MOCK authorization */
3575
3600
  this.states = /* @__PURE__ */ new Map();
3576
3601
  /** Maps access token -> token set (mock backend) */
3577
3602
  this.tokens = /* @__PURE__ */ new Map();
@@ -3600,12 +3625,25 @@ var OAuthIntegration = class extends BaseIntegration {
3600
3625
  try {
3601
3626
  let data;
3602
3627
  switch (action) {
3603
- case "authorize":
3604
- data = await this.executeWithRetry(() => this.real ? this.oidcAuthorize(params) : this.authorize(params));
3628
+ case "authorize": {
3629
+ const input = {
3630
+ provider: params.provider,
3631
+ scopes: params.scopes,
3632
+ redirectUri: params.redirectUri,
3633
+ ...params.subject === void 0 ? {} : { subject: params.subject }
3634
+ };
3635
+ data = await this.executeWithRetry(() => this.startAuthorization(input));
3605
3636
  break;
3606
- case "token":
3607
- data = await this.executeWithRetry(() => this.real ? this.oidcToken(params) : this.token(params));
3637
+ }
3638
+ case "token": {
3639
+ const input = {
3640
+ code: params.code,
3641
+ state: params.state,
3642
+ ...params.subject === void 0 ? {} : { subject: params.subject }
3643
+ };
3644
+ data = await this.executeWithRetry(() => this.completeAuthorization(input));
3608
3645
  break;
3646
+ }
3609
3647
  case "refresh":
3610
3648
  data = await this.executeWithRetry(() => this.real ? this.oidcRefresh(params) : this.refresh(params));
3611
3649
  break;
@@ -3627,6 +3665,14 @@ var OAuthIntegration = class extends BaseIntegration {
3627
3665
  return this.handleError(action, error);
3628
3666
  }
3629
3667
  }
3668
+ /** Build the provider authorization URL and hold the pending grant (PKCE verifier, subject) under `state`. */
3669
+ async startAuthorization(input) {
3670
+ return this.real ? this.oidcAuthorize(input) : this.authorize(input);
3671
+ }
3672
+ /** Consume the pending grant once and exchange the code; throws on an unknown, replayed or expired `state`. */
3673
+ async completeAuthorization(input) {
3674
+ return this.real ? this.oidcToken(input) : this.token(input);
3675
+ }
3630
3676
  // ---------------------------------------------------------------------------
3631
3677
  // OIDC backend (openid-client)
3632
3678
  // ---------------------------------------------------------------------------
@@ -3642,6 +3688,19 @@ var OAuthIntegration = class extends BaseIntegration {
3642
3688
  return issuer;
3643
3689
  }
3644
3690
  async configurationFor(provider) {
3691
+ const metadata = PROVIDER_SERVER_METADATA[provider];
3692
+ if (metadata && !this.config.env.OIDC_ISSUER_URL) {
3693
+ const cached2 = this.discovered.get(metadata.issuer);
3694
+ if (cached2) return cached2;
3695
+ const configuration2 = new oidc.Configuration(
3696
+ metadata,
3697
+ this.config.env.OAUTH_CLIENT_ID,
3698
+ this.config.env.OAUTH_CLIENT_SECRET,
3699
+ oidc.ClientSecretPost(this.config.env.OAUTH_CLIENT_SECRET)
3700
+ );
3701
+ this.discovered.set(metadata.issuer, configuration2);
3702
+ return configuration2;
3703
+ }
3645
3704
  const issuer = this.issuerFor(provider);
3646
3705
  const cached = this.discovered.get(issuer);
3647
3706
  if (cached) return cached;
@@ -3653,10 +3712,9 @@ var OAuthIntegration = class extends BaseIntegration {
3653
3712
  this.discovered.set(issuer, configuration);
3654
3713
  return configuration;
3655
3714
  }
3656
- async oidcAuthorize(params) {
3657
- const provider = params.provider;
3658
- const scopes = params.scopes;
3659
- const redirectUri = params.redirectUri || this.config.env.OAUTH_REDIRECT_URI;
3715
+ async oidcAuthorize(input) {
3716
+ const { provider, scopes, subject } = input;
3717
+ const redirectUri = input.redirectUri || this.config.env.OAUTH_REDIRECT_URI;
3660
3718
  const configuration = await this.configurationFor(provider);
3661
3719
  const state = oidc.randomState();
3662
3720
  const pkceVerifier = oidc.randomPKCECodeVerifier();
@@ -3673,17 +3731,21 @@ var OAuthIntegration = class extends BaseIntegration {
3673
3731
  parameters.prompt = "consent";
3674
3732
  }
3675
3733
  const authUrl = oidc.buildAuthorizationUrl(configuration, parameters);
3676
- await this.pendingStore().put(state, { provider, redirectUri, pkceVerifier }, PENDING_GRANT_TTL_MS);
3734
+ await this.pendingStore().put(
3735
+ state,
3736
+ { provider, redirectUri, pkceVerifier, ...subject === void 0 ? {} : { subject } },
3737
+ PENDING_GRANT_TTL_MS
3738
+ );
3677
3739
  void this.pendingStore().sweep();
3678
3740
  return { authUrl: authUrl.toString(), state };
3679
3741
  }
3680
- async oidcToken(params) {
3681
- const code = params.code;
3682
- const state = params.state;
3742
+ async oidcToken(input) {
3743
+ const { code, state } = input;
3683
3744
  const pendingAuth = await this.pendingStore().take(state);
3684
3745
  if (!pendingAuth) {
3685
- throw new Error(`Invalid or expired state token: ${state}`);
3746
+ throw new OAuthGrantNotFoundError(state);
3686
3747
  }
3748
+ assertSubject(pendingAuth.subject, input.subject);
3687
3749
  const configuration = await this.configurationFor(pendingAuth.provider);
3688
3750
  const callbackUrl = new URL(pendingAuth.redirectUri);
3689
3751
  callbackUrl.searchParams.set("code", code);
@@ -3700,7 +3762,9 @@ var OAuthIntegration = class extends BaseIntegration {
3700
3762
  accessToken: tokens.access_token,
3701
3763
  refreshToken: tokens.refresh_token ?? "",
3702
3764
  expiresIn: tokens.expires_in ?? 3600,
3703
- tokenType: "bearer"
3765
+ tokenType: "bearer",
3766
+ scope: grantedScopes(tokens.scope),
3767
+ ...pendingAuth.subject === void 0 ? {} : { subject: pendingAuth.subject }
3704
3768
  };
3705
3769
  }
3706
3770
  async oidcRefresh(params) {
@@ -3763,13 +3827,11 @@ var OAuthIntegration = class extends BaseIntegration {
3763
3827
  // ---------------------------------------------------------------------------
3764
3828
  // Mock backend actions
3765
3829
  // ---------------------------------------------------------------------------
3766
- async authorize(params) {
3767
- const provider = params.provider;
3768
- const scopes = params.scopes;
3769
- const redirectUri = params.redirectUri;
3830
+ async authorize(input) {
3831
+ const { provider, scopes, redirectUri, subject } = input;
3770
3832
  this.logger.debug("OAuth AUTHORIZE", { provider, scopes: scopes.join(", "), redirectUri });
3771
3833
  const state = this.generateToken(16);
3772
- this.states.set(state, provider);
3834
+ this.states.set(state, { provider, scopes, ...subject === void 0 ? {} : { subject } });
3773
3835
  const baseUrl = PROVIDER_AUTH_URLS[provider];
3774
3836
  const queryParams = new URLSearchParams({
3775
3837
  response_type: "code",
@@ -3781,17 +3843,15 @@ var OAuthIntegration = class extends BaseIntegration {
3781
3843
  const authUrl = `${baseUrl}?${queryParams.toString()}`;
3782
3844
  return { authUrl, state };
3783
3845
  }
3784
- async token(params) {
3785
- const code = params.code;
3786
- const state = params.state;
3846
+ async token(input) {
3847
+ const { code, state } = input;
3787
3848
  this.logger.debug("OAuth TOKEN", { code, state });
3788
- const provider = this.states.get(state);
3789
- if (!provider) {
3790
- throw new Error(
3791
- `Invalid or expired state token: ${state}`
3792
- );
3849
+ const pending = this.states.get(state);
3850
+ if (!pending) {
3851
+ throw new OAuthGrantNotFoundError(state);
3793
3852
  }
3794
3853
  this.states.delete(state);
3854
+ assertSubject(pending.subject, input.subject);
3795
3855
  const accessToken = this.generateToken(32);
3796
3856
  const refreshToken = this.generateToken(32);
3797
3857
  const expiresIn = 3600;
@@ -3803,8 +3863,15 @@ var OAuthIntegration = class extends BaseIntegration {
3803
3863
  this.tokens.set(accessToken, tokenSet);
3804
3864
  this.refreshIndex.set(refreshToken, accessToken);
3805
3865
  const sub = this.generateToken(8);
3806
- this.sessions.set(accessToken, this.generateMockUser(provider, sub));
3807
- return { accessToken, refreshToken, expiresIn, tokenType: "bearer" };
3866
+ this.sessions.set(accessToken, this.generateMockUser(pending.provider, sub));
3867
+ return {
3868
+ accessToken,
3869
+ refreshToken,
3870
+ expiresIn,
3871
+ tokenType: "bearer",
3872
+ scope: pending.scopes,
3873
+ ...pending.subject === void 0 ? {} : { subject: pending.subject }
3874
+ };
3808
3875
  }
3809
3876
  async refresh(params) {
3810
3877
  const refreshToken = params.refreshToken;