@agenticmail/enterprise 0.5.52 → 0.5.53
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/chunk-4ZVC4XJY.js +3563 -0
- package/dist/chunk-G54QH5PZ.js +9085 -0
- package/dist/chunk-GI5RMYH6.js +37 -0
- package/dist/chunk-HAN6MNXJ.js +374 -0
- package/dist/chunk-L447KLHG.js +13099 -0
- package/dist/chunk-ORN3ILZD.js +48 -0
- package/dist/chunk-QIBEF5G3.js +898 -0
- package/dist/chunk-WA6WJN3D.js +2115 -0
- package/dist/cidr-MQSAKEA6.js +17 -0
- package/dist/cli-build-skill-ZD5FURGY.js +235 -0
- package/dist/cli-recover-XKHGIGGR.js +97 -0
- package/dist/cli-submit-skill-R7HUAMYR.js +162 -0
- package/dist/cli-validate-BDWKT6KH.js +148 -0
- package/dist/cli-verify-RTYVUWD7.js +98 -0
- package/dist/config-store-44Y6KOVX.js +58 -0
- package/dist/db-adapter-FJZ5BESQ.js +7 -0
- package/dist/domain-lock-7EMDJXFQ.js +7 -0
- package/dist/dynamodb-CDDPVEXF.js +424 -0
- package/dist/factory-2SRP3B3U.js +9 -0
- package/dist/firewall-RTIDM4NY.js +10 -0
- package/dist/imap-flow-QTNT4Y75.js +44953 -0
- package/dist/index.js +322 -1
- package/dist/managed-2CZ3CSGR.js +17 -0
- package/dist/mongodb-UEHZUXYD.js +320 -0
- package/dist/mysql-4BBS773W.js +575 -0
- package/dist/nodemailer-JCKCTRMI.js +11711 -0
- package/dist/postgres-BCVODZLS.js +597 -0
- package/dist/providers-VPFJNW4H.js +17 -0
- package/dist/resolve-driver-ZLJYEK72.js +27 -0
- package/dist/routes-M5J73UQY.js +5783 -0
- package/dist/runtime-KQFFAIFR.js +47 -0
- package/dist/server-4FHO44MK.js +12 -0
- package/dist/setup-ZWEA6MUM.js +20 -0
- package/dist/skills-WMCZVXBK.js +14 -0
- package/dist/sqlite-K6HN7XRU.js +491 -0
- package/dist/turso-BBUTZACL.js +496 -0
- package/package.json +2 -2
- package/src/agenticmail/index.ts +2 -0
- package/src/agenticmail/providers/imap.ts +454 -0
- package/src/agenticmail/providers/index.ts +4 -2
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DomainLock
|
|
3
|
+
} from "./chunk-UPU23ZRG.js";
|
|
4
|
+
import "./chunk-GI5RMYH6.js";
|
|
5
|
+
|
|
6
|
+
// src/domain-lock/cli-verify.ts
|
|
7
|
+
function getFlag(args, name) {
|
|
8
|
+
const idx = args.indexOf(name);
|
|
9
|
+
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
async function runVerifyDomain(args) {
|
|
13
|
+
const { default: chalk } = await import("chalk");
|
|
14
|
+
const { default: ora } = await import("ora");
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log(chalk.bold(" AgenticMail Enterprise \u2014 Domain Verification"));
|
|
17
|
+
console.log("");
|
|
18
|
+
let domain = getFlag(args, "--domain");
|
|
19
|
+
let dnsChallenge;
|
|
20
|
+
let dbConnected = false;
|
|
21
|
+
let db = null;
|
|
22
|
+
if (!domain) {
|
|
23
|
+
const dbPath = getFlag(args, "--db");
|
|
24
|
+
const dbType = getFlag(args, "--db-type") || "sqlite";
|
|
25
|
+
if (dbPath) {
|
|
26
|
+
try {
|
|
27
|
+
const { createAdapter } = await import("./factory-2SRP3B3U.js");
|
|
28
|
+
db = await createAdapter({ type: dbType, connectionString: dbPath });
|
|
29
|
+
await db.migrate();
|
|
30
|
+
const settings = await db.getSettings();
|
|
31
|
+
domain = settings?.domain;
|
|
32
|
+
dnsChallenge = settings?.domainDnsChallenge;
|
|
33
|
+
dbConnected = true;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!domain) {
|
|
39
|
+
const { default: inquirer } = await import("inquirer");
|
|
40
|
+
const answer = await inquirer.prompt([{
|
|
41
|
+
type: "input",
|
|
42
|
+
name: "domain",
|
|
43
|
+
message: "Domain to verify:",
|
|
44
|
+
suffix: chalk.dim(" (e.g. agents.agenticmail.io)"),
|
|
45
|
+
validate: (v) => v.includes(".") || "Enter a valid domain"
|
|
46
|
+
}]);
|
|
47
|
+
domain = answer.domain;
|
|
48
|
+
}
|
|
49
|
+
const spinner = ora("Checking DNS verification...").start();
|
|
50
|
+
const lock = new DomainLock();
|
|
51
|
+
const result = await lock.checkVerification(domain);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
spinner.fail("Verification check failed");
|
|
54
|
+
console.log("");
|
|
55
|
+
console.error(chalk.red(` ${result.error}`));
|
|
56
|
+
console.log("");
|
|
57
|
+
if (db) await db.disconnect();
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (result.verified) {
|
|
61
|
+
spinner.succeed("Domain verified!");
|
|
62
|
+
if (dbConnected && db) {
|
|
63
|
+
try {
|
|
64
|
+
await db.updateSettings({
|
|
65
|
+
domainStatus: "verified",
|
|
66
|
+
domainVerifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log(chalk.green.bold(` ${domain} is verified and protected.`));
|
|
73
|
+
console.log(chalk.dim(" Your deployment domain is locked. No other instance can claim it."));
|
|
74
|
+
console.log("");
|
|
75
|
+
} else {
|
|
76
|
+
spinner.info("DNS record not found yet");
|
|
77
|
+
console.log("");
|
|
78
|
+
console.log(chalk.yellow(" The DNS TXT record has not been detected."));
|
|
79
|
+
console.log("");
|
|
80
|
+
console.log(chalk.bold(" Make sure this record exists:"));
|
|
81
|
+
console.log("");
|
|
82
|
+
console.log(` ${chalk.bold("Host:")} ${chalk.cyan(`_agenticmail-verify.${domain}`)}`);
|
|
83
|
+
console.log(` ${chalk.bold("Type:")} ${chalk.cyan("TXT")}`);
|
|
84
|
+
if (dnsChallenge) {
|
|
85
|
+
console.log(` ${chalk.bold("Value:")} ${chalk.cyan(dnsChallenge)}`);
|
|
86
|
+
} else {
|
|
87
|
+
console.log(` ${chalk.bold("Value:")} ${chalk.dim("(check your setup records or dashboard)")}`);
|
|
88
|
+
}
|
|
89
|
+
console.log("");
|
|
90
|
+
console.log(chalk.dim(" DNS changes can take up to 48 hours to propagate."));
|
|
91
|
+
console.log(chalk.dim(" Run this command again after adding the record."));
|
|
92
|
+
console.log("");
|
|
93
|
+
}
|
|
94
|
+
if (db) await db.disconnect();
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
runVerifyDomain
|
|
98
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import "./chunk-GI5RMYH6.js";
|
|
2
|
+
|
|
3
|
+
// src/lib/config-store.ts
|
|
4
|
+
import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var CONFIG_FILE = ".agenticmail.enc";
|
|
8
|
+
var ALGORITHM = "aes-256-gcm";
|
|
9
|
+
var SCRYPT_KEYLEN = 32;
|
|
10
|
+
var SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
|
|
11
|
+
function deriveKey(secret, salt) {
|
|
12
|
+
return scryptSync(secret, salt, SCRYPT_KEYLEN, SCRYPT_PARAMS);
|
|
13
|
+
}
|
|
14
|
+
function getConfigPath() {
|
|
15
|
+
return join(process.cwd(), CONFIG_FILE);
|
|
16
|
+
}
|
|
17
|
+
async function saveDbConfig(config, secret) {
|
|
18
|
+
const salt = randomBytes(16);
|
|
19
|
+
const key = deriveKey(secret, salt);
|
|
20
|
+
const iv = randomBytes(12);
|
|
21
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
22
|
+
const plaintext = JSON.stringify(config);
|
|
23
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
24
|
+
const tag = cipher.getAuthTag();
|
|
25
|
+
const payload = {
|
|
26
|
+
iv: iv.toString("base64"),
|
|
27
|
+
tag: tag.toString("base64"),
|
|
28
|
+
salt: salt.toString("base64"),
|
|
29
|
+
data: encrypted.toString("base64")
|
|
30
|
+
};
|
|
31
|
+
writeFileSync(getConfigPath(), JSON.stringify(payload, null, 2), "utf-8");
|
|
32
|
+
}
|
|
33
|
+
async function loadDbConfig(secret) {
|
|
34
|
+
const path = getConfigPath();
|
|
35
|
+
if (!existsSync(path)) return null;
|
|
36
|
+
try {
|
|
37
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
38
|
+
const salt = Buffer.from(raw.salt, "base64");
|
|
39
|
+
const iv = Buffer.from(raw.iv, "base64");
|
|
40
|
+
const tag = Buffer.from(raw.tag, "base64");
|
|
41
|
+
const data = Buffer.from(raw.data, "base64");
|
|
42
|
+
const key = deriveKey(secret, salt);
|
|
43
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
44
|
+
decipher.setAuthTag(tag);
|
|
45
|
+
const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
|
|
46
|
+
return JSON.parse(decrypted.toString("utf-8"));
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function configFileExists() {
|
|
52
|
+
return existsSync(getConfigPath());
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
configFileExists,
|
|
56
|
+
loadDbConfig,
|
|
57
|
+
saveDbConfig
|
|
58
|
+
};
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatabaseAdapter
|
|
3
|
+
} from "./chunk-FLRYMSKY.js";
|
|
4
|
+
import "./chunk-GI5RMYH6.js";
|
|
5
|
+
|
|
6
|
+
// src/db/dynamodb.ts
|
|
7
|
+
import { randomUUID, createHash } from "crypto";
|
|
8
|
+
var ddbLib;
|
|
9
|
+
var ddbDocLib;
|
|
10
|
+
async function getDdb() {
|
|
11
|
+
if (!ddbLib) {
|
|
12
|
+
const { resolveDriver } = await import("./resolve-driver-ZLJYEK72.js");
|
|
13
|
+
const errMsg = "DynamoDB drivers not found. Install: npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb";
|
|
14
|
+
ddbLib = await resolveDriver("@aws-sdk/client-dynamodb", errMsg);
|
|
15
|
+
ddbDocLib = await resolveDriver("@aws-sdk/lib-dynamodb", errMsg);
|
|
16
|
+
}
|
|
17
|
+
return { ddbLib, ddbDocLib };
|
|
18
|
+
}
|
|
19
|
+
var TABLE = "agenticmail_enterprise";
|
|
20
|
+
function pk(type) {
|
|
21
|
+
return `${type}`;
|
|
22
|
+
}
|
|
23
|
+
var DynamoAdapter = class extends DatabaseAdapter {
|
|
24
|
+
type = "dynamodb";
|
|
25
|
+
client = null;
|
|
26
|
+
docClient = null;
|
|
27
|
+
tableName = TABLE;
|
|
28
|
+
async connect(config) {
|
|
29
|
+
const { ddbLib: ddbLib2, ddbDocLib: ddbDocLib2 } = await getDdb();
|
|
30
|
+
const opts = {};
|
|
31
|
+
if (config.region) opts.region = config.region;
|
|
32
|
+
if (config.accessKeyId && config.secretAccessKey) {
|
|
33
|
+
opts.credentials = { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey };
|
|
34
|
+
}
|
|
35
|
+
if (config.connectionString) {
|
|
36
|
+
opts.endpoint = config.connectionString;
|
|
37
|
+
}
|
|
38
|
+
if (config.options?.tableName) this.tableName = config.options.tableName;
|
|
39
|
+
this.client = new ddbLib2.DynamoDBClient(opts);
|
|
40
|
+
this.docClient = ddbDocLib2.DynamoDBDocumentClient.from(this.client);
|
|
41
|
+
}
|
|
42
|
+
async disconnect() {
|
|
43
|
+
if (this.client) this.client.destroy();
|
|
44
|
+
}
|
|
45
|
+
isConnected() {
|
|
46
|
+
return this.client !== null;
|
|
47
|
+
}
|
|
48
|
+
async put(item) {
|
|
49
|
+
const { ddbDocLib: ddbDocLib2 } = await getDdb();
|
|
50
|
+
await this.docClient.send(new ddbDocLib2.PutCommand({ TableName: this.tableName, Item: item }));
|
|
51
|
+
}
|
|
52
|
+
async getItem(pkVal, skVal) {
|
|
53
|
+
const { ddbDocLib: ddbDocLib2 } = await getDdb();
|
|
54
|
+
const result = await this.docClient.send(new ddbDocLib2.GetCommand({
|
|
55
|
+
TableName: this.tableName,
|
|
56
|
+
Key: { PK: pkVal, SK: skVal }
|
|
57
|
+
}));
|
|
58
|
+
return result.Item || null;
|
|
59
|
+
}
|
|
60
|
+
async query(pkVal, opts) {
|
|
61
|
+
const { ddbDocLib: ddbDocLib2 } = await getDdb();
|
|
62
|
+
const params = {
|
|
63
|
+
TableName: this.tableName,
|
|
64
|
+
KeyConditionExpression: "#pk = :pk",
|
|
65
|
+
ExpressionAttributeNames: { "#pk": opts?.pkField || "PK" },
|
|
66
|
+
ExpressionAttributeValues: { ":pk": pkVal }
|
|
67
|
+
};
|
|
68
|
+
if (opts?.sk?.begins) {
|
|
69
|
+
params.KeyConditionExpression += " AND begins_with(#sk, :skPrefix)";
|
|
70
|
+
params.ExpressionAttributeNames["#sk"] = "SK";
|
|
71
|
+
params.ExpressionAttributeValues[":skPrefix"] = opts.sk.begins;
|
|
72
|
+
}
|
|
73
|
+
if (opts?.index) params.IndexName = opts.index;
|
|
74
|
+
if (opts?.limit) params.Limit = opts.limit;
|
|
75
|
+
params.ScanIndexForward = false;
|
|
76
|
+
const result = await this.docClient.send(new ddbDocLib2.QueryCommand(params));
|
|
77
|
+
return result.Items || [];
|
|
78
|
+
}
|
|
79
|
+
async deleteItem(pkVal, skVal) {
|
|
80
|
+
const { ddbDocLib: ddbDocLib2 } = await getDdb();
|
|
81
|
+
await this.docClient.send(new ddbDocLib2.DeleteCommand({
|
|
82
|
+
TableName: this.tableName,
|
|
83
|
+
Key: { PK: pkVal, SK: skVal }
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
async migrate() {
|
|
87
|
+
const { ddbLib: ddbLib2 } = await getDdb();
|
|
88
|
+
try {
|
|
89
|
+
await this.client.send(new ddbLib2.CreateTableCommand({
|
|
90
|
+
TableName: this.tableName,
|
|
91
|
+
KeySchema: [
|
|
92
|
+
{ AttributeName: "PK", KeyType: "HASH" },
|
|
93
|
+
{ AttributeName: "SK", KeyType: "RANGE" }
|
|
94
|
+
],
|
|
95
|
+
AttributeDefinitions: [
|
|
96
|
+
{ AttributeName: "PK", AttributeType: "S" },
|
|
97
|
+
{ AttributeName: "SK", AttributeType: "S" },
|
|
98
|
+
{ AttributeName: "GSI1PK", AttributeType: "S" },
|
|
99
|
+
{ AttributeName: "GSI1SK", AttributeType: "S" }
|
|
100
|
+
],
|
|
101
|
+
GlobalSecondaryIndexes: [
|
|
102
|
+
{
|
|
103
|
+
IndexName: "GSI1",
|
|
104
|
+
KeySchema: [
|
|
105
|
+
{ AttributeName: "GSI1PK", KeyType: "HASH" },
|
|
106
|
+
{ AttributeName: "GSI1SK", KeyType: "RANGE" }
|
|
107
|
+
],
|
|
108
|
+
Projection: { ProjectionType: "ALL" },
|
|
109
|
+
ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
BillingMode: "PAY_PER_REQUEST"
|
|
113
|
+
}));
|
|
114
|
+
const waiter = new ddbLib2.DescribeTableCommand({ TableName: this.tableName });
|
|
115
|
+
for (let i = 0; i < 30; i++) {
|
|
116
|
+
const desc = await this.client.send(waiter);
|
|
117
|
+
if (desc.Table?.TableStatus === "ACTIVE") break;
|
|
118
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (!err.name?.includes("ResourceInUse") && !err.message?.includes("already exists")) throw err;
|
|
122
|
+
}
|
|
123
|
+
const existing = await this.getItem(pk("SETTINGS"), "default");
|
|
124
|
+
if (!existing) {
|
|
125
|
+
await this.put({ PK: pk("SETTINGS"), SK: "default", name: "", subdomain: "", plan: "free", primaryColor: "#6366f1", createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
126
|
+
}
|
|
127
|
+
const retPol = await this.getItem(pk("RETENTION"), "default");
|
|
128
|
+
if (!retPol) {
|
|
129
|
+
await this.put({ PK: pk("RETENTION"), SK: "default", enabled: false, retainDays: 365, excludeTags: [], archiveFirst: true });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// ─── Company ─────────────────────────────────────────────
|
|
133
|
+
async getSettings() {
|
|
134
|
+
const r = await this.getItem(pk("SETTINGS"), "default");
|
|
135
|
+
if (!r) return null;
|
|
136
|
+
return { id: "default", name: r.name, domain: r.domain, subdomain: r.subdomain, smtpHost: r.smtpHost, smtpPort: r.smtpPort, smtpUser: r.smtpUser, smtpPass: r.smtpPass, dkimPrivateKey: r.dkimPrivateKey, logoUrl: r.logoUrl, primaryColor: r.primaryColor, ssoConfig: r.ssoConfig, toolSecurityConfig: r.toolSecurityConfig || {}, firewallConfig: r.firewallConfig || {}, modelPricingConfig: r.modelPricingConfig || {}, plan: r.plan, deploymentKeyHash: r.deploymentKeyHash, domainRegistrationId: r.domainRegistrationId, domainDnsChallenge: r.domainDnsChallenge, domainVerifiedAt: r.domainVerifiedAt || void 0, domainRegisteredAt: r.domainRegisteredAt || void 0, domainStatus: r.domainStatus || "unregistered", createdAt: new Date(r.createdAt), updatedAt: new Date(r.updatedAt) };
|
|
137
|
+
}
|
|
138
|
+
async updateSettings(updates) {
|
|
139
|
+
const current = await this.getItem(pk("SETTINGS"), "default") || {};
|
|
140
|
+
const { id, ...rest } = updates;
|
|
141
|
+
await this.put({ ...current, ...rest, PK: pk("SETTINGS"), SK: "default", updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
142
|
+
return this.getSettings();
|
|
143
|
+
}
|
|
144
|
+
// ─── Agents ──────────────────────────────────────────────
|
|
145
|
+
async createAgent(input) {
|
|
146
|
+
const id = input.id || randomUUID();
|
|
147
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
148
|
+
const email = input.email || `${input.name.toLowerCase().replace(/\s+/g, "-")}@localhost`;
|
|
149
|
+
const item = {
|
|
150
|
+
PK: pk("AGENT"),
|
|
151
|
+
SK: id,
|
|
152
|
+
GSI1PK: "AGENT_NAME",
|
|
153
|
+
GSI1SK: input.name,
|
|
154
|
+
name: input.name,
|
|
155
|
+
email,
|
|
156
|
+
role: input.role || "assistant",
|
|
157
|
+
status: "active",
|
|
158
|
+
metadata: input.metadata || {},
|
|
159
|
+
createdBy: input.createdBy,
|
|
160
|
+
createdAt: now,
|
|
161
|
+
updatedAt: now
|
|
162
|
+
};
|
|
163
|
+
await this.put(item);
|
|
164
|
+
return this.itemToAgent(item);
|
|
165
|
+
}
|
|
166
|
+
async getAgent(id) {
|
|
167
|
+
const r = await this.getItem(pk("AGENT"), id);
|
|
168
|
+
return r ? this.itemToAgent(r) : null;
|
|
169
|
+
}
|
|
170
|
+
async getAgentByName(name) {
|
|
171
|
+
const items = await this.query("AGENT_NAME", { index: "GSI1", pkField: "GSI1PK", sk: { begins: name }, limit: 1 });
|
|
172
|
+
return items.length > 0 ? this.itemToAgent(items[0]) : null;
|
|
173
|
+
}
|
|
174
|
+
async listAgents(opts) {
|
|
175
|
+
const items = await this.query(pk("AGENT"), { limit: (opts?.limit || 50) + (opts?.offset || 0) });
|
|
176
|
+
let result = items.map((r) => this.itemToAgent(r));
|
|
177
|
+
if (opts?.status) result = result.filter((a) => a.status === opts.status);
|
|
178
|
+
if (opts?.offset) result = result.slice(opts.offset);
|
|
179
|
+
if (opts?.limit) result = result.slice(0, opts.limit);
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
async updateAgent(id, updates) {
|
|
183
|
+
const current = await this.getItem(pk("AGENT"), id);
|
|
184
|
+
if (!current) throw new Error("Agent not found");
|
|
185
|
+
const merged = { ...current, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
186
|
+
for (const key of ["name", "email", "role", "status", "metadata"]) {
|
|
187
|
+
if (updates[key] !== void 0) merged[key] = updates[key];
|
|
188
|
+
}
|
|
189
|
+
if (updates.name) {
|
|
190
|
+
merged.GSI1SK = updates.name;
|
|
191
|
+
}
|
|
192
|
+
await this.put(merged);
|
|
193
|
+
return this.itemToAgent(merged);
|
|
194
|
+
}
|
|
195
|
+
async archiveAgent(id) {
|
|
196
|
+
await this.updateAgent(id, { status: "archived" });
|
|
197
|
+
}
|
|
198
|
+
async deleteAgent(id) {
|
|
199
|
+
await this.deleteItem(pk("AGENT"), id);
|
|
200
|
+
}
|
|
201
|
+
async countAgents(status) {
|
|
202
|
+
const items = await this.query(pk("AGENT"));
|
|
203
|
+
if (status) return items.filter((i) => i.status === status).length;
|
|
204
|
+
return items.length;
|
|
205
|
+
}
|
|
206
|
+
// ─── Users ───────────────────────────────────────────────
|
|
207
|
+
async createUser(input) {
|
|
208
|
+
const id = randomUUID();
|
|
209
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
210
|
+
let passwordHash = null;
|
|
211
|
+
if (input.password) {
|
|
212
|
+
const { default: bcrypt } = await import("bcryptjs");
|
|
213
|
+
passwordHash = await bcrypt.hash(input.password, 12);
|
|
214
|
+
}
|
|
215
|
+
const item = {
|
|
216
|
+
PK: pk("USER"),
|
|
217
|
+
SK: id,
|
|
218
|
+
GSI1PK: "USER_EMAIL",
|
|
219
|
+
GSI1SK: input.email,
|
|
220
|
+
email: input.email,
|
|
221
|
+
name: input.name,
|
|
222
|
+
role: input.role,
|
|
223
|
+
passwordHash,
|
|
224
|
+
ssoProvider: input.ssoProvider || null,
|
|
225
|
+
ssoSubject: input.ssoSubject || null,
|
|
226
|
+
createdAt: now,
|
|
227
|
+
updatedAt: now,
|
|
228
|
+
lastLoginAt: null
|
|
229
|
+
};
|
|
230
|
+
await this.put(item);
|
|
231
|
+
return this.itemToUser(item);
|
|
232
|
+
}
|
|
233
|
+
async getUser(id) {
|
|
234
|
+
const r = await this.getItem(pk("USER"), id);
|
|
235
|
+
return r ? this.itemToUser(r) : null;
|
|
236
|
+
}
|
|
237
|
+
async getUserByEmail(email) {
|
|
238
|
+
const items = await this.query("USER_EMAIL", { index: "GSI1", pkField: "GSI1PK", sk: { begins: email }, limit: 1 });
|
|
239
|
+
return items.length > 0 ? this.itemToUser(items[0]) : null;
|
|
240
|
+
}
|
|
241
|
+
async getUserBySso(provider, subject) {
|
|
242
|
+
const items = await this.query(pk("USER"));
|
|
243
|
+
const found = items.find((i) => i.ssoProvider === provider && i.ssoSubject === subject);
|
|
244
|
+
return found ? this.itemToUser(found) : null;
|
|
245
|
+
}
|
|
246
|
+
async listUsers(opts) {
|
|
247
|
+
const items = await this.query(pk("USER"), { limit: (opts?.limit || 50) + (opts?.offset || 0) });
|
|
248
|
+
let result = items.map((r) => this.itemToUser(r));
|
|
249
|
+
if (opts?.offset) result = result.slice(opts.offset);
|
|
250
|
+
if (opts?.limit) result = result.slice(0, opts.limit);
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
async updateUser(id, updates) {
|
|
254
|
+
const current = await this.getItem(pk("USER"), id);
|
|
255
|
+
if (!current) throw new Error("User not found");
|
|
256
|
+
const merged = { ...current, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
257
|
+
for (const key of ["email", "name", "role", "lastLoginAt"]) {
|
|
258
|
+
if (updates[key] !== void 0) merged[key] = updates[key];
|
|
259
|
+
}
|
|
260
|
+
if (updates.email) {
|
|
261
|
+
merged.GSI1SK = updates.email;
|
|
262
|
+
}
|
|
263
|
+
await this.put(merged);
|
|
264
|
+
return this.itemToUser(merged);
|
|
265
|
+
}
|
|
266
|
+
async deleteUser(id) {
|
|
267
|
+
await this.deleteItem(pk("USER"), id);
|
|
268
|
+
}
|
|
269
|
+
// ─── Audit ───────────────────────────────────────────────
|
|
270
|
+
async logEvent(event) {
|
|
271
|
+
const id = randomUUID();
|
|
272
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
273
|
+
await this.put({
|
|
274
|
+
PK: pk("AUDIT"),
|
|
275
|
+
SK: `${now}#${id}`,
|
|
276
|
+
GSI1PK: `AUDIT_ACTOR#${event.actor}`,
|
|
277
|
+
GSI1SK: now,
|
|
278
|
+
id,
|
|
279
|
+
timestamp: now,
|
|
280
|
+
actor: event.actor,
|
|
281
|
+
actorType: event.actorType,
|
|
282
|
+
action: event.action,
|
|
283
|
+
resource: event.resource,
|
|
284
|
+
details: event.details || {},
|
|
285
|
+
ip: event.ip || null
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async queryAudit(filters) {
|
|
289
|
+
let items;
|
|
290
|
+
if (filters.actor) {
|
|
291
|
+
items = await this.query(`AUDIT_ACTOR#${filters.actor}`, { index: "GSI1", pkField: "GSI1PK" });
|
|
292
|
+
} else {
|
|
293
|
+
items = await this.query(pk("AUDIT"));
|
|
294
|
+
}
|
|
295
|
+
if (filters.action) items = items.filter((i) => i.action === filters.action);
|
|
296
|
+
if (filters.resource) items = items.filter((i) => i.resource?.includes(filters.resource));
|
|
297
|
+
if (filters.from) items = items.filter((i) => new Date(i.timestamp) >= filters.from);
|
|
298
|
+
if (filters.to) items = items.filter((i) => new Date(i.timestamp) <= filters.to);
|
|
299
|
+
const total = items.length;
|
|
300
|
+
if (filters.offset) items = items.slice(filters.offset);
|
|
301
|
+
if (filters.limit) items = items.slice(0, filters.limit);
|
|
302
|
+
return {
|
|
303
|
+
events: items.map((r) => ({ id: r.id || r.SK, timestamp: new Date(r.timestamp), actor: r.actor, actorType: r.actorType, action: r.action, resource: r.resource, details: r.details, ip: r.ip })),
|
|
304
|
+
total
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// ─── API Keys ────────────────────────────────────────────
|
|
308
|
+
async createApiKey(input) {
|
|
309
|
+
const id = randomUUID();
|
|
310
|
+
const plaintext = `ek_${randomUUID().replace(/-/g, "")}`;
|
|
311
|
+
const keyHash = createHash("sha256").update(plaintext).digest("hex");
|
|
312
|
+
const keyPrefix = plaintext.substring(0, 11);
|
|
313
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
314
|
+
const item = {
|
|
315
|
+
PK: pk("APIKEY"),
|
|
316
|
+
SK: id,
|
|
317
|
+
GSI1PK: "APIKEY_HASH",
|
|
318
|
+
GSI1SK: keyHash,
|
|
319
|
+
name: input.name,
|
|
320
|
+
keyHash,
|
|
321
|
+
keyPrefix,
|
|
322
|
+
scopes: input.scopes,
|
|
323
|
+
createdBy: input.createdBy,
|
|
324
|
+
createdAt: now,
|
|
325
|
+
lastUsedAt: null,
|
|
326
|
+
expiresAt: input.expiresAt?.toISOString() || null,
|
|
327
|
+
revoked: false
|
|
328
|
+
};
|
|
329
|
+
await this.put(item);
|
|
330
|
+
return { key: this.itemToApiKey(item), plaintext };
|
|
331
|
+
}
|
|
332
|
+
async getApiKey(id) {
|
|
333
|
+
const r = await this.getItem(pk("APIKEY"), id);
|
|
334
|
+
return r ? this.itemToApiKey(r) : null;
|
|
335
|
+
}
|
|
336
|
+
async validateApiKey(plaintext) {
|
|
337
|
+
const keyHash = createHash("sha256").update(plaintext).digest("hex");
|
|
338
|
+
const items = await this.query("APIKEY_HASH", { index: "GSI1", pkField: "GSI1PK", sk: { begins: keyHash }, limit: 1 });
|
|
339
|
+
if (items.length === 0 || items[0].revoked) return null;
|
|
340
|
+
const key = this.itemToApiKey(items[0]);
|
|
341
|
+
if (key.expiresAt && /* @__PURE__ */ new Date() > key.expiresAt) return null;
|
|
342
|
+
items[0].lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
343
|
+
await this.put(items[0]);
|
|
344
|
+
return key;
|
|
345
|
+
}
|
|
346
|
+
async listApiKeys(opts) {
|
|
347
|
+
const items = await this.query(pk("APIKEY"));
|
|
348
|
+
let result = items;
|
|
349
|
+
if (opts?.createdBy) result = result.filter((i) => i.createdBy === opts.createdBy);
|
|
350
|
+
return result.map((r) => this.itemToApiKey(r));
|
|
351
|
+
}
|
|
352
|
+
async revokeApiKey(id) {
|
|
353
|
+
const current = await this.getItem(pk("APIKEY"), id);
|
|
354
|
+
if (current) {
|
|
355
|
+
current.revoked = true;
|
|
356
|
+
await this.put(current);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// ─── Rules ───────────────────────────────────────────────
|
|
360
|
+
async createRule(rule) {
|
|
361
|
+
const id = randomUUID();
|
|
362
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
363
|
+
const item = { PK: pk("RULE"), SK: id, ...rule, createdAt: now, updatedAt: now };
|
|
364
|
+
await this.put(item);
|
|
365
|
+
return this.itemToRule(item);
|
|
366
|
+
}
|
|
367
|
+
async getRules(agentId) {
|
|
368
|
+
const items = await this.query(pk("RULE"));
|
|
369
|
+
let result = items;
|
|
370
|
+
if (agentId) result = result.filter((i) => !i.agentId || i.agentId === agentId);
|
|
371
|
+
return result.map((r) => this.itemToRule(r)).sort((a, b) => b.priority - a.priority);
|
|
372
|
+
}
|
|
373
|
+
async updateRule(id, updates) {
|
|
374
|
+
const current = await this.getItem(pk("RULE"), id);
|
|
375
|
+
if (!current) throw new Error("Rule not found");
|
|
376
|
+
const { id: _id, createdAt, ...rest } = updates;
|
|
377
|
+
const merged = { ...current, ...rest, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
378
|
+
await this.put(merged);
|
|
379
|
+
return this.itemToRule(merged);
|
|
380
|
+
}
|
|
381
|
+
async deleteRule(id) {
|
|
382
|
+
await this.deleteItem(pk("RULE"), id);
|
|
383
|
+
}
|
|
384
|
+
// ─── Retention ───────────────────────────────────────────
|
|
385
|
+
async getRetentionPolicy() {
|
|
386
|
+
const r = await this.getItem(pk("RETENTION"), "default");
|
|
387
|
+
if (!r) return { enabled: false, retainDays: 365, archiveFirst: true };
|
|
388
|
+
return { enabled: r.enabled, retainDays: r.retainDays, excludeTags: r.excludeTags || [], archiveFirst: r.archiveFirst };
|
|
389
|
+
}
|
|
390
|
+
async setRetentionPolicy(policy) {
|
|
391
|
+
await this.put({ PK: pk("RETENTION"), SK: "default", ...policy });
|
|
392
|
+
}
|
|
393
|
+
// ─── Stats ───────────────────────────────────────────────
|
|
394
|
+
async getStats() {
|
|
395
|
+
const [agents, users, audit] = await Promise.all([
|
|
396
|
+
this.query(pk("AGENT")),
|
|
397
|
+
this.query(pk("USER")),
|
|
398
|
+
this.query(pk("AUDIT"))
|
|
399
|
+
]);
|
|
400
|
+
return {
|
|
401
|
+
totalAgents: agents.length,
|
|
402
|
+
activeAgents: agents.filter((a) => a.status === "active").length,
|
|
403
|
+
totalUsers: users.length,
|
|
404
|
+
totalEmails: 0,
|
|
405
|
+
totalAuditEvents: audit.length
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
// ─── Mappers ─────────────────────────────────────────────
|
|
409
|
+
itemToAgent(r) {
|
|
410
|
+
return { id: r.SK || r.id, name: r.name, email: r.email, role: r.role, status: r.status, metadata: r.metadata || {}, createdBy: r.createdBy, createdAt: new Date(r.createdAt), updatedAt: new Date(r.updatedAt) };
|
|
411
|
+
}
|
|
412
|
+
itemToUser(r) {
|
|
413
|
+
return { id: r.SK || r.id, email: r.email, name: r.name, role: r.role, passwordHash: r.passwordHash, ssoProvider: r.ssoProvider, ssoSubject: r.ssoSubject, createdAt: new Date(r.createdAt), updatedAt: new Date(r.updatedAt), lastLoginAt: r.lastLoginAt ? new Date(r.lastLoginAt) : void 0 };
|
|
414
|
+
}
|
|
415
|
+
itemToApiKey(r) {
|
|
416
|
+
return { id: r.SK || r.id, name: r.name, keyHash: r.keyHash, keyPrefix: r.keyPrefix, scopes: r.scopes || [], createdBy: r.createdBy, createdAt: new Date(r.createdAt), lastUsedAt: r.lastUsedAt ? new Date(r.lastUsedAt) : void 0, expiresAt: r.expiresAt ? new Date(r.expiresAt) : void 0, revoked: r.revoked };
|
|
417
|
+
}
|
|
418
|
+
itemToRule(r) {
|
|
419
|
+
return { id: r.SK || r.id, name: r.name, agentId: r.agentId, conditions: r.conditions || {}, actions: r.actions || {}, priority: r.priority || 0, enabled: r.enabled ?? true, createdAt: new Date(r.createdAt), updatedAt: new Date(r.updatedAt) };
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
export {
|
|
423
|
+
DynamoAdapter
|
|
424
|
+
};
|