@almadar/integrations 2.38.0 → 2.40.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.
- package/dist/index.d.ts +51 -5
- package/dist/index.js +118 -39
- package/dist/index.js.map +1 -1
- package/dist/mocks/index.js +13 -6
- package/dist/mocks/index.js.map +1 -1
- package/dist/runtime/index.d.ts +10 -2
- package/dist/runtime/index.js +129 -41
- package/dist/runtime/index.js.map +1 -1
- package/dist/{store-RNvxZO-q.d.ts → store-DG5OGiuQ.d.ts} +8 -2
- package/package.json +3 -3
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
/**
|
|
@@ -50,7 +50,15 @@ declare class RuntimeIntegrationManager {
|
|
|
50
50
|
private credentialStore;
|
|
51
51
|
private storeEnvBase;
|
|
52
52
|
private storeFirst;
|
|
53
|
-
|
|
53
|
+
private readonly tenant;
|
|
54
|
+
/**
|
|
55
|
+
* `tenant`: one tenant of a multi-tenant host. Its services are configured from its credential
|
|
56
|
+
* store alone (never the host's env), and it installs nothing process-wide; resolve its
|
|
57
|
+
* references inside `runWithCredentialScope`.
|
|
58
|
+
*/
|
|
59
|
+
constructor(options?: {
|
|
60
|
+
tenant?: boolean;
|
|
61
|
+
});
|
|
54
62
|
/**
|
|
55
63
|
* W4: configure with the tenant credential store layered over the env —
|
|
56
64
|
* store → env → unconfigured. Installs the store as the process-wide
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createLogger } from '@almadar/logger';
|
|
2
2
|
import { integratorsRegistry } from '@almadar/core/patterns';
|
|
3
3
|
import { createHmac, createHash } from 'crypto';
|
|
4
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
5
|
import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
|
|
5
6
|
import { join } from 'path';
|
|
6
7
|
import Stripe2 from 'stripe';
|
|
@@ -133,8 +134,8 @@ var serviceCredentials = {
|
|
|
133
134
|
{ envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
|
|
134
135
|
],
|
|
135
136
|
oauth: [
|
|
136
|
-
{ envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
|
|
137
|
-
{ envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
|
|
137
|
+
{ envVar: "OAUTH_CLIENT_ID", required: true, description: "OAuth/OIDC client ID" },
|
|
138
|
+
{ envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OAuth/OIDC client secret" },
|
|
138
139
|
{ envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
|
|
139
140
|
{ envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
|
|
140
141
|
],
|
|
@@ -400,8 +401,7 @@ function getIntegration(name) {
|
|
|
400
401
|
function getRegisteredIntegrations() {
|
|
401
402
|
return Object.keys(INTEGRATION_REGISTRY);
|
|
402
403
|
}
|
|
403
|
-
|
|
404
|
-
// src/factory.ts
|
|
404
|
+
var callLog = createLogger("almadar:integrations:calls");
|
|
405
405
|
function instanceKey(name, principal) {
|
|
406
406
|
return principal ? `${name}\0${principal}` : name;
|
|
407
407
|
}
|
|
@@ -446,8 +446,16 @@ var IntegrationFactory = class {
|
|
|
446
446
|
* Execute an action on an integration
|
|
447
447
|
*/
|
|
448
448
|
async execute(integration, action, params, context) {
|
|
449
|
-
const
|
|
450
|
-
|
|
449
|
+
const start = performance.now();
|
|
450
|
+
const record = (ok, error) => callLog.call({ kind: "external", service: integration, op: action, durationMs: performance.now() - start, ok, ...error !== void 0 ? { error } : {} });
|
|
451
|
+
try {
|
|
452
|
+
const result = await this.get(integration, context?.principal).execute(action, params, context);
|
|
453
|
+
record(result.success, result.success ? void 0 : result.error?.message);
|
|
454
|
+
return result;
|
|
455
|
+
} catch (err) {
|
|
456
|
+
record(false, err instanceof Error ? err.message : String(err));
|
|
457
|
+
throw err;
|
|
458
|
+
}
|
|
451
459
|
}
|
|
452
460
|
/**
|
|
453
461
|
* Check if integration is configured
|
|
@@ -491,9 +499,8 @@ var IntegrationFactory = class {
|
|
|
491
499
|
this.configs.clear();
|
|
492
500
|
}
|
|
493
501
|
};
|
|
494
|
-
|
|
495
|
-
// src/credentials/resolver.ts
|
|
496
502
|
var installedStore = null;
|
|
503
|
+
var tenantScope = new AsyncLocalStorage();
|
|
497
504
|
var activeFactory = null;
|
|
498
505
|
function installActiveFactory(factory) {
|
|
499
506
|
activeFactory = factory;
|
|
@@ -508,6 +515,8 @@ function getInstalledCredentialStore() {
|
|
|
508
515
|
return installedStore;
|
|
509
516
|
}
|
|
510
517
|
function resolveCredentialRef(ref, env = process.env) {
|
|
518
|
+
const tenant = tenantScope.getStore();
|
|
519
|
+
if (tenant) return tenant.resolve(ref);
|
|
511
520
|
return installedStore?.resolve(ref) ?? env[ref];
|
|
512
521
|
}
|
|
513
522
|
join(".almadar", "dev-credentials.json");
|
|
@@ -3547,6 +3556,31 @@ var PROVIDER_AUTH_URLS = {
|
|
|
3547
3556
|
var PROVIDER_ISSUERS = {
|
|
3548
3557
|
google: "https://accounts.google.com"
|
|
3549
3558
|
};
|
|
3559
|
+
var PROVIDER_SERVER_METADATA = {
|
|
3560
|
+
github: {
|
|
3561
|
+
issuer: "https://github.com",
|
|
3562
|
+
authorization_endpoint: "https://github.com/login/oauth/authorize",
|
|
3563
|
+
token_endpoint: "https://github.com/login/oauth/access_token"
|
|
3564
|
+
}
|
|
3565
|
+
};
|
|
3566
|
+
var OAuthGrantNotFoundError = class extends Error {
|
|
3567
|
+
constructor(state) {
|
|
3568
|
+
super(`Invalid or expired state token: ${state}`);
|
|
3569
|
+
this.name = "OAuthGrantNotFoundError";
|
|
3570
|
+
}
|
|
3571
|
+
};
|
|
3572
|
+
var OAuthSubjectMismatchError = class extends Error {
|
|
3573
|
+
constructor() {
|
|
3574
|
+
super("OAuth completion refused: the completing subject is not the one that started the authorization");
|
|
3575
|
+
this.name = "OAuthSubjectMismatchError";
|
|
3576
|
+
}
|
|
3577
|
+
};
|
|
3578
|
+
function assertSubject(bound, completer) {
|
|
3579
|
+
if (bound !== completer) throw new OAuthSubjectMismatchError();
|
|
3580
|
+
}
|
|
3581
|
+
function grantedScopes(scope) {
|
|
3582
|
+
return (scope ?? "").split(/[\s,]+/).filter((s) => s.length > 0);
|
|
3583
|
+
}
|
|
3550
3584
|
var PENDING_GRANT_TTL_MS = 10 * 60 * 1e3;
|
|
3551
3585
|
var InMemoryPendingGrantStore = class {
|
|
3552
3586
|
constructor() {
|
|
@@ -3571,7 +3605,7 @@ var InMemoryPendingGrantStore = class {
|
|
|
3571
3605
|
var OAuthIntegration = class extends BaseIntegration {
|
|
3572
3606
|
constructor(config) {
|
|
3573
3607
|
super(config);
|
|
3574
|
-
/** Maps state token ->
|
|
3608
|
+
/** Maps state token -> pending MOCK authorization */
|
|
3575
3609
|
this.states = /* @__PURE__ */ new Map();
|
|
3576
3610
|
/** Maps access token -> token set (mock backend) */
|
|
3577
3611
|
this.tokens = /* @__PURE__ */ new Map();
|
|
@@ -3600,12 +3634,25 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3600
3634
|
try {
|
|
3601
3635
|
let data;
|
|
3602
3636
|
switch (action) {
|
|
3603
|
-
case "authorize":
|
|
3604
|
-
|
|
3637
|
+
case "authorize": {
|
|
3638
|
+
const input = {
|
|
3639
|
+
provider: params.provider,
|
|
3640
|
+
scopes: params.scopes,
|
|
3641
|
+
redirectUri: params.redirectUri,
|
|
3642
|
+
...params.subject === void 0 ? {} : { subject: params.subject }
|
|
3643
|
+
};
|
|
3644
|
+
data = await this.executeWithRetry(() => this.startAuthorization(input));
|
|
3605
3645
|
break;
|
|
3606
|
-
|
|
3607
|
-
|
|
3646
|
+
}
|
|
3647
|
+
case "token": {
|
|
3648
|
+
const input = {
|
|
3649
|
+
code: params.code,
|
|
3650
|
+
state: params.state,
|
|
3651
|
+
...params.subject === void 0 ? {} : { subject: params.subject }
|
|
3652
|
+
};
|
|
3653
|
+
data = await this.executeWithRetry(() => this.completeAuthorization(input));
|
|
3608
3654
|
break;
|
|
3655
|
+
}
|
|
3609
3656
|
case "refresh":
|
|
3610
3657
|
data = await this.executeWithRetry(() => this.real ? this.oidcRefresh(params) : this.refresh(params));
|
|
3611
3658
|
break;
|
|
@@ -3627,6 +3674,14 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3627
3674
|
return this.handleError(action, error);
|
|
3628
3675
|
}
|
|
3629
3676
|
}
|
|
3677
|
+
/** Build the provider authorization URL and hold the pending grant (PKCE verifier, subject) under `state`. */
|
|
3678
|
+
async startAuthorization(input) {
|
|
3679
|
+
return this.real ? this.oidcAuthorize(input) : this.authorize(input);
|
|
3680
|
+
}
|
|
3681
|
+
/** Consume the pending grant once and exchange the code; throws on an unknown, replayed or expired `state`. */
|
|
3682
|
+
async completeAuthorization(input) {
|
|
3683
|
+
return this.real ? this.oidcToken(input) : this.token(input);
|
|
3684
|
+
}
|
|
3630
3685
|
// ---------------------------------------------------------------------------
|
|
3631
3686
|
// OIDC backend (openid-client)
|
|
3632
3687
|
// ---------------------------------------------------------------------------
|
|
@@ -3642,6 +3697,19 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3642
3697
|
return issuer;
|
|
3643
3698
|
}
|
|
3644
3699
|
async configurationFor(provider) {
|
|
3700
|
+
const metadata = PROVIDER_SERVER_METADATA[provider];
|
|
3701
|
+
if (metadata && !this.config.env.OIDC_ISSUER_URL) {
|
|
3702
|
+
const cached2 = this.discovered.get(metadata.issuer);
|
|
3703
|
+
if (cached2) return cached2;
|
|
3704
|
+
const configuration2 = new oidc.Configuration(
|
|
3705
|
+
metadata,
|
|
3706
|
+
this.config.env.OAUTH_CLIENT_ID,
|
|
3707
|
+
this.config.env.OAUTH_CLIENT_SECRET,
|
|
3708
|
+
oidc.ClientSecretPost(this.config.env.OAUTH_CLIENT_SECRET)
|
|
3709
|
+
);
|
|
3710
|
+
this.discovered.set(metadata.issuer, configuration2);
|
|
3711
|
+
return configuration2;
|
|
3712
|
+
}
|
|
3645
3713
|
const issuer = this.issuerFor(provider);
|
|
3646
3714
|
const cached = this.discovered.get(issuer);
|
|
3647
3715
|
if (cached) return cached;
|
|
@@ -3653,10 +3721,9 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3653
3721
|
this.discovered.set(issuer, configuration);
|
|
3654
3722
|
return configuration;
|
|
3655
3723
|
}
|
|
3656
|
-
async oidcAuthorize(
|
|
3657
|
-
const provider =
|
|
3658
|
-
const
|
|
3659
|
-
const redirectUri = params.redirectUri || this.config.env.OAUTH_REDIRECT_URI;
|
|
3724
|
+
async oidcAuthorize(input) {
|
|
3725
|
+
const { provider, scopes, subject } = input;
|
|
3726
|
+
const redirectUri = input.redirectUri || this.config.env.OAUTH_REDIRECT_URI;
|
|
3660
3727
|
const configuration = await this.configurationFor(provider);
|
|
3661
3728
|
const state = oidc.randomState();
|
|
3662
3729
|
const pkceVerifier = oidc.randomPKCECodeVerifier();
|
|
@@ -3673,17 +3740,21 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3673
3740
|
parameters.prompt = "consent";
|
|
3674
3741
|
}
|
|
3675
3742
|
const authUrl = oidc.buildAuthorizationUrl(configuration, parameters);
|
|
3676
|
-
await this.pendingStore().put(
|
|
3743
|
+
await this.pendingStore().put(
|
|
3744
|
+
state,
|
|
3745
|
+
{ provider, redirectUri, pkceVerifier, ...subject === void 0 ? {} : { subject } },
|
|
3746
|
+
PENDING_GRANT_TTL_MS
|
|
3747
|
+
);
|
|
3677
3748
|
void this.pendingStore().sweep();
|
|
3678
3749
|
return { authUrl: authUrl.toString(), state };
|
|
3679
3750
|
}
|
|
3680
|
-
async oidcToken(
|
|
3681
|
-
const code =
|
|
3682
|
-
const state = params.state;
|
|
3751
|
+
async oidcToken(input) {
|
|
3752
|
+
const { code, state } = input;
|
|
3683
3753
|
const pendingAuth = await this.pendingStore().take(state);
|
|
3684
3754
|
if (!pendingAuth) {
|
|
3685
|
-
throw new
|
|
3755
|
+
throw new OAuthGrantNotFoundError(state);
|
|
3686
3756
|
}
|
|
3757
|
+
assertSubject(pendingAuth.subject, input.subject);
|
|
3687
3758
|
const configuration = await this.configurationFor(pendingAuth.provider);
|
|
3688
3759
|
const callbackUrl = new URL(pendingAuth.redirectUri);
|
|
3689
3760
|
callbackUrl.searchParams.set("code", code);
|
|
@@ -3700,7 +3771,9 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3700
3771
|
accessToken: tokens.access_token,
|
|
3701
3772
|
refreshToken: tokens.refresh_token ?? "",
|
|
3702
3773
|
expiresIn: tokens.expires_in ?? 3600,
|
|
3703
|
-
tokenType: "bearer"
|
|
3774
|
+
tokenType: "bearer",
|
|
3775
|
+
scope: grantedScopes(tokens.scope),
|
|
3776
|
+
...pendingAuth.subject === void 0 ? {} : { subject: pendingAuth.subject }
|
|
3704
3777
|
};
|
|
3705
3778
|
}
|
|
3706
3779
|
async oidcRefresh(params) {
|
|
@@ -3763,13 +3836,11 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3763
3836
|
// ---------------------------------------------------------------------------
|
|
3764
3837
|
// Mock backend actions
|
|
3765
3838
|
// ---------------------------------------------------------------------------
|
|
3766
|
-
async authorize(
|
|
3767
|
-
const provider =
|
|
3768
|
-
const scopes = params.scopes;
|
|
3769
|
-
const redirectUri = params.redirectUri;
|
|
3839
|
+
async authorize(input) {
|
|
3840
|
+
const { provider, scopes, redirectUri, subject } = input;
|
|
3770
3841
|
this.logger.debug("OAuth AUTHORIZE", { provider, scopes: scopes.join(", "), redirectUri });
|
|
3771
3842
|
const state = this.generateToken(16);
|
|
3772
|
-
this.states.set(state, provider);
|
|
3843
|
+
this.states.set(state, { provider, scopes, ...subject === void 0 ? {} : { subject } });
|
|
3773
3844
|
const baseUrl = PROVIDER_AUTH_URLS[provider];
|
|
3774
3845
|
const queryParams = new URLSearchParams({
|
|
3775
3846
|
response_type: "code",
|
|
@@ -3781,17 +3852,15 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3781
3852
|
const authUrl = `${baseUrl}?${queryParams.toString()}`;
|
|
3782
3853
|
return { authUrl, state };
|
|
3783
3854
|
}
|
|
3784
|
-
async token(
|
|
3785
|
-
const code =
|
|
3786
|
-
const state = params.state;
|
|
3855
|
+
async token(input) {
|
|
3856
|
+
const { code, state } = input;
|
|
3787
3857
|
this.logger.debug("OAuth TOKEN", { code, state });
|
|
3788
|
-
const
|
|
3789
|
-
if (!
|
|
3790
|
-
throw new
|
|
3791
|
-
`Invalid or expired state token: ${state}`
|
|
3792
|
-
);
|
|
3858
|
+
const pending = this.states.get(state);
|
|
3859
|
+
if (!pending) {
|
|
3860
|
+
throw new OAuthGrantNotFoundError(state);
|
|
3793
3861
|
}
|
|
3794
3862
|
this.states.delete(state);
|
|
3863
|
+
assertSubject(pending.subject, input.subject);
|
|
3795
3864
|
const accessToken = this.generateToken(32);
|
|
3796
3865
|
const refreshToken = this.generateToken(32);
|
|
3797
3866
|
const expiresIn = 3600;
|
|
@@ -3803,8 +3872,15 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3803
3872
|
this.tokens.set(accessToken, tokenSet);
|
|
3804
3873
|
this.refreshIndex.set(refreshToken, accessToken);
|
|
3805
3874
|
const sub = this.generateToken(8);
|
|
3806
|
-
this.sessions.set(accessToken, this.generateMockUser(provider, sub));
|
|
3807
|
-
return {
|
|
3875
|
+
this.sessions.set(accessToken, this.generateMockUser(pending.provider, sub));
|
|
3876
|
+
return {
|
|
3877
|
+
accessToken,
|
|
3878
|
+
refreshToken,
|
|
3879
|
+
expiresIn,
|
|
3880
|
+
tokenType: "bearer",
|
|
3881
|
+
scope: pending.scopes,
|
|
3882
|
+
...pending.subject === void 0 ? {} : { subject: pending.subject }
|
|
3883
|
+
};
|
|
3808
3884
|
}
|
|
3809
3885
|
async refresh(params) {
|
|
3810
3886
|
const refreshToken = params.refreshToken;
|
|
@@ -5370,12 +5446,18 @@ function generateMockFromShape(shape) {
|
|
|
5370
5446
|
return data;
|
|
5371
5447
|
}
|
|
5372
5448
|
var RuntimeIntegrationManager = class {
|
|
5373
|
-
|
|
5449
|
+
/**
|
|
5450
|
+
* `tenant`: one tenant of a multi-tenant host. Its services are configured from its credential
|
|
5451
|
+
* store alone (never the host's env), and it installs nothing process-wide; resolve its
|
|
5452
|
+
* references inside `runWithCredentialScope`.
|
|
5453
|
+
*/
|
|
5454
|
+
constructor(options = {}) {
|
|
5374
5455
|
this.credentialStore = null;
|
|
5375
5456
|
this.storeEnvBase = null;
|
|
5376
5457
|
this.storeFirst = false;
|
|
5377
5458
|
this.factory = new IntegrationFactory();
|
|
5378
|
-
|
|
5459
|
+
this.tenant = options.tenant === true;
|
|
5460
|
+
if (!this.tenant) installActiveFactory(this.factory);
|
|
5379
5461
|
}
|
|
5380
5462
|
/**
|
|
5381
5463
|
* W4: configure with the tenant credential store layered over the env —
|
|
@@ -5387,7 +5469,13 @@ var RuntimeIntegrationManager = class {
|
|
|
5387
5469
|
*/
|
|
5388
5470
|
async configureFromStore(store, envOverride) {
|
|
5389
5471
|
this.credentialStore = store;
|
|
5390
|
-
this.storeEnvBase = envOverride ?? process.env;
|
|
5472
|
+
this.storeEnvBase = this.tenant ? {} : envOverride ?? process.env;
|
|
5473
|
+
if (this.tenant) {
|
|
5474
|
+
await store.warm();
|
|
5475
|
+
store.subscribe(() => this.refreshFromStore());
|
|
5476
|
+
this.refreshFromStore();
|
|
5477
|
+
return;
|
|
5478
|
+
}
|
|
5391
5479
|
this.storeFirst = this.storeEnvBase.ALMADAR_CREDENTIALS_SOURCE === "store";
|
|
5392
5480
|
installCredentialStore(store);
|
|
5393
5481
|
await store.warm();
|