aap-agent-server 2.5.0 → 2.7.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/whitelist.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * AAP Whitelist & Key Management
3
+ *
4
+ * Optional: Maintain list of trusted agent public IDs
5
+ */
6
+
7
+ /**
8
+ * Create a whitelist manager
9
+ * @param {Object} options
10
+ * @param {boolean} [options.enabled=false] - Enable whitelist enforcement
11
+ * @param {string[]} [options.allowedIds=[]] - Pre-approved public IDs
12
+ * @param {Function} [options.onNewAgent] - Callback when new agent attempts verification
13
+ */
14
+ export function createWhitelist(options = {}) {
15
+ const {
16
+ enabled = false,
17
+ allowedIds = [],
18
+ onNewAgent = null
19
+ } = options;
20
+
21
+ const whitelist = new Set(allowedIds);
22
+ const pendingApproval = new Map(); // publicId -> { firstSeen, attempts }
23
+
24
+ return {
25
+ /**
26
+ * Check if agent is allowed
27
+ * @param {string} publicId
28
+ * @returns {boolean}
29
+ */
30
+ isAllowed(publicId) {
31
+ if (!enabled) return true;
32
+ return whitelist.has(publicId);
33
+ },
34
+
35
+ /**
36
+ * Add agent to whitelist
37
+ * @param {string} publicId
38
+ */
39
+ add(publicId) {
40
+ whitelist.add(publicId);
41
+ pendingApproval.delete(publicId);
42
+ },
43
+
44
+ /**
45
+ * Remove agent from whitelist
46
+ * @param {string} publicId
47
+ */
48
+ remove(publicId) {
49
+ whitelist.delete(publicId);
50
+ },
51
+
52
+ /**
53
+ * Get all whitelisted IDs
54
+ * @returns {string[]}
55
+ */
56
+ list() {
57
+ return [...whitelist];
58
+ },
59
+
60
+ /**
61
+ * Record attempt from unknown agent
62
+ * @param {string} publicId
63
+ * @param {Object} details
64
+ */
65
+ recordAttempt(publicId, details = {}) {
66
+ if (!enabled) return;
67
+
68
+ let record = pendingApproval.get(publicId);
69
+ if (!record) {
70
+ record = { firstSeen: Date.now(), attempts: 0, lastDetails: null };
71
+ pendingApproval.set(publicId, record);
72
+
73
+ if (onNewAgent) {
74
+ onNewAgent(publicId, details);
75
+ }
76
+ }
77
+
78
+ record.attempts++;
79
+ record.lastDetails = details;
80
+ },
81
+
82
+ /**
83
+ * Get pending approvals
84
+ * @returns {Object[]}
85
+ */
86
+ getPending() {
87
+ return [...pendingApproval.entries()].map(([id, data]) => ({
88
+ publicId: id,
89
+ ...data
90
+ }));
91
+ },
92
+
93
+ /**
94
+ * Middleware to enforce whitelist
95
+ */
96
+ middleware() {
97
+ return (req, res, next) => {
98
+ if (!enabled) return next();
99
+
100
+ const publicId = req.body?.publicId;
101
+ if (!publicId) return next(); // Will fail later anyway
102
+
103
+ if (!whitelist.has(publicId)) {
104
+ this.recordAttempt(publicId, {
105
+ ip: req.ip,
106
+ timestamp: Date.now()
107
+ });
108
+
109
+ return res.status(403).json({
110
+ verified: false,
111
+ error: 'Agent not in whitelist',
112
+ publicId
113
+ });
114
+ }
115
+
116
+ next();
117
+ };
118
+ },
119
+
120
+ /**
121
+ * Check if whitelist is enabled
122
+ */
123
+ isEnabled() {
124
+ return enabled;
125
+ },
126
+
127
+ /**
128
+ * Get stats
129
+ */
130
+ stats() {
131
+ return {
132
+ enabled,
133
+ whitelistedCount: whitelist.size,
134
+ pendingCount: pendingApproval.size
135
+ };
136
+ }
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Key rotation helper for agents
142
+ *
143
+ * Tracks key history and provides rotation utilities
144
+ */
145
+ export function createKeyRotation(options = {}) {
146
+ const {
147
+ maxKeyAge = 30 * 24 * 60 * 60 * 1000, // 30 days default
148
+ onRotationNeeded = null
149
+ } = options;
150
+
151
+ const keyHistory = new Map(); // publicId -> { keys: [], currentIndex }
152
+
153
+ return {
154
+ /**
155
+ * Register a key
156
+ * @param {string} publicId
157
+ * @param {string} publicKey
158
+ * @param {number} [createdAt]
159
+ */
160
+ registerKey(publicId, publicKey, createdAt = Date.now()) {
161
+ let history = keyHistory.get(publicId);
162
+ if (!history) {
163
+ history = { keys: [], currentIndex: 0 };
164
+ keyHistory.set(publicId, history);
165
+ }
166
+
167
+ history.keys.push({
168
+ publicKey,
169
+ createdAt,
170
+ revokedAt: null
171
+ });
172
+ history.currentIndex = history.keys.length - 1;
173
+ },
174
+
175
+ /**
176
+ * Get current key for agent
177
+ * @param {string} publicId
178
+ * @returns {string|null}
179
+ */
180
+ getCurrentKey(publicId) {
181
+ const history = keyHistory.get(publicId);
182
+ if (!history) return null;
183
+ return history.keys[history.currentIndex]?.publicKey || null;
184
+ },
185
+
186
+ /**
187
+ * Check if key needs rotation
188
+ * @param {string} publicId
189
+ * @returns {boolean}
190
+ */
191
+ needsRotation(publicId) {
192
+ const history = keyHistory.get(publicId);
193
+ if (!history || !history.keys.length) return false;
194
+
195
+ const currentKey = history.keys[history.currentIndex];
196
+ const age = Date.now() - currentKey.createdAt;
197
+
198
+ if (age > maxKeyAge) {
199
+ if (onRotationNeeded) {
200
+ onRotationNeeded(publicId, age);
201
+ }
202
+ return true;
203
+ }
204
+
205
+ return false;
206
+ },
207
+
208
+ /**
209
+ * Revoke old key
210
+ * @param {string} publicId
211
+ * @param {number} keyIndex
212
+ */
213
+ revokeKey(publicId, keyIndex) {
214
+ const history = keyHistory.get(publicId);
215
+ if (!history || !history.keys[keyIndex]) return;
216
+
217
+ history.keys[keyIndex].revokedAt = Date.now();
218
+ },
219
+
220
+ /**
221
+ * Get key history for agent
222
+ * @param {string} publicId
223
+ * @returns {Object[]}
224
+ */
225
+ getHistory(publicId) {
226
+ return keyHistory.get(publicId)?.keys || [];
227
+ }
228
+ };
229
+ }
230
+
231
+ export default { createWhitelist, createKeyRotation };