diff --git a/maternal-app/maternal-app-backend/src/modules/auth/auth.module.ts b/maternal-app/maternal-app-backend/src/modules/auth/auth.module.ts index b68a8b2..f5345a6 100644 --- a/maternal-app/maternal-app-backend/src/modules/auth/auth.module.ts +++ b/maternal-app/maternal-app-backend/src/modules/auth/auth.module.ts @@ -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, diff --git a/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts b/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts index de7a14d..de480da 100644 --- a/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts +++ b/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts @@ -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, @InjectRepository(FamilyMember) private familyMemberRepository: Repository, + @InjectRepository(InviteCode) + private inviteCodeRepository: Repository, + @InjectRepository(InviteCodeUse) + private inviteCodeUseRepository: Repository, private jwtService: JwtService, private configService: ConfigService, private auditService: AuditService, ) {} async register(registerDto: RegisterDto): Promise { + // Check registration mode and validate invite code if required + const registrationMode = this.configService.get('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`, diff --git a/maternal-app/maternal-app-backend/src/modules/auth/dto/register.dto.ts b/maternal-app/maternal-app-backend/src/modules/auth/dto/register.dto.ts index 0b35cad..dae2e5e 100644 --- a/maternal-app/maternal-app-backend/src/modules/auth/dto/register.dto.ts +++ b/maternal-app/maternal-app-backend/src/modules/auth/dto/register.dto.ts @@ -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; }