subaccountAccess.ts 1.6 KB

123456789101112131415161718192021222324252627
  1. /** Owner-scoped Bot endpoints. Keep native and administrative surfaces denied. */
  2. export function isWorkspaceAccountPath(method: string, path: string): boolean {
  3. const read = method === 'GET' || method === 'HEAD'
  4. if(read&&path==='/api/app/vault')return true
  5. if(method==='POST'&&/^\/api\/app\/vault\/(initialize|unlock|lock|rotate|backup|restore|entries|leases)$/.test(path))return true
  6. if(['PUT','DELETE'].includes(method)&&/^\/api\/app\/vault\/entries\/[0-9a-f-]{36}$/.test(path))return true
  7. if(method==='DELETE'&&/^\/api\/app\/vault\/leases\/[0-9a-f-]{36}$/.test(path))return true
  8. if (read && /^\/api\/app\/workspace\/(snapshot|projects|memories|memory-export|memory-jobs|collaboration)$/.test(path)) return true
  9. if (read && /^\/api\/app\/workspace\/memories\/[^/]+\/revisions$/.test(path)) return true
  10. return method === 'POST' && /^\/api\/app\/workspace\/(projects|memories|memories\/forget|memory-jobs\/[^/]+\/retry|collaboration\/[^/]+\/stop)$/.test(path)
  11. }
  12. /** Bound memory as well as request frequency; untrusted forwarded IPs are not used. */
  13. export class RegistrationLimiter {
  14. private readonly attempts = new Map<string, { count: number; expires: number }>()
  15. take(address: string, now = Date.now()): number {
  16. for (const [key, value] of this.attempts) if (value.expires <= now) this.attempts.delete(key)
  17. let entry = this.attempts.get(address)
  18. if (!entry) {
  19. if (this.attempts.size >= 10_000) return 60
  20. entry = { count: 0, expires: now + 15 * 60_000 }
  21. this.attempts.set(address, entry)
  22. }
  23. if (++entry.count > 10) return Math.ceil((entry.expires - now) / 1000)
  24. return 0
  25. }
  26. }