paygate-mcp 3.4.0 → 3.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/dist/groups.js ADDED
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ /**
3
+ * Key Groups — Policy templates for API keys.
4
+ *
5
+ * Groups define shared policies (ACL, rate limits, pricing, quotas, IP rules)
6
+ * that are inherited by all member keys. Key-level settings override group defaults.
7
+ *
8
+ * Unlike Teams (which share budgets), Groups share *policies*:
9
+ * - Tool ACL (allowedTools / deniedTools)
10
+ * - Rate limit override (calls per minute)
11
+ * - Per-tool pricing overrides
12
+ * - Quota defaults (daily/monthly limits)
13
+ * - IP allowlist
14
+ * - Metadata tags
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.KeyGroupManager = void 0;
18
+ const crypto_1 = require("crypto");
19
+ // ─── KeyGroupManager ─────────────────────────────────────────────────────────
20
+ class KeyGroupManager {
21
+ groups = new Map();
22
+ /** Reverse index: apiKey → groupId */
23
+ keyToGroup = new Map();
24
+ // ─── CRUD ────────────────────────────────────────────────────────────────
25
+ createGroup(params) {
26
+ const name = String(params.name || '').trim();
27
+ if (!name)
28
+ throw new Error('Group must have a name');
29
+ // Enforce unique names
30
+ for (const g of this.groups.values()) {
31
+ if (g.active && g.name === name) {
32
+ throw new Error(`Group '${name}' already exists`);
33
+ }
34
+ }
35
+ const id = 'grp_' + (0, crypto_1.randomBytes)(8).toString('hex');
36
+ const record = {
37
+ id,
38
+ name,
39
+ description: String(params.description || ''),
40
+ allowedTools: params.allowedTools || [],
41
+ deniedTools: params.deniedTools || [],
42
+ rateLimitPerMin: Math.max(0, Math.floor(Number(params.rateLimitPerMin) || 0)),
43
+ toolPricing: params.toolPricing || {},
44
+ quota: params.quota,
45
+ ipAllowlist: params.ipAllowlist || [],
46
+ defaultCredits: Math.max(0, Math.floor(Number(params.defaultCredits) || 0)),
47
+ maxSpendingLimit: Math.max(0, Math.floor(Number(params.maxSpendingLimit) || 0)),
48
+ tags: params.tags || {},
49
+ createdAt: new Date().toISOString(),
50
+ active: true,
51
+ };
52
+ this.groups.set(id, record);
53
+ return record;
54
+ }
55
+ getGroup(id) {
56
+ return this.groups.get(id);
57
+ }
58
+ getGroupByName(name) {
59
+ for (const g of this.groups.values()) {
60
+ if (g.active && g.name === name)
61
+ return g;
62
+ }
63
+ return undefined;
64
+ }
65
+ updateGroup(id, updates) {
66
+ const group = this.groups.get(id);
67
+ if (!group || !group.active)
68
+ throw new Error(`Group '${id}' not found`);
69
+ // Check name uniqueness if name is being changed
70
+ if (updates.name !== undefined && updates.name !== group.name) {
71
+ const name = String(updates.name).trim();
72
+ if (!name)
73
+ throw new Error('Group must have a name');
74
+ for (const g of this.groups.values()) {
75
+ if (g.active && g.id !== id && g.name === name) {
76
+ throw new Error(`Group '${name}' already exists`);
77
+ }
78
+ }
79
+ group.name = name;
80
+ }
81
+ if (updates.description !== undefined)
82
+ group.description = String(updates.description);
83
+ if (updates.allowedTools !== undefined)
84
+ group.allowedTools = updates.allowedTools;
85
+ if (updates.deniedTools !== undefined)
86
+ group.deniedTools = updates.deniedTools;
87
+ if (updates.rateLimitPerMin !== undefined)
88
+ group.rateLimitPerMin = Math.max(0, Math.floor(Number(updates.rateLimitPerMin) || 0));
89
+ if (updates.toolPricing !== undefined)
90
+ group.toolPricing = updates.toolPricing;
91
+ if (updates.quota === null)
92
+ delete group.quota;
93
+ else if (updates.quota !== undefined)
94
+ group.quota = updates.quota;
95
+ if (updates.ipAllowlist !== undefined)
96
+ group.ipAllowlist = updates.ipAllowlist;
97
+ if (updates.defaultCredits !== undefined)
98
+ group.defaultCredits = Math.max(0, Math.floor(Number(updates.defaultCredits) || 0));
99
+ if (updates.maxSpendingLimit !== undefined)
100
+ group.maxSpendingLimit = Math.max(0, Math.floor(Number(updates.maxSpendingLimit) || 0));
101
+ if (updates.tags !== undefined)
102
+ group.tags = { ...group.tags, ...updates.tags };
103
+ return group;
104
+ }
105
+ deleteGroup(id) {
106
+ const group = this.groups.get(id);
107
+ if (!group || !group.active)
108
+ return false;
109
+ group.active = false;
110
+ // Remove all key assignments for this group
111
+ for (const [key, gid] of this.keyToGroup.entries()) {
112
+ if (gid === id)
113
+ this.keyToGroup.delete(key);
114
+ }
115
+ return true;
116
+ }
117
+ listGroups() {
118
+ const result = [];
119
+ for (const g of this.groups.values()) {
120
+ if (!g.active)
121
+ continue;
122
+ const memberCount = this.getGroupMembers(g.id).length;
123
+ result.push({
124
+ id: g.id,
125
+ name: g.name,
126
+ description: g.description,
127
+ memberCount,
128
+ allowedTools: g.allowedTools,
129
+ deniedTools: g.deniedTools,
130
+ rateLimitPerMin: g.rateLimitPerMin,
131
+ quota: g.quota,
132
+ ipAllowlist: g.ipAllowlist,
133
+ defaultCredits: g.defaultCredits,
134
+ maxSpendingLimit: g.maxSpendingLimit,
135
+ tags: g.tags,
136
+ createdAt: g.createdAt,
137
+ active: g.active,
138
+ });
139
+ }
140
+ return result;
141
+ }
142
+ // ─── Key Membership ──────────────────────────────────────────────────────
143
+ assignKey(apiKey, groupId) {
144
+ const group = this.groups.get(groupId);
145
+ if (!group || !group.active)
146
+ throw new Error(`Group '${groupId}' not found`);
147
+ this.keyToGroup.set(apiKey, groupId);
148
+ }
149
+ removeKey(apiKey) {
150
+ return this.keyToGroup.delete(apiKey);
151
+ }
152
+ getKeyGroup(apiKey) {
153
+ const groupId = this.keyToGroup.get(apiKey);
154
+ if (!groupId)
155
+ return undefined;
156
+ const group = this.groups.get(groupId);
157
+ if (!group || !group.active) {
158
+ this.keyToGroup.delete(apiKey);
159
+ return undefined;
160
+ }
161
+ return group;
162
+ }
163
+ getKeyGroupId(apiKey) {
164
+ const groupId = this.keyToGroup.get(apiKey);
165
+ if (!groupId)
166
+ return undefined;
167
+ const group = this.groups.get(groupId);
168
+ if (!group || !group.active) {
169
+ this.keyToGroup.delete(apiKey);
170
+ return undefined;
171
+ }
172
+ return groupId;
173
+ }
174
+ getGroupMembers(groupId) {
175
+ const members = [];
176
+ for (const [key, gid] of this.keyToGroup.entries()) {
177
+ if (gid === groupId)
178
+ members.push(key);
179
+ }
180
+ return members;
181
+ }
182
+ // ─── Policy Resolution ───────────────────────────────────────────────────
183
+ /**
184
+ * Resolve the effective policy for a key, merging group defaults with key overrides.
185
+ *
186
+ * Resolution rules:
187
+ * - allowedTools: key-level wins if non-empty, else group default
188
+ * - deniedTools: union of group + key (both applied)
189
+ * - rateLimitPerMin: key-level wins if set, else group default
190
+ * - quota: key-level wins if set, else group default
191
+ * - ipAllowlist: union of group + key (both applied)
192
+ * - toolPricing: group pricing is base, key-level (if any) would need external handling
193
+ * - maxSpendingLimit: group's maxSpendingLimit is a cap on the key's spending limit
194
+ */
195
+ resolvePolicy(apiKey, keyRecord) {
196
+ const group = this.getKeyGroup(apiKey);
197
+ if (!group)
198
+ return null;
199
+ // Allowed tools: key wins if non-empty, else group
200
+ const allowedTools = keyRecord.allowedTools.length > 0
201
+ ? keyRecord.allowedTools
202
+ : group.allowedTools;
203
+ // Denied tools: union (both applied)
204
+ const deniedSet = new Set([...group.deniedTools, ...keyRecord.deniedTools]);
205
+ const deniedTools = Array.from(deniedSet);
206
+ // Rate limit: group default (0 = use global)
207
+ const rateLimitPerMin = group.rateLimitPerMin;
208
+ // Quota: key wins if set, else group
209
+ const quota = keyRecord.quota || group.quota;
210
+ // IP allowlist: union
211
+ const ipSet = new Set([...group.ipAllowlist, ...keyRecord.ipAllowlist]);
212
+ const ipAllowlist = Array.from(ipSet);
213
+ // Tool pricing: group overrides
214
+ const toolPricing = group.toolPricing;
215
+ // Spending limit: cap to group's max if group has one set and key's limit is higher (or unlimited)
216
+ let maxSpendingLimit = group.maxSpendingLimit;
217
+ return {
218
+ allowedTools,
219
+ deniedTools,
220
+ rateLimitPerMin,
221
+ quota,
222
+ ipAllowlist,
223
+ toolPricing,
224
+ maxSpendingLimit,
225
+ };
226
+ }
227
+ // ─── Serialization (for state file persistence) ──────────────────────────
228
+ serialize() {
229
+ return {
230
+ groups: Array.from(this.groups.entries()),
231
+ assignments: Array.from(this.keyToGroup.entries()),
232
+ };
233
+ }
234
+ load(data) {
235
+ this.groups.clear();
236
+ this.keyToGroup.clear();
237
+ if (data.groups) {
238
+ for (const [id, record] of data.groups) {
239
+ this.groups.set(id, record);
240
+ }
241
+ }
242
+ if (data.assignments) {
243
+ for (const [key, groupId] of data.assignments) {
244
+ this.keyToGroup.set(key, groupId);
245
+ }
246
+ }
247
+ }
248
+ get count() {
249
+ let count = 0;
250
+ for (const g of this.groups.values()) {
251
+ if (g.active)
252
+ count++;
253
+ }
254
+ return count;
255
+ }
256
+ }
257
+ exports.KeyGroupManager = KeyGroupManager;
258
+ //# sourceMappingURL=groups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups.js","sourceRoot":"","sources":["../src/groups.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -57,6 +57,8 @@ export { AdminKeyManager, ROLE_HIERARCHY, VALID_ROLES } from './admin-keys';
57
57
  export type { AdminRole, AdminKeyRecord } from './admin-keys';
58
58
  export { PluginManager } from './plugin';
59
59
  export type { PayGatePlugin, PluginGateContext, PluginToolContext, PluginGateOverride, PluginInfo } from './plugin';
60
+ export { KeyGroupManager } from './groups';
61
+ export type { KeyGroupRecord, KeyGroupInfo, ResolvedPolicy } from './groups';
60
62
  export type { PayGateConfig, JsonRpcRequest, JsonRpcResponse, JsonRpcError, ToolCallParams, ToolInfo, ToolPricing, ServerBackendConfig, ApiKeyRecord, UsageEvent, UsageSummary, GateDecision, QuotaConfig, BatchToolCall, BatchGateResult, } from './types';
61
63
  export { DEFAULT_CONFIG } from './types';
62
64
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * ```
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.DEFAULT_CONFIG = exports.PluginManager = exports.VALID_ROLES = exports.ROLE_HIERARCHY = exports.AdminKeyManager = exports.TokenRevocationList = exports.ScopedTokenManager = exports.formatDiagnostics = exports.validateConfig = exports.PayGateError = exports.PayGateClient = exports.RedisSync = exports.RedisSubscriber = exports.parseRedisUrl = exports.RedisClient = exports.TeamManager = exports.AlertEngine = exports.AnalyticsEngine = exports.getDashboardHtml = exports.MetricsCollector = exports.ToolRegistry = exports.maskKeyForAudit = exports.AuditLogger = exports.writeSseKeepAlive = exports.writeSseEvent = exports.writeSseHeaders = exports.SessionManager = exports.OAuthProvider = exports.QuotaTracker = exports.WebhookEmitter = exports.StripeWebhookHandler = exports.RateLimiter = exports.UsageMeter = exports.KeyStore = exports.MultiServerRouter = exports.HttpMcpProxy = exports.McpProxy = exports.Gate = exports.PayGateServer = void 0;
19
+ exports.DEFAULT_CONFIG = exports.KeyGroupManager = exports.PluginManager = exports.VALID_ROLES = exports.ROLE_HIERARCHY = exports.AdminKeyManager = exports.TokenRevocationList = exports.ScopedTokenManager = exports.formatDiagnostics = exports.validateConfig = exports.PayGateError = exports.PayGateClient = exports.RedisSync = exports.RedisSubscriber = exports.parseRedisUrl = exports.RedisClient = exports.TeamManager = exports.AlertEngine = exports.AnalyticsEngine = exports.getDashboardHtml = exports.MetricsCollector = exports.ToolRegistry = exports.maskKeyForAudit = exports.AuditLogger = exports.writeSseKeepAlive = exports.writeSseEvent = exports.writeSseHeaders = exports.SessionManager = exports.OAuthProvider = exports.QuotaTracker = exports.WebhookEmitter = exports.StripeWebhookHandler = exports.RateLimiter = exports.UsageMeter = exports.KeyStore = exports.MultiServerRouter = exports.HttpMcpProxy = exports.McpProxy = exports.Gate = exports.PayGateServer = void 0;
20
20
  var server_1 = require("./server");
21
21
  Object.defineProperty(exports, "PayGateServer", { enumerable: true, get: function () { return server_1.PayGateServer; } });
22
22
  var gate_1 = require("./gate");
@@ -82,6 +82,8 @@ Object.defineProperty(exports, "ROLE_HIERARCHY", { enumerable: true, get: functi
82
82
  Object.defineProperty(exports, "VALID_ROLES", { enumerable: true, get: function () { return admin_keys_1.VALID_ROLES; } });
83
83
  var plugin_1 = require("./plugin");
84
84
  Object.defineProperty(exports, "PluginManager", { enumerable: true, get: function () { return plugin_1.PluginManager; } });
85
+ var groups_1 = require("./groups");
86
+ Object.defineProperty(exports, "KeyGroupManager", { enumerable: true, get: function () { return groups_1.KeyGroupManager; } });
85
87
  var types_1 = require("./types");
86
88
  Object.defineProperty(exports, "DEFAULT_CONFIG", { enumerable: true, get: function () { return types_1.DEFAULT_CONFIG; } });
87
89
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AACtB,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AACjB,2CAA4C;AAAnC,0GAAA,YAAY,OAAA;AACrB,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAC1B,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AACjB,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;AACnB,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AACpB,mCAAgD;AAAvC,8GAAA,oBAAoB,OAAA;AAC7B,qCAA2C;AAAlC,yGAAA,cAAc,OAAA;AAEvB,iCAAuC;AAA9B,qGAAA,YAAY,OAAA;AACrB,iCAAwC;AAA/B,sGAAA,aAAa,OAAA;AAEtB,qCAA8F;AAArF,yGAAA,cAAc,OAAA;AAAE,0GAAA,eAAe,OAAA;AAAE,wGAAA,aAAa,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAC1E,iCAAuD;AAA9C,oGAAA,WAAW,OAAA;AAAE,wGAAA,eAAe,OAAA;AAErC,uCAA0C;AAAjC,wGAAA,YAAY,OAAA;AACrB,qCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AAIzB,yCAA+C;AAAtC,6GAAA,gBAAgB,OAAA;AACzB,yCAA8C;AAArC,4GAAA,eAAe,OAAA;AAExB,mCAAuC;AAA9B,qGAAA,WAAW,OAAA;AAEpB,iCAAsC;AAA7B,oGAAA,WAAW,OAAA;AAEpB,+CAA6E;AAApE,2GAAA,WAAW,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,+GAAA,eAAe,OAAA;AAEpD,2CAAyC;AAAhC,uGAAA,SAAS,OAAA;AAElB,mCAAuD;AAA9C,uGAAA,aAAa,OAAA;AAAE,sGAAA,YAAY,OAAA;AAEpC,uDAAuE;AAA9D,kHAAA,cAAc,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAE1C,mCAAmE;AAA1D,4GAAA,kBAAkB,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAEhD,2CAA4E;AAAnE,6GAAA,eAAe,OAAA;AAAE,4GAAA,cAAc,OAAA;AAAE,yGAAA,WAAW,OAAA;AAErD,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AAqBtB,iCAAyC;AAAhC,uGAAA,cAAc,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AACtB,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AACjB,2CAA4C;AAAnC,0GAAA,YAAY,OAAA;AACrB,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAC1B,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AACjB,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;AACnB,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AACpB,mCAAgD;AAAvC,8GAAA,oBAAoB,OAAA;AAC7B,qCAA2C;AAAlC,yGAAA,cAAc,OAAA;AAEvB,iCAAuC;AAA9B,qGAAA,YAAY,OAAA;AACrB,iCAAwC;AAA/B,sGAAA,aAAa,OAAA;AAEtB,qCAA8F;AAArF,yGAAA,cAAc,OAAA;AAAE,0GAAA,eAAe,OAAA;AAAE,wGAAA,aAAa,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AAC1E,iCAAuD;AAA9C,oGAAA,WAAW,OAAA;AAAE,wGAAA,eAAe,OAAA;AAErC,uCAA0C;AAAjC,wGAAA,YAAY,OAAA;AACrB,qCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AAIzB,yCAA+C;AAAtC,6GAAA,gBAAgB,OAAA;AACzB,yCAA8C;AAArC,4GAAA,eAAe,OAAA;AAExB,mCAAuC;AAA9B,qGAAA,WAAW,OAAA;AAEpB,iCAAsC;AAA7B,oGAAA,WAAW,OAAA;AAEpB,+CAA6E;AAApE,2GAAA,WAAW,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,+GAAA,eAAe,OAAA;AAEpD,2CAAyC;AAAhC,uGAAA,SAAS,OAAA;AAElB,mCAAuD;AAA9C,uGAAA,aAAa,OAAA;AAAE,sGAAA,YAAY,OAAA;AAEpC,uDAAuE;AAA9D,kHAAA,cAAc,OAAA;AAAE,qHAAA,iBAAiB,OAAA;AAE1C,mCAAmE;AAA1D,4GAAA,kBAAkB,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAEhD,2CAA4E;AAAnE,6GAAA,eAAe,OAAA;AAAE,4GAAA,cAAc,OAAA;AAAE,yGAAA,WAAW,OAAA;AAErD,mCAAyC;AAAhC,uGAAA,aAAa,OAAA;AAEtB,mCAA2C;AAAlC,yGAAA,eAAe,OAAA;AAqBxB,iCAAyC;AAAhC,uGAAA,cAAc,OAAA"}
@@ -18,10 +18,11 @@ import { RedisClient } from './redis-client';
18
18
  import type { RedisClientOptions } from './redis-client';
19
19
  import { KeyStore } from './store';
20
20
  import { ApiKeyRecord, UsageEvent } from './types';
21
+ import { KeyGroupManager, KeyGroupRecord } from './groups';
21
22
  export interface PubSubEvent {
22
23
  /** Event type */
23
- type: 'key_updated' | 'key_revoked' | 'credits_changed' | 'key_created' | 'token_revoked';
24
- /** API key affected */
24
+ type: 'key_updated' | 'key_revoked' | 'credits_changed' | 'key_created' | 'token_revoked' | 'group_updated' | 'group_deleted' | 'group_assignment_changed';
25
+ /** API key or group ID affected */
25
26
  key: string;
26
27
  /** Originating instance ID (for self-message filtering) */
27
28
  instanceId: string;
@@ -53,6 +54,8 @@ export declare class RedisSync {
53
54
  onPubSubEvent?: (event: PubSubEvent) => void;
54
55
  /** Callback for token revocation events (wired to ScopedTokenManager by server) */
55
56
  onTokenRevoked?: (fingerprint: string, expiresAt: string, revokedAt: string, reason?: string) => void;
57
+ /** Optional KeyGroupManager for group sync (wired by server when groups are used) */
58
+ groupManager?: KeyGroupManager;
56
59
  constructor(redis: RedisClient, store: KeyStore, syncIntervalMs?: number);
57
60
  /**
58
61
  * Initialize: connect to Redis, load existing state, and start pub/sub listener.
@@ -143,5 +146,28 @@ export declare class RedisSync {
143
146
  private pushLocalToRedis;
144
147
  private recordToHash;
145
148
  private hashToRecord;
149
+ /**
150
+ * Save a group record to Redis. Fire-and-forget.
151
+ */
152
+ saveGroup(group: KeyGroupRecord): Promise<void>;
153
+ /**
154
+ * Delete a group from Redis. Fire-and-forget.
155
+ */
156
+ deleteGroup(groupId: string): Promise<void>;
157
+ /**
158
+ * Save all group assignments to Redis as a single hash (apiKey → groupId).
159
+ * Fire-and-forget.
160
+ */
161
+ saveGroupAssignments(): Promise<void>;
162
+ /**
163
+ * Load all groups and assignments from Redis into the local KeyGroupManager.
164
+ */
165
+ loadGroupsFromRedis(): Promise<void>;
166
+ /**
167
+ * Push local groups to Redis (used when Redis is empty on first connect).
168
+ */
169
+ private pushGroupsToRedis;
170
+ private groupToHash;
171
+ private hashToGroup;
146
172
  }
147
173
  //# sourceMappingURL=redis-sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-sync.d.ts","sourceRoot":"","sources":["../src/redis-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAkC,MAAM,gBAAgB,CAAC;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,YAAY,EAAe,UAAU,EAAE,MAAM,SAAS,CAAC;AAYhE,MAAM,WAAW,WAAW;IAC1B,iBAAiB;IACjB,IAAI,EAAE,aAAa,GAAG,aAAa,GAAG,iBAAiB,GAAG,aAAa,GAAG,eAAe,CAAC;IAC1F,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,OAAO,CAAC;QAEjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAwDD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yFAAyF;IACzF,OAAO,CAAC,UAAU,CAAgC;IAClD,0EAA0E;IAC1E,OAAO,CAAC,SAAS,CAAmC;IACpD,4CAA4C;IAC5C,OAAO,CAAC,YAAY,CAAS;IAC7B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,mFAAmF;IACnF,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;gBAE1F,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,cAAc,SAAO;IAStE;;OAEG;IACG,IAAI,CAAC,cAAc,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB9B;;;OAGG;YACW,WAAW;IAezB;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IASzE;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAsD3B;;OAEG;YACW,gBAAgB;IAa9B,iDAAiD;IACjD,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,4CAA4C;IAC5C,IAAI,cAAc,IAAI,OAAO,CAE5B;IAID;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB9B;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsCpE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA6BnE;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAcjD;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD;;OAEG;IACG,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAa3D;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAWtC;;;;;;;;OAQG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,OAAO,CAAC;QAC9E,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IA8CF;;;OAGG;YACW,aAAa;IA+B3B;;OAEG;YACW,gBAAgB;IAc9B,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,YAAY;CA8BrB"}
1
+ {"version":3,"file":"redis-sync.d.ts","sourceRoot":"","sources":["../src/redis-sync.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,9 @@ const KEY_SET = 'pg:keys';
24
24
  const META_KEY = 'pg:meta';
25
25
  const USAGE_LIST = 'pg:usage';
26
26
  const RATE_PREFIX = 'pg:rate:';
27
+ const GROUP_PREFIX = 'pg:group:';
28
+ const GROUP_SET = 'pg:groups';
29
+ const GROUP_ASSIGN_KEY = 'pg:group_assignments';
27
30
  const PG_CHANNEL = 'pg:events';
28
31
  /** Lua: atomic credit deduction — check + deduct + increment in one round trip */
29
32
  const DEDUCT_LUA = `
@@ -93,6 +96,8 @@ class RedisSync {
93
96
  onPubSubEvent;
94
97
  /** Callback for token revocation events (wired to ScopedTokenManager by server) */
95
98
  onTokenRevoked;
99
+ /** Optional KeyGroupManager for group sync (wired by server when groups are used) */
100
+ groupManager;
96
101
  constructor(redis, store, syncIntervalMs = 5000) {
97
102
  this.redis = redis;
98
103
  this.store = store;
@@ -108,11 +113,15 @@ class RedisSync {
108
113
  await this.redis.ping();
109
114
  console.log('[paygate:redis] Connected to Redis');
110
115
  await this.loadFromRedis();
116
+ await this.loadGroupsFromRedis();
111
117
  // Start periodic refresh (fallback for missed pub/sub messages)
112
118
  this.syncInterval = setInterval(() => {
113
119
  this.loadFromRedis().catch(err => {
114
120
  console.error('[paygate:redis] Sync error:', err.message);
115
121
  });
122
+ this.loadGroupsFromRedis().catch(err => {
123
+ console.error('[paygate:redis] Group sync error:', err.message);
124
+ });
116
125
  }, this.syncMs);
117
126
  // Start pub/sub subscriber if connection options available
118
127
  if (subscriberOpts) {
@@ -215,6 +224,13 @@ class RedisSync {
215
224
  }
216
225
  break;
217
226
  }
227
+ case 'group_updated':
228
+ case 'group_deleted':
229
+ case 'group_assignment_changed': {
230
+ // Reload all groups from Redis (groups are small, full reload is fine)
231
+ this.loadGroupsFromRedis().catch(() => { });
232
+ break;
233
+ }
218
234
  }
219
235
  }
220
236
  catch {
@@ -526,6 +542,7 @@ class RedisSync {
526
542
  fields.push('autoTopup', record.autoTopup ? JSON.stringify(record.autoTopup) : '');
527
543
  fields.push('autoTopupTodayCount', String(record.autoTopupTodayCount));
528
544
  fields.push('autoTopupLastResetDay', record.autoTopupLastResetDay);
545
+ fields.push('group', record.group || '');
529
546
  return fields;
530
547
  }
531
548
  hashToRecord(hash) {
@@ -557,6 +574,165 @@ class RedisSync {
557
574
  autoTopup: hash.autoTopup ? JSON.parse(hash.autoTopup) : undefined,
558
575
  autoTopupTodayCount: parseInt(hash.autoTopupTodayCount, 10) || 0,
559
576
  autoTopupLastResetDay: hash.autoTopupLastResetDay || new Date().toISOString().slice(0, 10),
577
+ group: hash.group || undefined,
578
+ };
579
+ }
580
+ // ─── Group Sync Operations ─────────────────────────────────────────────────
581
+ /**
582
+ * Save a group record to Redis. Fire-and-forget.
583
+ */
584
+ async saveGroup(group) {
585
+ try {
586
+ const redisKey = GROUP_PREFIX + group.id;
587
+ await this.redis.hset(redisKey, ...this.groupToHash(group));
588
+ await this.redis.command('SADD', GROUP_SET, group.id);
589
+ await this.publishEvent({ type: 'group_updated', key: group.id });
590
+ }
591
+ catch (err) {
592
+ console.error(`[paygate:redis] Save group error: ${err.message}`);
593
+ }
594
+ }
595
+ /**
596
+ * Delete a group from Redis. Fire-and-forget.
597
+ */
598
+ async deleteGroup(groupId) {
599
+ try {
600
+ await this.redis.command('DEL', GROUP_PREFIX + groupId);
601
+ await this.redis.command('SREM', GROUP_SET, groupId);
602
+ await this.publishEvent({ type: 'group_deleted', key: groupId });
603
+ }
604
+ catch (err) {
605
+ console.error(`[paygate:redis] Delete group error: ${err.message}`);
606
+ }
607
+ }
608
+ /**
609
+ * Save all group assignments to Redis as a single hash (apiKey → groupId).
610
+ * Fire-and-forget.
611
+ */
612
+ async saveGroupAssignments() {
613
+ if (!this.groupManager)
614
+ return;
615
+ try {
616
+ // Clear existing assignments
617
+ await this.redis.command('DEL', GROUP_ASSIGN_KEY);
618
+ // Rebuild from group manager
619
+ const serialized = this.groupManager.serialize();
620
+ if (serialized.assignments.length > 0) {
621
+ const fields = [];
622
+ for (const [apiKey, groupId] of serialized.assignments) {
623
+ fields.push(apiKey, groupId);
624
+ }
625
+ await this.redis.hset(GROUP_ASSIGN_KEY, ...fields);
626
+ }
627
+ await this.publishEvent({ type: 'group_assignment_changed', key: '' });
628
+ }
629
+ catch (err) {
630
+ console.error(`[paygate:redis] Save assignments error: ${err.message}`);
631
+ }
632
+ }
633
+ /**
634
+ * Load all groups and assignments from Redis into the local KeyGroupManager.
635
+ */
636
+ async loadGroupsFromRedis() {
637
+ if (!this.groupManager)
638
+ return;
639
+ try {
640
+ const groupIds = await this.redis.command('SMEMBERS', GROUP_SET);
641
+ if (!groupIds || groupIds.length === 0) {
642
+ // No groups in Redis — push local state up if we have any
643
+ if (this.groupManager.count > 0) {
644
+ await this.pushGroupsToRedis();
645
+ }
646
+ return;
647
+ }
648
+ // Load all group records as [id, record] tuples (matches serialize() format)
649
+ const groups = [];
650
+ for (const id of groupIds) {
651
+ const hash = await this.redis.hgetall(GROUP_PREFIX + id);
652
+ if (!hash || !hash.id)
653
+ continue;
654
+ const record = this.hashToGroup(hash);
655
+ if (record)
656
+ groups.push([id, record]);
657
+ }
658
+ // Load assignments
659
+ const assignHash = await this.redis.hgetall(GROUP_ASSIGN_KEY);
660
+ const assignments = [];
661
+ if (assignHash) {
662
+ for (const [apiKey, groupId] of Object.entries(assignHash)) {
663
+ assignments.push([apiKey, groupId]);
664
+ }
665
+ }
666
+ // Load into group manager
667
+ this.groupManager.load({ groups, assignments });
668
+ if (groups.length > 0) {
669
+ console.log(`[paygate:redis] Synced ${groups.length} group(s) from Redis`);
670
+ }
671
+ }
672
+ catch (err) {
673
+ console.error(`[paygate:redis] Load groups error: ${err.message}`);
674
+ }
675
+ }
676
+ /**
677
+ * Push local groups to Redis (used when Redis is empty on first connect).
678
+ */
679
+ async pushGroupsToRedis() {
680
+ if (!this.groupManager)
681
+ return;
682
+ const serialized = this.groupManager.serialize();
683
+ if (serialized.groups.length === 0)
684
+ return;
685
+ for (const [id, group] of serialized.groups) {
686
+ const redisKey = GROUP_PREFIX + id;
687
+ await this.redis.hset(redisKey, ...this.groupToHash(group));
688
+ await this.redis.command('SADD', GROUP_SET, id);
689
+ }
690
+ if (serialized.assignments.length > 0) {
691
+ const fields = [];
692
+ for (const [apiKey, groupId] of serialized.assignments) {
693
+ fields.push(apiKey, groupId);
694
+ }
695
+ await this.redis.hset(GROUP_ASSIGN_KEY, ...fields);
696
+ }
697
+ console.log(`[paygate:redis] Pushed ${serialized.groups.length} local group(s) to Redis`);
698
+ }
699
+ // ─── Group Serialization ───────────────────────────────────────────────────
700
+ groupToHash(group) {
701
+ return [
702
+ 'id', group.id,
703
+ 'name', group.name,
704
+ 'description', group.description,
705
+ 'allowedTools', JSON.stringify(group.allowedTools),
706
+ 'deniedTools', JSON.stringify(group.deniedTools),
707
+ 'rateLimitPerMin', String(group.rateLimitPerMin),
708
+ 'toolPricing', JSON.stringify(group.toolPricing),
709
+ 'quota', group.quota ? JSON.stringify(group.quota) : '',
710
+ 'ipAllowlist', JSON.stringify(group.ipAllowlist),
711
+ 'defaultCredits', String(group.defaultCredits),
712
+ 'maxSpendingLimit', String(group.maxSpendingLimit),
713
+ 'tags', JSON.stringify(group.tags),
714
+ 'createdAt', group.createdAt,
715
+ 'active', group.active ? '1' : '0',
716
+ ];
717
+ }
718
+ hashToGroup(hash) {
719
+ if (!hash.id)
720
+ return null;
721
+ return {
722
+ id: hash.id,
723
+ name: hash.name || '',
724
+ description: hash.description || '',
725
+ allowedTools: hash.allowedTools ? JSON.parse(hash.allowedTools) : [],
726
+ deniedTools: hash.deniedTools ? JSON.parse(hash.deniedTools) : [],
727
+ rateLimitPerMin: parseInt(hash.rateLimitPerMin, 10) || 0,
728
+ toolPricing: hash.toolPricing ? JSON.parse(hash.toolPricing) : {},
729
+ quota: hash.quota ? JSON.parse(hash.quota) : undefined,
730
+ ipAllowlist: hash.ipAllowlist ? JSON.parse(hash.ipAllowlist) : [],
731
+ defaultCredits: parseInt(hash.defaultCredits, 10) || 0,
732
+ maxSpendingLimit: parseInt(hash.maxSpendingLimit, 10) || 0,
733
+ tags: hash.tags ? JSON.parse(hash.tags) : {},
734
+ createdAt: hash.createdAt || new Date().toISOString(),
735
+ active: hash.active !== '0',
560
736
  };
561
737
  }
562
738
  }