@almadar/integrations 2.25.0 → 2.27.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/{BaseIntegration-MA-b4fh8.d.ts → BaseIntegration-C_5q54DM.d.ts} +64 -3
- package/dist/index.d.ts +127 -19
- package/dist/index.js +505 -198
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/index.d.ts +1 -1
- package/dist/integrations/github/index.js +11 -0
- package/dist/integrations/github/index.js.map +1 -1
- package/dist/mocks/index.d.ts +1 -2
- package/dist/mocks/index.js +12 -1
- package/dist/mocks/index.js.map +1 -1
- package/dist/runtime/index.d.ts +14 -4
- package/dist/runtime/index.js +379 -193
- package/dist/runtime/index.js.map +1 -1
- package/dist/{store-CW1v7Apc.d.ts → store-tehIm2rt.d.ts} +68 -4
- package/package.json +2 -2
- package/dist/factory-BPVhvv5q.d.ts +0 -59
package/dist/runtime/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createLogger } from '@almadar/logger';
|
|
2
2
|
import { integratorsRegistry } from '@almadar/core/patterns';
|
|
3
3
|
import { createHmac, createHash } from 'crypto';
|
|
4
|
+
import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
4
6
|
import Stripe2 from 'stripe';
|
|
5
7
|
import { google } from 'googleapis';
|
|
6
8
|
import twilio from 'twilio';
|
|
@@ -11,8 +13,6 @@ import { Readable } from 'stream';
|
|
|
11
13
|
import { getAvailableProvider, LLMClient, EmbeddingClient } from '@almadar/llm';
|
|
12
14
|
import { z } from 'zod';
|
|
13
15
|
import { execSync, spawn } from 'child_process';
|
|
14
|
-
import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
|
|
15
|
-
import { join } from 'path';
|
|
16
16
|
import { tmpdir } from 'os';
|
|
17
17
|
import * as oidc from 'openid-client';
|
|
18
18
|
import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
@@ -39,6 +39,133 @@ var IntegrationError = class extends Error {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
|
|
43
|
+
// src/contracts.ts
|
|
44
|
+
var serviceCredentials = {
|
|
45
|
+
stripe: [
|
|
46
|
+
{ envVar: "STRIPE_SECRET_KEY", required: true, description: "Stripe secret API key" },
|
|
47
|
+
{ envVar: "STRIPE_WEBHOOK_SECRET", required: false, description: "Webhook endpoint signing secret" }
|
|
48
|
+
],
|
|
49
|
+
youtube: [
|
|
50
|
+
{ envVar: "YOUTUBE_API_KEY", required: true, description: "YouTube Data API key" }
|
|
51
|
+
],
|
|
52
|
+
twilio: [
|
|
53
|
+
{ envVar: "TWILIO_ACCOUNT_SID", required: true, description: "Twilio account SID" },
|
|
54
|
+
{ envVar: "TWILIO_AUTH_TOKEN", required: true, description: "Twilio auth token" },
|
|
55
|
+
{ envVar: "TWILIO_PHONE_NUMBER", required: false, description: "Default sender phone number" }
|
|
56
|
+
],
|
|
57
|
+
email: [
|
|
58
|
+
{ envVar: "SENDGRID_API_KEY", required: false, description: "SendGrid API key (use this OR RESEND_API_KEY)" },
|
|
59
|
+
{ envVar: "RESEND_API_KEY", required: false, description: "Resend API key (use this OR SENDGRID_API_KEY)" },
|
|
60
|
+
{ envVar: "FROM_EMAIL", required: false, description: "Default sender email address" }
|
|
61
|
+
],
|
|
62
|
+
webhook: [
|
|
63
|
+
{ envVar: "WEBHOOK_SIGNING_SECRET", required: false, description: "Default HMAC-SHA256 signing secret (per-call secret overrides)" },
|
|
64
|
+
{ envVar: "WEBHOOK_TIMEOUT_MS", required: false, description: "Per-request timeout (ms, default 10000)" }
|
|
65
|
+
],
|
|
66
|
+
push: [
|
|
67
|
+
{ envVar: "VAPID_PUBLIC_KEY", required: true, description: "VAPID public key (web-push generate-vapid-keys)" },
|
|
68
|
+
{ envVar: "VAPID_PRIVATE_KEY", required: true, description: "VAPID private key" },
|
|
69
|
+
{ envVar: "VAPID_SUBJECT", required: true, description: "VAPID subject (mailto: or https: contact URI)" }
|
|
70
|
+
],
|
|
71
|
+
calendar: [
|
|
72
|
+
{ envVar: "GOOGLE_CALENDAR_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with calendar scope; store in Secret Manager, bind as env" },
|
|
73
|
+
{ envVar: "GOOGLE_CALENDAR_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
|
|
74
|
+
{ envVar: "GOOGLE_CALENDAR_ID", required: false, description: "Default calendar id (default: primary)" },
|
|
75
|
+
{ envVar: "GOOGLE_CALENDAR_CHANNEL_TOKEN", required: false, description: "Watch-channel verification token \u2014 required to receive inbound calendar hooks (two-way sync)" }
|
|
76
|
+
],
|
|
77
|
+
drive: [
|
|
78
|
+
{ envVar: "GOOGLE_DRIVE_SA_KEY", required: false, description: "Google service-account key JSON (raw or base64) with drive scope \u2014 serves reads; store in Secret Manager, bind as env" },
|
|
79
|
+
{ envVar: "GOOGLE_DRIVE_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
|
|
80
|
+
{ envVar: "GOOGLE_DRIVE_REFRESH_TOKEN", required: false, description: "User OAuth refresh token (drive-consent.mjs) \u2014 serves writes; SA uploads are impossible on personal accounts (no SA storage quota)" },
|
|
81
|
+
{ envVar: "GOOGLE_DRIVE_FOLDER_ID", required: false, description: "Default parent folder for uploads/new folders when the call names none" }
|
|
82
|
+
],
|
|
83
|
+
metaAds: [
|
|
84
|
+
{ envVar: "META_ACCESS_TOKEN", required: true, description: "Meta Graph API access token (Marketing API, ads_read)" },
|
|
85
|
+
{ envVar: "META_AD_ACCOUNT_ID", required: false, description: "Default ad account id (act_\u2026); per-call accountId overrides" }
|
|
86
|
+
],
|
|
87
|
+
accounting: [],
|
|
88
|
+
banking: [
|
|
89
|
+
{ envVar: "GOCARDLESS_SECRET_ID", required: true, description: "GoCardless Bank Account Data secret id" },
|
|
90
|
+
{ envVar: "GOCARDLESS_SECRET_KEY", required: true, description: "GoCardless Bank Account Data secret key" }
|
|
91
|
+
],
|
|
92
|
+
esign: [
|
|
93
|
+
{ envVar: "DOCUSIGN_BASE_URL", required: true, description: "DocuSign REST base (e.g. https://demo.docusign.net/restapi/v2.1/accounts/<accountId>)" },
|
|
94
|
+
{ envVar: "DOCUSIGN_ACCESS_TOKEN", required: true, description: "DocuSign OAuth access token (JWT grant rotation is the deployment concern)" }
|
|
95
|
+
],
|
|
96
|
+
llm: [
|
|
97
|
+
{ envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key (use this OR OPENAI_API_KEY)" },
|
|
98
|
+
{ envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key (use this OR ANTHROPIC_API_KEY)" }
|
|
99
|
+
],
|
|
100
|
+
"llm-integration": [
|
|
101
|
+
{ envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key" },
|
|
102
|
+
{ envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key" }
|
|
103
|
+
],
|
|
104
|
+
ml: [
|
|
105
|
+
{ envVar: "MASAR_URL", required: true, description: "Base URL of the deployed model-serving orbital" },
|
|
106
|
+
{ envVar: "MASAR_ML_TRAIT", required: true, description: "Kebab-case trait name the serving orbital exposes its /events route under" }
|
|
107
|
+
],
|
|
108
|
+
deepagent: [
|
|
109
|
+
{ envVar: "DEEPAGENT_API_URL", required: true, description: "DeepAgent server URL" },
|
|
110
|
+
{ envVar: "DEEPAGENT_API_KEY", required: false, description: "DeepAgent API key" }
|
|
111
|
+
],
|
|
112
|
+
github: [
|
|
113
|
+
{ envVar: "GITHUB_TOKEN", required: true, description: "GitHub personal access token" },
|
|
114
|
+
{ envVar: "GITHUB_OWNER", required: false, description: "Default repository owner" },
|
|
115
|
+
{ envVar: "GITHUB_REPO", required: false, description: "Default repository name" }
|
|
116
|
+
],
|
|
117
|
+
docker: [
|
|
118
|
+
{ envVar: "DOCKER_HOST", required: false, description: "Docker daemon host URL" }
|
|
119
|
+
],
|
|
120
|
+
storage: [
|
|
121
|
+
{ envVar: "STORAGE_ACCESS_KEY_ID", required: true, description: "S3-compatible access key id" },
|
|
122
|
+
{ envVar: "STORAGE_SECRET_ACCESS_KEY", required: true, description: "S3-compatible secret access key" },
|
|
123
|
+
{ envVar: "STORAGE_BUCKET", required: true, description: "Default bucket name" },
|
|
124
|
+
{ envVar: "STORAGE_REGION", required: false, description: "Region (default us-east-1)" },
|
|
125
|
+
{ envVar: "STORAGE_ENDPOINT", required: false, description: "Custom S3-compatible endpoint (R2/MinIO/\u2026); empty = AWS S3" },
|
|
126
|
+
{ envVar: "STORAGE_PUBLIC_URL_BASE", required: false, description: "Base URL for public-acl object links; empty = endpoint-derived" }
|
|
127
|
+
],
|
|
128
|
+
queue: [
|
|
129
|
+
{ envVar: "QUEUE_URL", required: true, description: "Message queue connection URL" }
|
|
130
|
+
],
|
|
131
|
+
redis: [
|
|
132
|
+
{ envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
|
|
133
|
+
],
|
|
134
|
+
oauth: [
|
|
135
|
+
{ envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
|
|
136
|
+
{ envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
|
|
137
|
+
{ envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
|
|
138
|
+
{ envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
|
|
139
|
+
],
|
|
140
|
+
credentials: [
|
|
141
|
+
{ envVar: "ALMADAR_CREDENTIAL_MASTER_KEY", required: false, description: "AES-256 master key (64-char hex) enabling the hosted credential store \u2014 hold it alone in the platform secret store" },
|
|
142
|
+
{ envVar: "ALMADAR_CREDENTIAL_MASTER_KEY_PREVIOUS", required: false, description: "Previous master key, present only during a rotation window \u2014 decrypts old rows until `credentials.rotate` re-encrypts them" }
|
|
143
|
+
],
|
|
144
|
+
otel: [
|
|
145
|
+
{ envVar: "OTEL_EXPORTER_OTLP_ENDPOINT", required: true, description: "OpenTelemetry collector endpoint" },
|
|
146
|
+
{ envVar: "OTEL_SERVICE_NAME", required: false, description: "Service name for traces" }
|
|
147
|
+
],
|
|
148
|
+
cli: [],
|
|
149
|
+
// No fixed env vars — connection strings are resolved per-query from the
|
|
150
|
+
// caller-supplied connectionRef, so credentials cannot be declared statically.
|
|
151
|
+
database: [],
|
|
152
|
+
wikimedia: [
|
|
153
|
+
{ envVar: "WIKIMEDIA_USER_AGENT", required: false, description: "Descriptive User-Agent for the Wikipedia API" },
|
|
154
|
+
{ envVar: "WIKIMEDIA_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
|
|
155
|
+
],
|
|
156
|
+
iconify: [
|
|
157
|
+
{ envVar: "ICONIFY_USER_AGENT", required: false, description: "User-Agent for the Iconify API" },
|
|
158
|
+
{ envVar: "ICONIFY_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
|
|
159
|
+
],
|
|
160
|
+
arxiv: [
|
|
161
|
+
{ envVar: "ARXIV_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
var serviceProbes = {
|
|
165
|
+
calendar: { action: "listEvents", params: { maxResults: 1 } },
|
|
166
|
+
drive: { action: "listFiles", params: { maxResults: 1 } },
|
|
167
|
+
metaAds: { action: "listCampaigns", params: {} }
|
|
168
|
+
};
|
|
42
169
|
var ConsoleLogger = class {
|
|
43
170
|
constructor(_level = "info") {
|
|
44
171
|
this.log = createLogger("almadar:integrations");
|
|
@@ -56,6 +183,7 @@ var ConsoleLogger = class {
|
|
|
56
183
|
this.log.error(message, meta);
|
|
57
184
|
}
|
|
58
185
|
};
|
|
186
|
+
var RESERVED_ENVELOPE_KEYS = /* @__PURE__ */ new Set(["emit", "onSuccess", "onError", "timeout"]);
|
|
59
187
|
function validateParams(integration, action, params) {
|
|
60
188
|
const typedRegistry = integratorsRegistry;
|
|
61
189
|
const registry = typedRegistry.integrators[integration];
|
|
@@ -78,6 +206,16 @@ function validateParams(integration, action, params) {
|
|
|
78
206
|
};
|
|
79
207
|
}
|
|
80
208
|
const errors = [];
|
|
209
|
+
const declaredNames = new Set(actionDef.params.map((p) => p.name));
|
|
210
|
+
for (const key of Object.keys(params)) {
|
|
211
|
+
if (RESERVED_ENVELOPE_KEYS.has(key)) continue;
|
|
212
|
+
if (!declaredNames.has(key)) {
|
|
213
|
+
errors.push({
|
|
214
|
+
param: key,
|
|
215
|
+
message: `Unknown parameter: ${key} (declared: ${[...declaredNames].sort().join(", ")})`
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
81
219
|
for (const paramDef of actionDef.params) {
|
|
82
220
|
if (paramDef.required && !(paramDef.name in params)) {
|
|
83
221
|
errors.push({
|
|
@@ -267,7 +405,7 @@ var IntegrationFactory = class {
|
|
|
267
405
|
*/
|
|
268
406
|
async execute(integration, action, params, context) {
|
|
269
407
|
const instance = this.get(integration, context?.principal);
|
|
270
|
-
return await instance.execute(action, params);
|
|
408
|
+
return await instance.execute(action, params, context);
|
|
271
409
|
}
|
|
272
410
|
/**
|
|
273
411
|
* Check if integration is configured
|
|
@@ -330,6 +468,7 @@ function getInstalledCredentialStore() {
|
|
|
330
468
|
function resolveCredentialRef(ref, env = process.env) {
|
|
331
469
|
return installedStore?.resolve(ref) ?? env[ref];
|
|
332
470
|
}
|
|
471
|
+
join(".almadar", "dev-credentials.json");
|
|
333
472
|
|
|
334
473
|
// src/integrations/stripe/index.ts
|
|
335
474
|
var STRIPE_API_VERSION = "2025-02-24.acacia";
|
|
@@ -774,10 +913,10 @@ var TwilioIntegration = class extends BaseIntegration {
|
|
|
774
913
|
}
|
|
775
914
|
}
|
|
776
915
|
async sendSMS(params) {
|
|
777
|
-
const { to, body } = params;
|
|
916
|
+
const { to, body, from } = params;
|
|
778
917
|
this.logger.debug("Sending SMS", { to: String(to ?? "") });
|
|
779
918
|
const message = await this.client.messages.create({
|
|
780
|
-
from: this.phoneNumber,
|
|
919
|
+
from: from || this.phoneNumber,
|
|
781
920
|
to,
|
|
782
921
|
body
|
|
783
922
|
});
|
|
@@ -856,31 +995,35 @@ var EmailIntegration = class extends BaseIntegration {
|
|
|
856
995
|
}
|
|
857
996
|
}
|
|
858
997
|
async send(params) {
|
|
859
|
-
const { to, subject, body, from } = params;
|
|
998
|
+
const { to, subject, body, from, htmlBody, replyTo, templateId } = params;
|
|
860
999
|
this.logger.debug("Sending email", { to: String(to), subject: String(subject), provider: this.provider });
|
|
1000
|
+
const message = {
|
|
1001
|
+
to,
|
|
1002
|
+
subject,
|
|
1003
|
+
body,
|
|
1004
|
+
from: from || this.fromEmail,
|
|
1005
|
+
htmlBody: htmlBody || void 0,
|
|
1006
|
+
replyTo: replyTo || void 0,
|
|
1007
|
+
templateId: templateId || void 0
|
|
1008
|
+
};
|
|
861
1009
|
if (this.provider === "sendgrid") {
|
|
862
|
-
return await this.sendViaSendGrid(
|
|
863
|
-
to,
|
|
864
|
-
subject,
|
|
865
|
-
body,
|
|
866
|
-
from || this.fromEmail
|
|
867
|
-
);
|
|
1010
|
+
return await this.sendViaSendGrid(message);
|
|
868
1011
|
} else if (this.provider === "resend") {
|
|
869
|
-
return await this.sendViaResend(
|
|
870
|
-
to,
|
|
871
|
-
subject,
|
|
872
|
-
body,
|
|
873
|
-
from || this.fromEmail
|
|
874
|
-
);
|
|
1012
|
+
return await this.sendViaResend(message);
|
|
875
1013
|
}
|
|
876
1014
|
throw new Error(`Unknown email provider: ${this.provider}`);
|
|
877
1015
|
}
|
|
878
|
-
async sendViaSendGrid(
|
|
1016
|
+
async sendViaSendGrid(message) {
|
|
879
1017
|
const msg = {
|
|
880
|
-
to,
|
|
881
|
-
from,
|
|
882
|
-
subject,
|
|
883
|
-
|
|
1018
|
+
to: message.to,
|
|
1019
|
+
from: message.from,
|
|
1020
|
+
subject: message.subject,
|
|
1021
|
+
// htmlBody present → it carries the HTML and body becomes the
|
|
1022
|
+
// plain-text alternative; absent → body renders as HTML (legacy).
|
|
1023
|
+
html: message.htmlBody ?? message.body,
|
|
1024
|
+
...message.htmlBody ? { text: message.body } : {},
|
|
1025
|
+
...message.replyTo ? { replyTo: message.replyTo } : {},
|
|
1026
|
+
...message.templateId ? { templateId: message.templateId } : {}
|
|
884
1027
|
};
|
|
885
1028
|
const response = await sgMail.send(msg);
|
|
886
1029
|
return {
|
|
@@ -888,15 +1031,20 @@ var EmailIntegration = class extends BaseIntegration {
|
|
|
888
1031
|
status: "sent"
|
|
889
1032
|
};
|
|
890
1033
|
}
|
|
891
|
-
async sendViaResend(
|
|
1034
|
+
async sendViaResend(message) {
|
|
892
1035
|
if (!this.resendClient) {
|
|
893
1036
|
throw new Error("Resend client not initialized");
|
|
894
1037
|
}
|
|
1038
|
+
if (message.templateId) {
|
|
1039
|
+
throw new Error("templateId is not supported by the resend provider \u2014 use sendgrid or drop templateId");
|
|
1040
|
+
}
|
|
895
1041
|
const response = await this.resendClient.emails.send({
|
|
896
|
-
from,
|
|
897
|
-
to,
|
|
898
|
-
subject,
|
|
899
|
-
html: body
|
|
1042
|
+
from: message.from,
|
|
1043
|
+
to: message.to,
|
|
1044
|
+
subject: message.subject,
|
|
1045
|
+
html: message.htmlBody ?? message.body,
|
|
1046
|
+
...message.htmlBody ? { text: message.body } : {},
|
|
1047
|
+
...message.replyTo ? { replyTo: message.replyTo } : {}
|
|
900
1048
|
});
|
|
901
1049
|
return {
|
|
902
1050
|
id: response.data?.id,
|
|
@@ -1298,20 +1446,53 @@ var DriveIntegration = class extends BaseIntegration {
|
|
|
1298
1446
|
constructor(config) {
|
|
1299
1447
|
super(config);
|
|
1300
1448
|
const rawKey = config.env.GOOGLE_DRIVE_SA_KEY;
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1449
|
+
if (rawKey) {
|
|
1450
|
+
const keyJson = rawKey.trim().startsWith("{") ? rawKey : Buffer.from(rawKey, "base64").toString("utf8");
|
|
1451
|
+
const key = JSON.parse(keyJson);
|
|
1452
|
+
const subject = config.env.GOOGLE_DRIVE_SUBJECT || void 0;
|
|
1453
|
+
const auth = new google.auth.JWT({
|
|
1454
|
+
email: key.client_email,
|
|
1455
|
+
key: key.private_key,
|
|
1456
|
+
scopes: ["https://www.googleapis.com/auth/drive"],
|
|
1457
|
+
subject
|
|
1458
|
+
});
|
|
1459
|
+
this.saClient = google.drive({ version: "v3", auth });
|
|
1460
|
+
} else {
|
|
1461
|
+
this.saClient = null;
|
|
1462
|
+
}
|
|
1463
|
+
const refreshToken = config.env.GOOGLE_DRIVE_REFRESH_TOKEN;
|
|
1464
|
+
const clientId = config.env.OAUTH_CLIENT_ID;
|
|
1465
|
+
const clientSecret = config.env.OAUTH_CLIENT_SECRET;
|
|
1466
|
+
if (refreshToken && clientId && clientSecret) {
|
|
1467
|
+
const oauth2 = new google.auth.OAuth2(clientId, clientSecret);
|
|
1468
|
+
oauth2.setCredentials({ refresh_token: refreshToken });
|
|
1469
|
+
this.userClient = google.drive({ version: "v3", auth: oauth2 });
|
|
1470
|
+
} else {
|
|
1471
|
+
this.userClient = null;
|
|
1303
1472
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1473
|
+
if (!this.saClient && !this.userClient) {
|
|
1474
|
+
throw new Error(
|
|
1475
|
+
"Drive not configured \u2014 set GOOGLE_DRIVE_SA_KEY (reads) and/or GOOGLE_DRIVE_REFRESH_TOKEN + OAUTH_CLIENT_ID/SECRET (writes)"
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
this.defaultFolderId = config.env.GOOGLE_DRIVE_FOLDER_ID || void 0;
|
|
1479
|
+
this.logger.info("Drive integration initialized", {
|
|
1480
|
+
serviceAccount: this.saClient !== null,
|
|
1481
|
+
userToken: this.userClient !== null,
|
|
1482
|
+
delegated: Boolean(config.env.GOOGLE_DRIVE_SUBJECT)
|
|
1312
1483
|
});
|
|
1313
|
-
|
|
1314
|
-
|
|
1484
|
+
}
|
|
1485
|
+
/** Reads prefer the SA client (delegation-aware); user client covers its absence. */
|
|
1486
|
+
readClient() {
|
|
1487
|
+
const client = this.saClient ?? this.userClient;
|
|
1488
|
+
if (!client) throw new Error("Drive not configured");
|
|
1489
|
+
return client;
|
|
1490
|
+
}
|
|
1491
|
+
/** Writes REQUIRE the user client on personal accounts (SA has no storage quota); SA only as a Workspace fallback. */
|
|
1492
|
+
writeClient() {
|
|
1493
|
+
const client = this.userClient ?? this.saClient;
|
|
1494
|
+
if (!client) throw new Error("Drive not configured");
|
|
1495
|
+
return client;
|
|
1315
1496
|
}
|
|
1316
1497
|
async execute(action, params) {
|
|
1317
1498
|
const validation = this.validateParams(action, params);
|
|
@@ -1363,7 +1544,7 @@ var DriveIntegration = class extends BaseIntegration {
|
|
|
1363
1544
|
const clauses = ["trashed = false"];
|
|
1364
1545
|
if (folderId) clauses.push(`'${String(folderId).replace(/'/g, "\\'")}' in parents`);
|
|
1365
1546
|
if (query) clauses.push(String(query));
|
|
1366
|
-
const response = await this.
|
|
1547
|
+
const response = await this.readClient().files.list({
|
|
1367
1548
|
q: clauses.join(" and "),
|
|
1368
1549
|
pageSize: maxResults || 100,
|
|
1369
1550
|
fields: "files(id, name, mimeType, size, modifiedTime, webViewLink)"
|
|
@@ -1381,11 +1562,11 @@ var DriveIntegration = class extends BaseIntegration {
|
|
|
1381
1562
|
}
|
|
1382
1563
|
async getFile(params) {
|
|
1383
1564
|
const fileId = params.fileId;
|
|
1384
|
-
const meta = await this.
|
|
1565
|
+
const meta = await this.readClient().files.get({
|
|
1385
1566
|
fileId,
|
|
1386
1567
|
fields: "id, name, mimeType, size"
|
|
1387
1568
|
});
|
|
1388
|
-
const content = await this.
|
|
1569
|
+
const content = await this.readClient().files.get(
|
|
1389
1570
|
{ fileId, alt: "media" },
|
|
1390
1571
|
{ responseType: "arraybuffer" }
|
|
1391
1572
|
);
|
|
@@ -1401,10 +1582,11 @@ var DriveIntegration = class extends BaseIntegration {
|
|
|
1401
1582
|
async uploadFile(params) {
|
|
1402
1583
|
const { name, content, mimeType, folderId } = params;
|
|
1403
1584
|
const { bytes, contentType } = decodeContent(content);
|
|
1404
|
-
const
|
|
1585
|
+
const parent = folderId || this.defaultFolderId;
|
|
1586
|
+
const response = await this.writeClient().files.create({
|
|
1405
1587
|
requestBody: {
|
|
1406
1588
|
name,
|
|
1407
|
-
parents:
|
|
1589
|
+
parents: parent ? [parent] : void 0
|
|
1408
1590
|
},
|
|
1409
1591
|
media: {
|
|
1410
1592
|
mimeType: mimeType || contentType || "application/octet-stream",
|
|
@@ -1420,11 +1602,12 @@ var DriveIntegration = class extends BaseIntegration {
|
|
|
1420
1602
|
}
|
|
1421
1603
|
async createFolder(params) {
|
|
1422
1604
|
const { name, parentId } = params;
|
|
1423
|
-
const
|
|
1605
|
+
const parent = parentId || this.defaultFolderId;
|
|
1606
|
+
const response = await this.writeClient().files.create({
|
|
1424
1607
|
requestBody: {
|
|
1425
1608
|
name,
|
|
1426
1609
|
mimeType: "application/vnd.google-apps.folder",
|
|
1427
|
-
parents:
|
|
1610
|
+
parents: parent ? [parent] : void 0
|
|
1428
1611
|
},
|
|
1429
1612
|
fields: "id, name"
|
|
1430
1613
|
});
|
|
@@ -1432,7 +1615,7 @@ var DriveIntegration = class extends BaseIntegration {
|
|
|
1432
1615
|
}
|
|
1433
1616
|
async shareFile(params) {
|
|
1434
1617
|
const { fileId, email, role } = params;
|
|
1435
|
-
const response = await this.
|
|
1618
|
+
const response = await this.readClient().permissions.create({
|
|
1436
1619
|
fileId,
|
|
1437
1620
|
requestBody: {
|
|
1438
1621
|
type: "user",
|
|
@@ -3446,6 +3629,27 @@ var PROVIDER_AUTH_URLS = {
|
|
|
3446
3629
|
var PROVIDER_ISSUERS = {
|
|
3447
3630
|
google: "https://accounts.google.com"
|
|
3448
3631
|
};
|
|
3632
|
+
var PENDING_GRANT_TTL_MS = 10 * 60 * 1e3;
|
|
3633
|
+
var InMemoryPendingGrantStore = class {
|
|
3634
|
+
constructor() {
|
|
3635
|
+
this.grants = /* @__PURE__ */ new Map();
|
|
3636
|
+
}
|
|
3637
|
+
async put(state, grant, ttlMs) {
|
|
3638
|
+
this.grants.set(state, { grant, expiresAt: Date.now() + ttlMs });
|
|
3639
|
+
}
|
|
3640
|
+
async take(state) {
|
|
3641
|
+
const entry = this.grants.get(state);
|
|
3642
|
+
if (!entry) return null;
|
|
3643
|
+
this.grants.delete(state);
|
|
3644
|
+
return entry.expiresAt >= Date.now() ? entry.grant : null;
|
|
3645
|
+
}
|
|
3646
|
+
async sweep() {
|
|
3647
|
+
const now = Date.now();
|
|
3648
|
+
for (const [state, entry] of this.grants) {
|
|
3649
|
+
if (entry.expiresAt < now) this.grants.delete(state);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3449
3653
|
var OAuthIntegration = class extends BaseIntegration {
|
|
3450
3654
|
constructor(config) {
|
|
3451
3655
|
super(config);
|
|
@@ -3457,8 +3661,8 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3457
3661
|
this.refreshIndex = /* @__PURE__ */ new Map();
|
|
3458
3662
|
/** Maps access token -> mock user session */
|
|
3459
3663
|
this.sessions = /* @__PURE__ */ new Map();
|
|
3460
|
-
/**
|
|
3461
|
-
this.
|
|
3664
|
+
/** Pending OIDC authorizations (real backend) — injectable, in-memory default. */
|
|
3665
|
+
this.fallbackPending = new InMemoryPendingGrantStore();
|
|
3462
3666
|
/** Maps access token -> ID-token subject, for userinfo subject checks */
|
|
3463
3667
|
this.subjects = /* @__PURE__ */ new Map();
|
|
3464
3668
|
/** Discovered issuer configurations, keyed by issuer URL */
|
|
@@ -3468,6 +3672,9 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3468
3672
|
this.real ? "OAuth integration initialized (OIDC backend via openid-client)" : "OAuth integration initialized (mock backend)"
|
|
3469
3673
|
);
|
|
3470
3674
|
}
|
|
3675
|
+
pendingStore() {
|
|
3676
|
+
return this.fallbackPending;
|
|
3677
|
+
}
|
|
3471
3678
|
async execute(action, params) {
|
|
3472
3679
|
const validation = this.validateParams(action, params);
|
|
3473
3680
|
if (!validation.valid) {
|
|
@@ -3559,17 +3766,17 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3559
3766
|
parameters.prompt = "consent";
|
|
3560
3767
|
}
|
|
3561
3768
|
const authUrl = oidc.buildAuthorizationUrl(configuration, parameters);
|
|
3562
|
-
this.
|
|
3769
|
+
await this.pendingStore().put(state, { provider, redirectUri, pkceVerifier }, PENDING_GRANT_TTL_MS);
|
|
3770
|
+
void this.pendingStore().sweep();
|
|
3563
3771
|
return { authUrl: authUrl.toString(), state };
|
|
3564
3772
|
}
|
|
3565
3773
|
async oidcToken(params) {
|
|
3566
3774
|
const code = params.code;
|
|
3567
3775
|
const state = params.state;
|
|
3568
|
-
const pendingAuth = this.
|
|
3776
|
+
const pendingAuth = await this.pendingStore().take(state);
|
|
3569
3777
|
if (!pendingAuth) {
|
|
3570
3778
|
throw new Error(`Invalid or expired state token: ${state}`);
|
|
3571
3779
|
}
|
|
3572
|
-
this.pending.delete(state);
|
|
3573
3780
|
const configuration = await this.configurationFor(pendingAuth.provider);
|
|
3574
3781
|
const callbackUrl = new URL(pendingAuth.redirectUri);
|
|
3575
3782
|
callbackUrl.searchParams.set("code", code);
|
|
@@ -3756,131 +3963,10 @@ var OAuthIntegration = class extends BaseIntegration {
|
|
|
3756
3963
|
};
|
|
3757
3964
|
registerIntegration("oauth", OAuthIntegration);
|
|
3758
3965
|
|
|
3759
|
-
// src/contracts.ts
|
|
3760
|
-
var serviceCredentials = {
|
|
3761
|
-
stripe: [
|
|
3762
|
-
{ envVar: "STRIPE_SECRET_KEY", required: true, description: "Stripe secret API key" },
|
|
3763
|
-
{ envVar: "STRIPE_WEBHOOK_SECRET", required: false, description: "Webhook endpoint signing secret" }
|
|
3764
|
-
],
|
|
3765
|
-
youtube: [
|
|
3766
|
-
{ envVar: "YOUTUBE_API_KEY", required: true, description: "YouTube Data API key" }
|
|
3767
|
-
],
|
|
3768
|
-
twilio: [
|
|
3769
|
-
{ envVar: "TWILIO_ACCOUNT_SID", required: true, description: "Twilio account SID" },
|
|
3770
|
-
{ envVar: "TWILIO_AUTH_TOKEN", required: true, description: "Twilio auth token" },
|
|
3771
|
-
{ envVar: "TWILIO_PHONE_NUMBER", required: false, description: "Default sender phone number" }
|
|
3772
|
-
],
|
|
3773
|
-
email: [
|
|
3774
|
-
{ envVar: "SENDGRID_API_KEY", required: false, description: "SendGrid API key (use this OR RESEND_API_KEY)" },
|
|
3775
|
-
{ envVar: "RESEND_API_KEY", required: false, description: "Resend API key (use this OR SENDGRID_API_KEY)" },
|
|
3776
|
-
{ envVar: "FROM_EMAIL", required: false, description: "Default sender email address" }
|
|
3777
|
-
],
|
|
3778
|
-
webhook: [
|
|
3779
|
-
{ envVar: "WEBHOOK_SIGNING_SECRET", required: false, description: "Default HMAC-SHA256 signing secret (per-call secret overrides)" },
|
|
3780
|
-
{ envVar: "WEBHOOK_TIMEOUT_MS", required: false, description: "Per-request timeout (ms, default 10000)" }
|
|
3781
|
-
],
|
|
3782
|
-
push: [
|
|
3783
|
-
{ envVar: "VAPID_PUBLIC_KEY", required: true, description: "VAPID public key (web-push generate-vapid-keys)" },
|
|
3784
|
-
{ envVar: "VAPID_PRIVATE_KEY", required: true, description: "VAPID private key" },
|
|
3785
|
-
{ envVar: "VAPID_SUBJECT", required: true, description: "VAPID subject (mailto: or https: contact URI)" }
|
|
3786
|
-
],
|
|
3787
|
-
calendar: [
|
|
3788
|
-
{ envVar: "GOOGLE_CALENDAR_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with calendar scope; store in Secret Manager, bind as env" },
|
|
3789
|
-
{ envVar: "GOOGLE_CALENDAR_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
|
|
3790
|
-
{ envVar: "GOOGLE_CALENDAR_ID", required: false, description: "Default calendar id (default: primary)" }
|
|
3791
|
-
],
|
|
3792
|
-
drive: [
|
|
3793
|
-
{ envVar: "GOOGLE_DRIVE_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with drive scope; store in Secret Manager, bind as env" },
|
|
3794
|
-
{ envVar: "GOOGLE_DRIVE_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" }
|
|
3795
|
-
],
|
|
3796
|
-
metaAds: [
|
|
3797
|
-
{ envVar: "META_ACCESS_TOKEN", required: true, description: "Meta Graph API access token (Marketing API, ads_read)" },
|
|
3798
|
-
{ envVar: "META_AD_ACCOUNT_ID", required: false, description: "Default ad account id (act_\u2026); per-call accountId overrides" }
|
|
3799
|
-
],
|
|
3800
|
-
accounting: [],
|
|
3801
|
-
banking: [
|
|
3802
|
-
{ envVar: "GOCARDLESS_SECRET_ID", required: true, description: "GoCardless Bank Account Data secret id" },
|
|
3803
|
-
{ envVar: "GOCARDLESS_SECRET_KEY", required: true, description: "GoCardless Bank Account Data secret key" }
|
|
3804
|
-
],
|
|
3805
|
-
esign: [
|
|
3806
|
-
{ envVar: "DOCUSIGN_BASE_URL", required: true, description: "DocuSign REST base (e.g. https://demo.docusign.net/restapi/v2.1/accounts/<accountId>)" },
|
|
3807
|
-
{ envVar: "DOCUSIGN_ACCESS_TOKEN", required: true, description: "DocuSign OAuth access token (JWT grant rotation is the deployment concern)" }
|
|
3808
|
-
],
|
|
3809
|
-
llm: [
|
|
3810
|
-
{ envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key (use this OR OPENAI_API_KEY)" },
|
|
3811
|
-
{ envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key (use this OR ANTHROPIC_API_KEY)" }
|
|
3812
|
-
],
|
|
3813
|
-
"llm-integration": [
|
|
3814
|
-
{ envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key" },
|
|
3815
|
-
{ envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key" }
|
|
3816
|
-
],
|
|
3817
|
-
ml: [
|
|
3818
|
-
{ envVar: "MASAR_URL", required: true, description: "Base URL of the deployed model-serving orbital" },
|
|
3819
|
-
{ envVar: "MASAR_ML_TRAIT", required: true, description: "Kebab-case trait name the serving orbital exposes its /events route under" }
|
|
3820
|
-
],
|
|
3821
|
-
deepagent: [
|
|
3822
|
-
{ envVar: "DEEPAGENT_API_URL", required: true, description: "DeepAgent server URL" },
|
|
3823
|
-
{ envVar: "DEEPAGENT_API_KEY", required: false, description: "DeepAgent API key" }
|
|
3824
|
-
],
|
|
3825
|
-
github: [
|
|
3826
|
-
{ envVar: "GITHUB_TOKEN", required: true, description: "GitHub personal access token" },
|
|
3827
|
-
{ envVar: "GITHUB_OWNER", required: false, description: "Default repository owner" },
|
|
3828
|
-
{ envVar: "GITHUB_REPO", required: false, description: "Default repository name" }
|
|
3829
|
-
],
|
|
3830
|
-
docker: [
|
|
3831
|
-
{ envVar: "DOCKER_HOST", required: false, description: "Docker daemon host URL" }
|
|
3832
|
-
],
|
|
3833
|
-
storage: [
|
|
3834
|
-
{ envVar: "STORAGE_ACCESS_KEY_ID", required: true, description: "S3-compatible access key id" },
|
|
3835
|
-
{ envVar: "STORAGE_SECRET_ACCESS_KEY", required: true, description: "S3-compatible secret access key" },
|
|
3836
|
-
{ envVar: "STORAGE_BUCKET", required: true, description: "Default bucket name" },
|
|
3837
|
-
{ envVar: "STORAGE_REGION", required: false, description: "Region (default us-east-1)" },
|
|
3838
|
-
{ envVar: "STORAGE_ENDPOINT", required: false, description: "Custom S3-compatible endpoint (R2/MinIO/\u2026); empty = AWS S3" },
|
|
3839
|
-
{ envVar: "STORAGE_PUBLIC_URL_BASE", required: false, description: "Base URL for public-acl object links; empty = endpoint-derived" }
|
|
3840
|
-
],
|
|
3841
|
-
queue: [
|
|
3842
|
-
{ envVar: "QUEUE_URL", required: true, description: "Message queue connection URL" }
|
|
3843
|
-
],
|
|
3844
|
-
redis: [
|
|
3845
|
-
{ envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
|
|
3846
|
-
],
|
|
3847
|
-
oauth: [
|
|
3848
|
-
{ envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
|
|
3849
|
-
{ envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
|
|
3850
|
-
{ envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
|
|
3851
|
-
{ envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
|
|
3852
|
-
],
|
|
3853
|
-
credentials: [
|
|
3854
|
-
{ envVar: "ALMADAR_CREDENTIAL_MASTER_KEY", required: false, description: "AES-256 master key (64-char hex) enabling the hosted credential store \u2014 hold it alone in the platform secret store" }
|
|
3855
|
-
],
|
|
3856
|
-
otel: [
|
|
3857
|
-
{ envVar: "OTEL_EXPORTER_OTLP_ENDPOINT", required: true, description: "OpenTelemetry collector endpoint" },
|
|
3858
|
-
{ envVar: "OTEL_SERVICE_NAME", required: false, description: "Service name for traces" }
|
|
3859
|
-
],
|
|
3860
|
-
cli: [],
|
|
3861
|
-
// No fixed env vars — connection strings are resolved per-query from the
|
|
3862
|
-
// caller-supplied connectionRef, so credentials cannot be declared statically.
|
|
3863
|
-
database: [],
|
|
3864
|
-
wikimedia: [
|
|
3865
|
-
{ envVar: "WIKIMEDIA_USER_AGENT", required: false, description: "Descriptive User-Agent for the Wikipedia API" },
|
|
3866
|
-
{ envVar: "WIKIMEDIA_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
|
|
3867
|
-
],
|
|
3868
|
-
iconify: [
|
|
3869
|
-
{ envVar: "ICONIFY_USER_AGENT", required: false, description: "User-Agent for the Iconify API" },
|
|
3870
|
-
{ envVar: "ICONIFY_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
|
|
3871
|
-
],
|
|
3872
|
-
arxiv: [
|
|
3873
|
-
{ envVar: "ARXIV_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
|
|
3874
|
-
]
|
|
3875
|
-
};
|
|
3876
|
-
var serviceProbes = {
|
|
3877
|
-
calendar: { action: "listEvents", params: { maxResults: 1 } },
|
|
3878
|
-
drive: { action: "listFiles", params: { maxResults: 1 } },
|
|
3879
|
-
metaAds: { action: "listCampaigns", params: {} }
|
|
3880
|
-
};
|
|
3881
|
-
|
|
3882
3966
|
// src/integrations/credentials/index.ts
|
|
3883
3967
|
var ENV_VAR_NAME = /^[A-Z][A-Z0-9_]*$/;
|
|
3968
|
+
var ROLE_GATED_ACTIONS = /* @__PURE__ */ new Set(["set", "remove", "test", "rotate"]);
|
|
3969
|
+
var DEFAULT_ADMIN_ROLES = ["admin", "owner"];
|
|
3884
3970
|
function isProbeService(service) {
|
|
3885
3971
|
return service in serviceProbes;
|
|
3886
3972
|
}
|
|
@@ -3889,7 +3975,20 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
3889
3975
|
super(config);
|
|
3890
3976
|
this.logger.info("Credentials integration initialized (tenant credential store surface)");
|
|
3891
3977
|
}
|
|
3892
|
-
async execute(action, params) {
|
|
3978
|
+
async execute(action, params, context) {
|
|
3979
|
+
if (ROLE_GATED_ACTIONS.has(action)) {
|
|
3980
|
+
const allowed = this.adminRoles();
|
|
3981
|
+
if (!context?.role || !allowed.includes(context.role)) {
|
|
3982
|
+
return {
|
|
3983
|
+
success: false,
|
|
3984
|
+
error: new IntegrationError(
|
|
3985
|
+
`forbidden: "${action}" requires one of roles: ${allowed.join(", ")}`,
|
|
3986
|
+
"AUTH_ERROR"
|
|
3987
|
+
),
|
|
3988
|
+
metadata: this.createMetadata(action, 0)
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3893
3992
|
const validation = this.validateParams(action, params);
|
|
3894
3993
|
if (!validation.valid) {
|
|
3895
3994
|
return {
|
|
@@ -3917,8 +4016,16 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
3917
4016
|
data = await this.remove(params.service, params.envVar);
|
|
3918
4017
|
break;
|
|
3919
4018
|
case "test":
|
|
3920
|
-
data = await this.test(params.service);
|
|
4019
|
+
data = await this.test(params.service, context);
|
|
3921
4020
|
break;
|
|
4021
|
+
case "rotate": {
|
|
4022
|
+
const store = getInstalledCredentialStore();
|
|
4023
|
+
if (!store) {
|
|
4024
|
+
throw new Error("No credential store is installed on this host");
|
|
4025
|
+
}
|
|
4026
|
+
data = await store.rotate();
|
|
4027
|
+
break;
|
|
4028
|
+
}
|
|
3922
4029
|
default:
|
|
3923
4030
|
throw new Error(`Unknown action: ${action}`);
|
|
3924
4031
|
}
|
|
@@ -3931,6 +4038,11 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
3931
4038
|
return this.handleError(action, error);
|
|
3932
4039
|
}
|
|
3933
4040
|
}
|
|
4041
|
+
adminRoles() {
|
|
4042
|
+
const raw = this.config.env["ALMADAR_CREDENTIAL_ADMIN_ROLES"] || process.env["ALMADAR_CREDENTIAL_ADMIN_ROLES"] || "";
|
|
4043
|
+
const roles = raw.split(",").map((r) => r.trim()).filter((r) => r.length > 0);
|
|
4044
|
+
return roles.length > 0 ? roles : DEFAULT_ADMIN_ROLES;
|
|
4045
|
+
}
|
|
3934
4046
|
declaredFor(service) {
|
|
3935
4047
|
return serviceCredentials[service] ?? [];
|
|
3936
4048
|
}
|
|
@@ -3950,6 +4062,10 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
3950
4062
|
throw new Error(`"${envVar}" is not a declared credential of "${service}" (declared: ${valid})`);
|
|
3951
4063
|
}
|
|
3952
4064
|
}
|
|
4065
|
+
storeFirst() {
|
|
4066
|
+
const flag = this.config.env["ALMADAR_CREDENTIALS_SOURCE"] || process.env["ALMADAR_CREDENTIALS_SOURCE"];
|
|
4067
|
+
return flag === "store";
|
|
4068
|
+
}
|
|
3953
4069
|
list(serviceFilter) {
|
|
3954
4070
|
const store = getInstalledCredentialStore();
|
|
3955
4071
|
const stored = new Map((store?.entries() ?? []).map((e) => [`${e.service}\0${e.envVar}`, e]));
|
|
@@ -3959,7 +4075,7 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
3959
4075
|
for (const { envVar, required, description } of declared) {
|
|
3960
4076
|
const fromStore = stored.get(`${service}\0${envVar}`);
|
|
3961
4077
|
stored.delete(`${service}\0${envVar}`);
|
|
3962
|
-
const fromEnv = process.env[envVar];
|
|
4078
|
+
const fromEnv = this.storeFirst() ? void 0 : process.env[envVar];
|
|
3963
4079
|
const source = fromStore ? "store" : fromEnv ? "env" : "none";
|
|
3964
4080
|
entries.push({
|
|
3965
4081
|
service,
|
|
@@ -4003,7 +4119,7 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
4003
4119
|
this.assertSettable(service, envVar);
|
|
4004
4120
|
return { removed: await store.remove(envVar) };
|
|
4005
4121
|
}
|
|
4006
|
-
async test(service) {
|
|
4122
|
+
async test(service, context) {
|
|
4007
4123
|
const declared = this.declaredFor(service);
|
|
4008
4124
|
const missing = declared.filter((c) => c.required && !resolveCredentialRef(c.envVar)).map((c) => c.envVar);
|
|
4009
4125
|
const configured = missing.length === 0;
|
|
@@ -4018,7 +4134,7 @@ var CredentialsIntegration = class extends BaseIntegration {
|
|
|
4018
4134
|
if (!factory.isConfigured(service)) {
|
|
4019
4135
|
return { service, configured, missing, probed: false, ok: false, message: "Credentials present but the service is not configured on this host \u2014 restart or re-save a credential" };
|
|
4020
4136
|
}
|
|
4021
|
-
const result = await factory.execute(service, probe.action, probe.params);
|
|
4137
|
+
const result = await factory.execute(service, probe.action, probe.params, context);
|
|
4022
4138
|
if (!result.success) {
|
|
4023
4139
|
return { service, configured, missing, probed: true, ok: false, message: result.error?.message ?? `Probe ${probe.action} failed` };
|
|
4024
4140
|
}
|
|
@@ -4248,10 +4364,16 @@ var StorageIntegration = class extends BaseIntegration {
|
|
|
4248
4364
|
const bucket = this.bucketOf(params);
|
|
4249
4365
|
const prefix = params.prefix ?? "";
|
|
4250
4366
|
const maxKeys = params.maxKeys ?? 1e3;
|
|
4367
|
+
const continuationToken = params.continuationToken;
|
|
4251
4368
|
this.logger.debug("Storage LIST", { bucket, prefix, maxKeys });
|
|
4252
4369
|
if (this.s3) {
|
|
4253
4370
|
const response = await this.s3.send(
|
|
4254
|
-
new ListObjectsV2Command({
|
|
4371
|
+
new ListObjectsV2Command({
|
|
4372
|
+
Bucket: bucket,
|
|
4373
|
+
Prefix: prefix || void 0,
|
|
4374
|
+
MaxKeys: maxKeys,
|
|
4375
|
+
ContinuationToken: continuationToken
|
|
4376
|
+
})
|
|
4255
4377
|
);
|
|
4256
4378
|
return {
|
|
4257
4379
|
keys: (response.Contents ?? []).map((entry) => ({
|
|
@@ -4259,9 +4381,8 @@ var StorageIntegration = class extends BaseIntegration {
|
|
|
4259
4381
|
size: entry.Size ?? 0,
|
|
4260
4382
|
lastModified: entry.LastModified?.getTime() ?? 0
|
|
4261
4383
|
})),
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
truncated: Boolean(response.IsTruncated)
|
|
4384
|
+
truncated: Boolean(response.IsTruncated),
|
|
4385
|
+
...response.IsTruncated && response.NextContinuationToken !== void 0 ? { nextToken: response.NextContinuationToken } : {}
|
|
4265
4386
|
};
|
|
4266
4387
|
}
|
|
4267
4388
|
const bucketPrefix = `${bucket}/`;
|
|
@@ -4276,7 +4397,14 @@ var StorageIntegration = class extends BaseIntegration {
|
|
|
4276
4397
|
});
|
|
4277
4398
|
}
|
|
4278
4399
|
results.sort((a, b) => a.key.localeCompare(b.key));
|
|
4279
|
-
|
|
4400
|
+
const offset = continuationToken !== void 0 ? Number.parseInt(continuationToken, 10) || 0 : 0;
|
|
4401
|
+
const page = results.slice(offset, offset + maxKeys);
|
|
4402
|
+
const truncated = offset + maxKeys < results.length;
|
|
4403
|
+
return {
|
|
4404
|
+
keys: page,
|
|
4405
|
+
truncated,
|
|
4406
|
+
...truncated ? { nextToken: String(offset + maxKeys) } : {}
|
|
4407
|
+
};
|
|
4280
4408
|
}
|
|
4281
4409
|
async deleteObject(params) {
|
|
4282
4410
|
const bucket = this.bucketOf(params);
|
|
@@ -5128,6 +5256,19 @@ function createCallServiceHandler(factory) {
|
|
|
5128
5256
|
}
|
|
5129
5257
|
|
|
5130
5258
|
// src/runtime/RuntimeIntegrationManager.ts
|
|
5259
|
+
var STORE_BOOTSTRAP_ENV_VARS = /* @__PURE__ */ new Set([
|
|
5260
|
+
"ALMADAR_CREDENTIAL_MASTER_KEY",
|
|
5261
|
+
"ALMADAR_CREDENTIAL_MASTER_KEY_PREVIOUS"
|
|
5262
|
+
]);
|
|
5263
|
+
function declaredCredentialEnvVars() {
|
|
5264
|
+
const vars = /* @__PURE__ */ new Set();
|
|
5265
|
+
for (const decls of Object.values(serviceCredentials)) {
|
|
5266
|
+
for (const { envVar } of decls) {
|
|
5267
|
+
if (!STORE_BOOTSTRAP_ENV_VARS.has(envVar)) vars.add(envVar);
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
return vars;
|
|
5271
|
+
}
|
|
5131
5272
|
function generateMockFromShape(shape) {
|
|
5132
5273
|
const data = {};
|
|
5133
5274
|
for (const [key, type] of Object.entries(shape)) {
|
|
@@ -5157,6 +5298,7 @@ var RuntimeIntegrationManager = class {
|
|
|
5157
5298
|
constructor() {
|
|
5158
5299
|
this.credentialStore = null;
|
|
5159
5300
|
this.storeEnvBase = null;
|
|
5301
|
+
this.storeFirst = false;
|
|
5160
5302
|
this.factory = new IntegrationFactory();
|
|
5161
5303
|
this.installNotConfiguredFallback();
|
|
5162
5304
|
installActiveFactory(this.factory);
|
|
@@ -5172,16 +5314,51 @@ var RuntimeIntegrationManager = class {
|
|
|
5172
5314
|
async configureFromStore(store, envOverride) {
|
|
5173
5315
|
this.credentialStore = store;
|
|
5174
5316
|
this.storeEnvBase = envOverride ?? process.env;
|
|
5317
|
+
this.storeFirst = this.storeEnvBase.ALMADAR_CREDENTIALS_SOURCE === "store";
|
|
5175
5318
|
installCredentialStore(store);
|
|
5176
5319
|
await store.warm();
|
|
5320
|
+
if (this.storeFirst && store.enabled) {
|
|
5321
|
+
await this.seedStoreFromEnv(store, this.storeEnvBase);
|
|
5322
|
+
}
|
|
5177
5323
|
store.subscribe(() => this.refreshFromStore());
|
|
5178
5324
|
this.refreshFromStore();
|
|
5179
5325
|
}
|
|
5180
|
-
/**
|
|
5326
|
+
/** Whether store-first custody (I-31) is active on this manager. */
|
|
5327
|
+
get credentialsStoreFirst() {
|
|
5328
|
+
return this.storeFirst;
|
|
5329
|
+
}
|
|
5330
|
+
/** Copy each declared credential present in env but absent from the store. */
|
|
5331
|
+
async seedStoreFromEnv(store, env) {
|
|
5332
|
+
const present = new Set(store.entries().map((e) => e.envVar));
|
|
5333
|
+
for (const [service, decls] of Object.entries(serviceCredentials)) {
|
|
5334
|
+
for (const { envVar } of decls) {
|
|
5335
|
+
if (STORE_BOOTSTRAP_ENV_VARS.has(envVar)) continue;
|
|
5336
|
+
const value = env[envVar];
|
|
5337
|
+
if (value && !present.has(envVar)) {
|
|
5338
|
+
await store.set(service, envVar, value);
|
|
5339
|
+
present.add(envVar);
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
/**
|
|
5345
|
+
* Re-derive configs from base env + warmed store values. `reset()` (not
|
|
5346
|
+
* `invalidate()`): configs must go too, or a REMOVED credential's service
|
|
5347
|
+
* resurrects from its stale config on the next `get()` — removal must
|
|
5348
|
+
* revoke. `configureFromEnv` below rebuilds every configured service from
|
|
5349
|
+
* the merged env.
|
|
5350
|
+
*/
|
|
5181
5351
|
refreshFromStore() {
|
|
5182
5352
|
if (!this.credentialStore || !this.storeEnvBase) return;
|
|
5183
|
-
this.factory.
|
|
5184
|
-
|
|
5353
|
+
this.factory.reset();
|
|
5354
|
+
let base = this.storeEnvBase;
|
|
5355
|
+
if (this.storeFirst) {
|
|
5356
|
+
const declared = declaredCredentialEnvVars();
|
|
5357
|
+
base = Object.fromEntries(
|
|
5358
|
+
Object.entries(this.storeEnvBase).filter(([key]) => !declared.has(key))
|
|
5359
|
+
);
|
|
5360
|
+
}
|
|
5361
|
+
this.configureFromEnv({ ...base, ...this.credentialStore.snapshotEnv() });
|
|
5185
5362
|
}
|
|
5186
5363
|
/**
|
|
5187
5364
|
* Wrap `factory.execute` so an unknown/unconfigured service echoes its
|
|
@@ -5290,11 +5467,15 @@ var RuntimeIntegrationManager = class {
|
|
|
5290
5467
|
}
|
|
5291
5468
|
});
|
|
5292
5469
|
}
|
|
5293
|
-
if (env.GOOGLE_DRIVE_SA_KEY) {
|
|
5470
|
+
if (env.GOOGLE_DRIVE_SA_KEY || env.GOOGLE_DRIVE_REFRESH_TOKEN && env.OAUTH_CLIENT_ID && env.OAUTH_CLIENT_SECRET) {
|
|
5294
5471
|
this.factory.configure("drive", {
|
|
5295
5472
|
env: {
|
|
5296
|
-
GOOGLE_DRIVE_SA_KEY: env.GOOGLE_DRIVE_SA_KEY,
|
|
5297
|
-
GOOGLE_DRIVE_SUBJECT: env.GOOGLE_DRIVE_SUBJECT || ""
|
|
5473
|
+
GOOGLE_DRIVE_SA_KEY: env.GOOGLE_DRIVE_SA_KEY || "",
|
|
5474
|
+
GOOGLE_DRIVE_SUBJECT: env.GOOGLE_DRIVE_SUBJECT || "",
|
|
5475
|
+
GOOGLE_DRIVE_REFRESH_TOKEN: env.GOOGLE_DRIVE_REFRESH_TOKEN || "",
|
|
5476
|
+
GOOGLE_DRIVE_FOLDER_ID: env.GOOGLE_DRIVE_FOLDER_ID || "",
|
|
5477
|
+
OAUTH_CLIENT_ID: env.OAUTH_CLIENT_ID || "",
|
|
5478
|
+
OAUTH_CLIENT_SECRET: env.OAUTH_CLIENT_SECRET || ""
|
|
5298
5479
|
}
|
|
5299
5480
|
});
|
|
5300
5481
|
}
|
|
@@ -5334,7 +5515,12 @@ var RuntimeIntegrationManager = class {
|
|
|
5334
5515
|
}
|
|
5335
5516
|
});
|
|
5336
5517
|
}
|
|
5337
|
-
this.factory.configure("credentials", {
|
|
5518
|
+
this.factory.configure("credentials", {
|
|
5519
|
+
env: {
|
|
5520
|
+
ALMADAR_CREDENTIAL_ADMIN_ROLES: env.ALMADAR_CREDENTIAL_ADMIN_ROLES || "",
|
|
5521
|
+
ALMADAR_CREDENTIALS_SOURCE: env.ALMADAR_CREDENTIALS_SOURCE || ""
|
|
5522
|
+
}
|
|
5523
|
+
});
|
|
5338
5524
|
this.factory.configure("storage", {
|
|
5339
5525
|
env: {
|
|
5340
5526
|
STORAGE_ACCESS_KEY_ID: env.STORAGE_ACCESS_KEY_ID || "",
|