feat(invites): implement invite-based self-service registration backend

- V45 migration: invite_tokens + invite_token_group_ids tables
- InviteToken entity with @ElementCollection group IDs
- InviteService: code generation, validation, redemption (pessimistic lock prevents TOCTOU), revoke, list
- RateLimitInterceptor (Caffeine-backed, 10 req/min per IP) registered via WebMvcConfigurer
- AuthController: GET /api/auth/invite/{code} + POST /api/auth/register (both public)
- InviteController: GET/POST/DELETE /api/invites (ADMIN_USER permission)
- SecurityConfig: permitAll for new public auth endpoints
- ErrorCode: INVITE_NOT_FOUND, INVITE_EXHAUSTED, INVITE_REVOKED, INVITE_EXPIRED
- 36 new tests (InviteServiceTest, AuthControllerTest, InviteControllerTest)

Closes #269

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-19 00:42:43 +02:00
parent b4004fce56
commit 61fa35df67
18 changed files with 1181 additions and 4 deletions

View File

@@ -0,0 +1,22 @@
CREATE TABLE invite_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(10) UNIQUE NOT NULL,
label VARCHAR(255),
max_uses INTEGER,
use_count INTEGER NOT NULL DEFAULT 0,
prefill_first_name VARCHAR(255),
prefill_last_name VARCHAR(255),
prefill_email VARCHAR(255),
expires_at TIMESTAMP,
created_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
revoked BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX idx_invite_tokens_code ON invite_tokens(code);
CREATE TABLE invite_token_group_ids (
invite_token_id UUID NOT NULL REFERENCES invite_tokens(id),
group_id UUID NOT NULL,
PRIMARY KEY (invite_token_id, group_id)
);