feat(backend): JIT-provision the local User on first Authentik login
AuthentikStrategy no longer rejects a valid token whose user has no local row — it creates the User from the token claims (given_name/family_name/ email) via the new shared resolveOrProvisionAuthentikUser helper, which is race-safe (P2002 -> re-read) and captures the User to the sync log. The WS token path (TokenVerificationService.verifyAuthentik) and OnboardingService now use the same helper, removing three copies of the lookup/create logic. A provisioned user still has no Membership and therefore no rights: LT role assignment from Authentik groups is the remaining gap; Verantwortliche go through the onboarding approval flow. Tests: provision-user.spec.ts (existing/new/race/rethrow); npm test green at 51. Docs updated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,11 @@ import { JwtService } from '@nestjs/jwt';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import * as jwksRsa from 'jwks-rsa';
|
||||
import { PrismaClient } from '../prisma/prisma.module';
|
||||
import { SyncService } from '../sync/sync.service';
|
||||
import { AuthenticatedUser } from './authenticated-request';
|
||||
import { GuestJwtPayload } from './guest-auth.service';
|
||||
import { TeamAuthService } from './team-auth.service';
|
||||
import { resolveOrProvisionAuthentikUser } from './provision-user';
|
||||
|
||||
/// Verifies raw bearer tokens outside the HTTP/passport pipeline, needed for
|
||||
/// the WebSocket handshake where AuthGuard('authentik'|'guest') don't apply.
|
||||
@@ -20,6 +22,7 @@ export class TokenVerificationService {
|
||||
private readonly prisma: PrismaClient,
|
||||
private readonly guestJwt: JwtService,
|
||||
private readonly teamAuth: TeamAuthService,
|
||||
private readonly sync: SyncService,
|
||||
) {
|
||||
this.issuerUrl = config.getOrThrow<string>('AUTHENTIK_ISSUER_URL');
|
||||
this.jwks = jwksRsa({ jwksUri: `${this.issuerUrl}/jwks/`, cache: true, rateLimit: true });
|
||||
@@ -60,15 +63,8 @@ export class TokenVerificationService {
|
||||
}
|
||||
|
||||
async verifyAuthentik(token: string): Promise<AuthenticatedUser> {
|
||||
const { sub } = await this.verifyAuthentikClaims(token);
|
||||
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { authentikSub: sub },
|
||||
include: { memberships: { where: { status: 'ACTIVE' } } },
|
||||
});
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('User not provisioned locally yet');
|
||||
}
|
||||
const claims = await this.verifyAuthentikClaims(token);
|
||||
const user = await resolveOrProvisionAuthentikUser(this.prisma, this.sync, claims);
|
||||
return {
|
||||
userId: user.id,
|
||||
authentikSub: user.authentikSub,
|
||||
|
||||
Reference in New Issue
Block a user