AI Implementation feature(857): Authenticated Shell: Header, Quick Tabs and Change Password (#14)
This commit was merged in pull request #14.
This commit is contained in:
@@ -2,6 +2,8 @@ import {
|
||||
Injectable,
|
||||
OnModuleInit,
|
||||
Logger,
|
||||
Inject,
|
||||
forwardRef,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
@@ -16,10 +18,11 @@ 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, RegisterDto } from './dto/auth.dto';
|
||||
import { ChangePasswordDto, 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';
|
||||
import { UsersRankService } from '../users/users-rank.service';
|
||||
|
||||
const REFRESH_TTL_SECONDS_DEFAULT = 7 * 24 * 60 * 60;
|
||||
|
||||
@@ -37,6 +40,7 @@ export class AuthService implements OnModuleInit {
|
||||
private readonly backoff: LoginBackoffService,
|
||||
private readonly registrationRateLimit: RegistrationRateLimitService,
|
||||
private readonly settings: SettingsService,
|
||||
private readonly rank: UsersRankService,
|
||||
) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
@@ -177,6 +181,64 @@ export class AuthService implements OnModuleInit {
|
||||
});
|
||||
}
|
||||
|
||||
async getMe(userId: string): Promise<{ id: string; username: string; role: 'admin' | 'player'; rank: number | null; points: number }> {
|
||||
const user = await this.users.findOne({ where: { id: userId } });
|
||||
if (!user) throw new ApiError(ERROR_CODES.NOT_FOUND, 'User not found', 404);
|
||||
const rankInfo = await this.rank.rankOfUser(this.dataSource.manager, user.id);
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
rank: rankInfo.rank,
|
||||
points: rankInfo.points,
|
||||
};
|
||||
}
|
||||
|
||||
async changePassword(userId: string, dto: ChangePasswordDto): Promise<void> {
|
||||
if (dto.newPassword !== dto.confirmNewPassword) {
|
||||
throw new ApiError(ERROR_CODES.PASSWORDS_DO_NOT_MATCH, 'Passwords do not match', 400);
|
||||
}
|
||||
if (dto.oldPassword === dto.newPassword) {
|
||||
throw new ApiError(ERROR_CODES.PASSWORD_POLICY, 'New password must differ from the old one', 400);
|
||||
}
|
||||
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const user = await manager.findOne(UserEntity, { where: { id: userId } });
|
||||
if (!user) throw new ApiError(ERROR_CODES.NOT_FOUND, 'User not found', 404);
|
||||
|
||||
const ok = await argon2.verify(user.passwordHash, dto.oldPassword);
|
||||
if (!ok) {
|
||||
throw new ApiError(ERROR_CODES.INVALID_OLD_PASSWORD, 'Old password is incorrect', 401);
|
||||
}
|
||||
|
||||
try {
|
||||
validatePassword(dto.newPassword, this.config);
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
throw new ApiError(ERROR_CODES.PASSWORD_POLICY, e.message, 400);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
user.passwordHash = await argon2.hash(dto.newPassword, {
|
||||
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'),
|
||||
});
|
||||
await manager.save(user);
|
||||
|
||||
// Invalidate all existing refresh tokens for this user.
|
||||
await manager
|
||||
.createQueryBuilder()
|
||||
.update(RefreshTokenEntity)
|
||||
.set({ revokedAt: new Date().toISOString() })
|
||||
.where('userId = :userId', { userId: user.id })
|
||||
.andWhere('revokedAt IS NULL')
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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