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
@@ -190,4 +190,42 @@ describe('API keys (e2e)', () => {
|
||||
.send({ name: 'x', scopes: ['shop-sync:read'] })
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it('works only from allowed addresses and records where it was used', async () => {
|
||||
const elsewhere = (
|
||||
await createKey({
|
||||
name: `Elsewhere ${suffix}`,
|
||||
scopes: ['shop-sync:read'],
|
||||
allowedIps: ['203.0.113.7'],
|
||||
}).expect(201)
|
||||
).body as CreatedKeyBody;
|
||||
await request(server())
|
||||
.get('/api/v1/shop-sync')
|
||||
.set('Authorization', `Bearer ${elsewhere.token}`)
|
||||
.expect(403);
|
||||
|
||||
// Allow loopback (where this test runs) afterwards, without a new key.
|
||||
await request(server())
|
||||
.patch(`/api/v1/api-keys/${elsewhere.apiKey.id}`)
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({ allowedIps: ['127.0.0.0/8', '::1'] })
|
||||
.expect(200);
|
||||
await request(server())
|
||||
.get('/api/v1/shop-sync')
|
||||
.set('Authorization', `Bearer ${elsewhere.token}`)
|
||||
.expect(200);
|
||||
|
||||
const stored = await prisma.apiKey.findUniqueOrThrow({
|
||||
where: { id: elsewhere.apiKey.id },
|
||||
});
|
||||
expect(stored.lastUsedIp).toMatch(/127\.0\.0\.1|::1/);
|
||||
});
|
||||
|
||||
it('refuses an allowlist entry that is not an address or network', async () => {
|
||||
await createKey({
|
||||
name: `Bad allowlist ${suffix}`,
|
||||
scopes: ['shop-sync:read'],
|
||||
allowedIps: ['example.com'],
|
||||
}).expect(400);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user