AI Implementation feature(823): Landing Page and Login/Register Modal (#11)
This commit was merged in pull request #11.
This commit is contained in:
@@ -16,8 +16,10 @@ import { ApiError } from '../../common/errors/api-error';
|
||||
import { ERROR_CODES } from '../../common/errors/error-codes';
|
||||
import { LoginBackoffService } from '../../common/services/login-backoff.service';
|
||||
import { RegistrationRateLimitService } from '../../common/services/registration-rate-limit.service';
|
||||
import { LoginDto, LoginResponse } from './dto/auth.dto';
|
||||
import { LoginDto, LoginResponse, RegisterDto } from './dto/auth.dto';
|
||||
import { validatePassword } from '../../common/utils/password-policy';
|
||||
import { SettingsService } from '../settings/settings.module';
|
||||
import { SETTINGS_KEYS } from '../../config/env.schema';
|
||||
|
||||
const REFRESH_TTL_SECONDS_DEFAULT = 7 * 24 * 60 * 60;
|
||||
|
||||
@@ -34,6 +36,7 @@ export class AuthService implements OnModuleInit {
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly backoff: LoginBackoffService,
|
||||
private readonly registrationRateLimit: RegistrationRateLimitService,
|
||||
private readonly settings: SettingsService,
|
||||
) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
@@ -130,6 +133,52 @@ export class AuthService implements OnModuleInit {
|
||||
});
|
||||
}
|
||||
|
||||
async registerPlayer(dto: RegisterDto, ip: string): Promise<LoginResponse> {
|
||||
validatePassword(dto.password, this.config);
|
||||
|
||||
if (!this.registrationRateLimit.isAllowed(ip)) {
|
||||
throw new ApiError(
|
||||
ERROR_CODES.RATE_LIMITED,
|
||||
'Too many registration attempts; please wait a minute before trying again.',
|
||||
429,
|
||||
);
|
||||
}
|
||||
|
||||
const enabled = (await this.settings.get(SETTINGS_KEYS.REGISTRATIONS_ENABLED, 'false')) === 'true';
|
||||
if (!enabled) {
|
||||
throw new ApiError(
|
||||
ERROR_CODES.REGISTRATIONS_DISABLED,
|
||||
'Registrations are currently disabled',
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const existing = await manager.findOne(UserEntity, { where: { username: dto.username } });
|
||||
if (existing) {
|
||||
throw new ApiError(ERROR_CODES.USERNAME_TAKEN, 'Username already taken', 409);
|
||||
}
|
||||
|
||||
const passwordHash = await argon2.hash(dto.password, {
|
||||
type: argon2.argon2id,
|
||||
memoryCost: this.config.get<number>('ARGON2_MEMORY_COST'),
|
||||
timeCost: this.config.get<number>('ARGON2_TIME_COST'),
|
||||
parallelism: this.config.get<number>('ARGON2_PARALLELISM'),
|
||||
});
|
||||
const user = manager.create(UserEntity, {
|
||||
id: uuid(),
|
||||
username: dto.username,
|
||||
passwordHash,
|
||||
role: 'player',
|
||||
status: 'enabled',
|
||||
});
|
||||
await manager.save(user);
|
||||
|
||||
this.registrationRateLimit.record(ip);
|
||||
return this.mintSession(manager, user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint a fresh access JWT and a new refresh token row inside the supplied
|
||||
* transaction. The refresh token (raw) is returned alongside the access
|
||||
|
||||
Reference in New Issue
Block a user