feat: Add invite code validation to user registration flow
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
- Added InviteCode and InviteCodeUse entity imports to auth.service.ts and auth.module.ts - Added inviteCode field to RegisterDto as optional string - Implemented registration mode check (invite_only vs public) - Added invite code validation logic at registration: * Check if code exists and is active * Validate expiration date * Verify usage limit not exceeded - Record invite code usage after successful registration: * Increment usage counter * Create invite_code_uses record with user details * Add audit logging for invite code usage - Registration now enforces REGISTRATION_MODE environment variable - Users get clear error messages for invalid/expired/used-up invite codes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
Family,
|
||||
FamilyMember,
|
||||
} from '../../database/entities';
|
||||
import { InviteCode, InviteCodeUse } from '../invite-codes/invite-codes.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -33,6 +34,8 @@ import {
|
||||
Family,
|
||||
FamilyMember,
|
||||
WebAuthnCredential,
|
||||
InviteCode,
|
||||
InviteCodeUse,
|
||||
]),
|
||||
PassportModule,
|
||||
CommonModule,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
AuditAction,
|
||||
EntityType,
|
||||
} from '../../database/entities';
|
||||
import { InviteCode, InviteCodeUse } from '../invite-codes/invite-codes.entity';
|
||||
import { RegisterDto } from './dto/register.dto';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto';
|
||||
@@ -43,12 +44,49 @@ export class AuthService {
|
||||
private familyRepository: Repository<Family>,
|
||||
@InjectRepository(FamilyMember)
|
||||
private familyMemberRepository: Repository<FamilyMember>,
|
||||
@InjectRepository(InviteCode)
|
||||
private inviteCodeRepository: Repository<InviteCode>,
|
||||
@InjectRepository(InviteCodeUse)
|
||||
private inviteCodeUseRepository: Repository<InviteCodeUse>,
|
||||
private jwtService: JwtService,
|
||||
private configService: ConfigService,
|
||||
private auditService: AuditService,
|
||||
) {}
|
||||
|
||||
async register(registerDto: RegisterDto): Promise<AuthResponse> {
|
||||
// Check registration mode and validate invite code if required
|
||||
const registrationMode = this.configService.get<string>('REGISTRATION_MODE', 'invite_only');
|
||||
const requireInviteCode = registrationMode === 'invite_only';
|
||||
|
||||
let validatedInviteCode: InviteCode | null = null;
|
||||
|
||||
if (requireInviteCode) {
|
||||
if (!registerDto.inviteCode) {
|
||||
throw new BadRequestException(
|
||||
'An invite code is required to register. Please contact an administrator for an invite code.',
|
||||
);
|
||||
}
|
||||
|
||||
// Validate the invite code
|
||||
validatedInviteCode = await this.inviteCodeRepository.findOne({
|
||||
where: { code: registerDto.inviteCode, isActive: true },
|
||||
});
|
||||
|
||||
if (!validatedInviteCode) {
|
||||
throw new BadRequestException('Invalid or inactive invite code');
|
||||
}
|
||||
|
||||
// Check if the invite code has expired
|
||||
if (validatedInviteCode.expiresAt && new Date(validatedInviteCode.expiresAt) < new Date()) {
|
||||
throw new BadRequestException('This invite code has expired');
|
||||
}
|
||||
|
||||
// Check if the invite code has reached its maximum number of uses
|
||||
if (validatedInviteCode.maxUses && validatedInviteCode.uses >= validatedInviteCode.maxUses) {
|
||||
throw new BadRequestException('This invite code has reached its maximum number of uses');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await this.userRepository.findOne({
|
||||
where: { email: registerDto.email },
|
||||
@@ -99,6 +137,28 @@ export class AuthService {
|
||||
|
||||
const savedUser = await this.userRepository.save(user);
|
||||
|
||||
// Record invite code usage if applicable
|
||||
if (validatedInviteCode) {
|
||||
// Increment the usage counter
|
||||
validatedInviteCode.uses += 1;
|
||||
await this.inviteCodeRepository.save(validatedInviteCode);
|
||||
|
||||
// Record the invite code usage
|
||||
const inviteCodeUse = this.inviteCodeUseRepository.create({
|
||||
inviteCodeId: validatedInviteCode.id,
|
||||
usedBy: savedUser.id,
|
||||
userEmail: savedUser.email,
|
||||
userIp: null, // Could be passed from the request if needed
|
||||
userAgent: null, // Could be passed from the request if needed
|
||||
});
|
||||
|
||||
await this.inviteCodeUseRepository.save(inviteCodeUse);
|
||||
|
||||
this.logger.log(
|
||||
`User ${savedUser.email} registered using invite code ${validatedInviteCode.code}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Create default family for user
|
||||
const family = this.familyRepository.create({
|
||||
name: `${registerDto.name}'s Family`,
|
||||
|
||||
@@ -57,6 +57,10 @@ export class RegisterDto {
|
||||
@IsEmail()
|
||||
parentalEmail?: string; // Parent/guardian email for users 13-17
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
inviteCode?: string; // Invite code (required if REGISTRATION_MODE=invite_only)
|
||||
|
||||
@IsObject()
|
||||
deviceInfo: DeviceInfoDto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user