feat(mcp): granular OAuth scopes and per-client rate limiting

- Split `media:read` into `geo:read` and `weather:read` scopes
- Add dedicated `atlas:read/write` scopes (previously under `places`)
- Add dedicated `todos:read/write` scopes (previously under `collab`)
- Rate limiting now keyed by userId+clientId instead of userId alone
- Bind MCP sessions to the OAuth client that created them
- Log MCP tool calls to audit log with clientId
- Invalidate all MCP sessions on addon state change
- Reduce session sweep interval from 10min to 1min
- Update all translations with new scope labels
This commit is contained in:
jubnl
2026-04-11 02:06:09 +02:00
parent 4670d4914c
commit 535c06bb3f
39 changed files with 1930 additions and 237 deletions
+5 -3
View File
@@ -33,6 +33,7 @@ interface PendingCode {
expiresAt: number;
}
const MAX_PENDING_CODES = 500;
const pendingCodes = new Map<string, PendingCode>();
setInterval(() => {
@@ -89,11 +90,11 @@ function timingSafeEqualHex(a: string, b: string): boolean {
}
function generateAccessToken(): string {
return 'trekoa_' + randomBytes(24).toString('hex');
return 'trekoa_' + randomBytes(32).toString('hex');
}
function generateRefreshToken(): string {
return 'trekrf_' + randomBytes(24).toString('hex');
return 'trekrf_' + randomBytes(32).toString('hex');
}
// ---------------------------------------------------------------------------
@@ -244,7 +245,8 @@ export function createAuthCode(params: {
scopes: string[];
codeChallenge: string;
codeChallengeMethod: 'S256';
}): string {
}): string | null {
if (pendingCodes.size >= MAX_PENDING_CODES) return null;
const rawCode = randomBytes(32).toString('hex');
pendingCodes.set(rawCode, { ...params, expiresAt: Date.now() + AUTH_CODE_TTL_MS });
return rawCode;