feat(api-keys): restrict keys to source IP addresses

A key can be limited to IP addresses or CIDR networks (e.g. the Wabenhain
server); a stolen key is useless from anywhere else. Requests from other
addresses get 403 "not allowed for this IP" and a log line. The admin UI
asks for the addresses when creating a key, warns when a key works from
anywhere, shows the last source address and can take it over into the
allowlist - changing the list needs no new key (PATCH /api-keys/:id).

Also: a key can only carry scopes its creator holds.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-01 02:41:46 +02:00
1 parent 9ae1d96a32
commit f1dc0de3e2
16 files changed
+463 -10

No files matched your search

@@ -1,9 +1,10 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { RequirePermission } from '../auth/decorators/require-permission.decorator';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import type { AuthenticatedUser } from '../auth/types/authenticated-user';
import { CreateApiKeyDto } from './dto/create-api-key.dto';
import { UpdateApiKeyDto } from './dto/update-api-key.dto';
import {
ApiKeySummary,
ApiKeysService,
@@ -31,6 +32,16 @@ export class ApiKeysController {
return this.apiKeys.create(dto, actor.id);
}
@RequirePermission('api-keys:manage')
@Patch(':id')
updateAllowedIps(
@Param('id') id: string,
@Body() dto: UpdateApiKeyDto,
@CurrentUser() actor: AuthenticatedUser,
): Promise<ApiKeySummary> {
return this.apiKeys.updateAllowedIps(id, dto, actor.id);
}
@RequirePermission('api-keys:manage')
@Post(':id/revoke')
revoke(