aap-agent-server 2.0.0 → 2.6.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/challenges.js +318 -109
- package/index.js +24 -3
- package/middleware.js +60 -15
- package/package.json +2 -2
- package/persistence.js +238 -0
- package/ratelimit.js +117 -0
- package/whitelist.js +231 -0
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 };
|