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:
1 parent
9ae1d96a32
commit
f1dc0de3e2
16 files changed
+463
-10
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
ValidationArguments,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from 'class-validator';
|
||||
import { normalizeAllowlistEntry } from '../ip-allowlist';
|
||||
|
||||
@ValidatorConstraint({ name: 'allowedIp' })
|
||||
export class AllowedIpConstraint implements ValidatorConstraintInterface {
|
||||
validate(value: unknown): boolean {
|
||||
return typeof value === 'string' && normalizeAllowlistEntry(value) !== null;
|
||||
}
|
||||
|
||||
defaultMessage(args: ValidationArguments): string {
|
||||
return `${String(args.value)} ist keine IP-Adresse und kein Netz (z. B. 203.0.113.7 oder 203.0.113.0/24)`;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
ArrayMinSize,
|
||||
ArrayUnique,
|
||||
IsArray,
|
||||
@@ -11,8 +12,11 @@ import {
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
Validate,
|
||||
} from 'class-validator';
|
||||
import { API_KEY_SCOPES } from '../api-key-scopes';
|
||||
import { MAX_ALLOWED_IPS } from '../ip-allowlist';
|
||||
import { AllowedIpConstraint } from './allowed-ips.validator';
|
||||
|
||||
export class CreateApiKeyDto {
|
||||
@ApiProperty({
|
||||
@@ -39,4 +43,16 @@ export class CreateApiKeyDto {
|
||||
@Min(1)
|
||||
@Max(730)
|
||||
expiresInDays?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Erlaubte Absender (IP oder CIDR), z. B. die Adresse des Wabenhain-Servers',
|
||||
type: [String],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ArrayMaxSize(MAX_ALLOWED_IPS)
|
||||
@IsString({ each: true })
|
||||
@Validate(AllowedIpConstraint, { each: true })
|
||||
allowedIps?: string[];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ArrayMaxSize, IsArray, IsString, Validate } from 'class-validator';
|
||||
import { MAX_ALLOWED_IPS } from '../ip-allowlist';
|
||||
import { AllowedIpConstraint } from './allowed-ips.validator';
|
||||
|
||||
export class UpdateApiKeyDto {
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Erlaubte Absender (IP oder CIDR); leere Liste = von überall nutzbar',
|
||||
type: [String],
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayMaxSize(MAX_ALLOWED_IPS)
|
||||
@IsString({ each: true })
|
||||
@Validate(AllowedIpConstraint, { each: true })
|
||||
allowedIps!: string[];
|
||||
}
|
||||
Reference in new issue
Block a user