add check for username already taken

This commit is contained in:
rubikscraft 2022-09-04 20:21:12 +02:00
parent 864758f296
commit 887b80aee8
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
6 changed files with 87 additions and 8 deletions

View file

@ -189,6 +189,21 @@ export class UserDbService {
// Listing
public async checkUsername(username: string): AsyncFailable<{
available: boolean;
}> {
try {
const found = await this.usersRepository.findOne({
where: { username },
select: ['id'],
});
return { available: !found};
} catch (e) {
return Fail(FT.Database, e);
}
}
public async findByUsername(
username: string,
// Also fetch fields that aren't normally sent to the client

View file

@ -1,10 +1,7 @@
import { Body, Controller, Get, Logger, Post } from '@nestjs/common';
import {
Body,
Controller,
Get, Logger,
Post
} from '@nestjs/common';
import {
UserCheckNameRequest,
UserCheckNameResponse,
UserLoginResponse,
UserMePermissionsResponse,
UserMeResponse,
@ -56,6 +53,17 @@ export class UserController {
return EUserBackend2EUser(user);
}
@Post('checkname')
@Returns(UserCheckNameResponse)
@RequiredPermissions(Permission.UserRegister)
async checkName(
@Body() checkName: UserCheckNameRequest,
): Promise<UserCheckNameResponse> {
return ThrowIfFailed(
await this.usersService.checkUsername(checkName.username),
);
}
@Get('me')
@Returns(UserMeResponse)
@RequiredPermissions(Permission.UserKeepLogin)
@ -76,7 +84,9 @@ export class UserController {
async refresh(
@ReqUserID() userid: string,
): Promise<UserMePermissionsResponse> {
const permissions = ThrowIfFailed(await this.usersService.getPermissions(userid));
const permissions = ThrowIfFailed(
await this.usersService.getPermissions(userid),
);
return { permissions };
}

View file

@ -23,6 +23,8 @@ export const CreateUsernameError = (
return 'Username is too long';
case 'pattern':
return 'Username can only contain letters and numbers';
case 'unavailable':
return 'Username is already taken';
default:
return 'Invalid username';
}

View file

@ -3,6 +3,7 @@ import { Router } from '@angular/router';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { HasFailed } from 'picsur-shared/dist/types';
import { debounceTime } from 'rxjs';
import { UserPassModel } from 'src/app/models/forms-dto/userpass.dto';
import { PermissionService } from 'src/app/services/api/permission.service';
import { UserService } from 'src/app/services/api/user.service';
@ -36,6 +37,20 @@ export class RegisterComponent implements OnInit {
history.replaceState(null, '');
}
this.model.username.valueChanges
.pipe(debounceTime(500))
.subscribe(async (value) => {
if (this.model.username.errors || value === null) return;
const result = await this.userService.checkNameIsAvailable(value);
if (HasFailed(result))
return this.errorService.showFailure(result, this.logger);
if (!result) {
this.model.username.setErrors({ unavailable: true });
}
});
this.onPermissions();
}

View file

@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import jwt_decode from 'jwt-decode';
import {
UserCheckNameRequest,
UserCheckNameResponse,
UserLoginRequest,
UserLoginResponse,
UserMeResponse,
@ -9,7 +11,13 @@ import {
} from 'picsur-shared/dist/dto/api/user.dto';
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
Open
} from 'picsur-shared/dist/types';
import { BehaviorSubject } from 'rxjs';
import { Logger } from '../logger/logger.service';
import { KeyService } from '../storage/key.service';
@ -86,6 +94,20 @@ export class UserService {
return user;
}
public async checkNameIsAvailable(username: string): AsyncFailable<boolean> {
return Open(
await this.api.post(
UserCheckNameRequest,
UserCheckNameResponse,
'/api/user/checkname',
{
username,
},
),
'available',
);
}
public async register(
username: string,
password: string,

View file

@ -30,6 +30,21 @@ export class UserRegisterResponse extends createZodDto(
UserRegisterResponseSchema,
) {}
// UserCheckName
export const UserCheckNameRequestSchema = z.object({
username: IsUsername(),
});
export class UserCheckNameRequest extends createZodDto(
UserCheckNameRequestSchema,
) {}
export const UserCheckNameResponseSchema = z.object({
available: z.boolean(),
});
export class UserCheckNameResponse extends createZodDto(
UserCheckNameResponseSchema,
) {}
// UserMe
export const UserMeResponseSchema = z.object({
user: EUserSchema,