Refactor user validation
This commit is contained in:
parent
8c88c5f24e
commit
581be5921b
61
frontend/src/app/models/forms/default-validators.ts
Normal file
61
frontend/src/app/models/forms/default-validators.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { ValidationErrors, Validators } from '@angular/forms';
|
||||
|
||||
// Match this with user entity in shared lib
|
||||
// (Security is not handled here, this is only for the user)
|
||||
|
||||
function errorsToError(errors: ValidationErrors | null): string {
|
||||
if (errors) {
|
||||
const error = Object.keys(errors)[0];
|
||||
return error;
|
||||
}
|
||||
return 'unkown';
|
||||
}
|
||||
|
||||
export const UsernameValidators = [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
Validators.maxLength(32),
|
||||
Validators.pattern('^[a-zA-Z0-9]+$'),
|
||||
];
|
||||
|
||||
export const CreateUsernameError = (
|
||||
errors: ValidationErrors | null
|
||||
): string => {
|
||||
const error = errorsToError(errors);
|
||||
switch (error) {
|
||||
case 'required':
|
||||
return 'Username is required';
|
||||
case 'minlength':
|
||||
return 'Username is too short';
|
||||
case 'maxlength':
|
||||
return 'Username is too long';
|
||||
case 'pattern':
|
||||
return 'Username can only contain letters and numbers';
|
||||
default:
|
||||
return 'Invalid username';
|
||||
}
|
||||
};
|
||||
|
||||
export const PasswordValidators = [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
Validators.maxLength(1024),
|
||||
];
|
||||
|
||||
export const CreatePasswordError = (
|
||||
errors: ValidationErrors | null
|
||||
): string => {
|
||||
const error = errorsToError(errors);
|
||||
switch (error) {
|
||||
case 'required':
|
||||
return 'Password is required';
|
||||
case 'minlength':
|
||||
return 'Password is too short';
|
||||
case 'maxlength':
|
||||
return 'Password is too long';
|
||||
case 'compare':
|
||||
return 'Password does not match';
|
||||
default:
|
||||
return 'Invalid password';
|
||||
}
|
||||
};
|
|
@ -1,43 +1,29 @@
|
|||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||
import {
|
||||
CreatePasswordError,
|
||||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators
|
||||
} from './default-validators';
|
||||
import { UserPassModel } from './userpass';
|
||||
|
||||
export class LoginControl {
|
||||
public username = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
]);
|
||||
|
||||
public password = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
]);
|
||||
public username = new FormControl('', UsernameValidators);
|
||||
public password = new FormControl('', PasswordValidators);
|
||||
|
||||
public get usernameError() {
|
||||
return this.username.hasError('required')
|
||||
? 'Username is required'
|
||||
: this.username.hasError('minlength')
|
||||
? 'Username is too short'
|
||||
: '';
|
||||
return CreateUsernameError(this.username.errors);
|
||||
}
|
||||
|
||||
public get passwordError() {
|
||||
return this.password.hasError('required')
|
||||
? 'Password is required'
|
||||
: this.password.hasError('minlength')
|
||||
? 'Password is too short'
|
||||
: '';
|
||||
return CreatePasswordError(this.password.errors);
|
||||
}
|
||||
|
||||
public getData(): Failable<UserPassModel> {
|
||||
if (this.username.errors || this.password.errors) {
|
||||
if (this.username.errors || this.password.errors)
|
||||
return Fail('Invalid username or password');
|
||||
} else {
|
||||
return {
|
||||
username: this.username.value,
|
||||
password: this.password.value,
|
||||
};
|
||||
}
|
||||
else return this.getRawData();
|
||||
}
|
||||
|
||||
public getRawData(): UserPassModel {
|
||||
|
|
|
@ -1,49 +1,32 @@
|
|||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||
import { Compare } from './compare.validator';
|
||||
import {
|
||||
CreatePasswordError,
|
||||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators
|
||||
} from './default-validators';
|
||||
import { UserPassModel } from './userpass';
|
||||
|
||||
export class RegisterControl {
|
||||
public username = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
]);
|
||||
|
||||
public password = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
]);
|
||||
|
||||
public username = new FormControl('', UsernameValidators);
|
||||
public password = new FormControl('', PasswordValidators);
|
||||
public passwordConfirm = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
...PasswordValidators,
|
||||
Compare(this.password),
|
||||
]);
|
||||
|
||||
public get usernameError() {
|
||||
return this.username.hasError('required')
|
||||
? 'Username is required'
|
||||
: this.username.hasError('minlength')
|
||||
? 'Username is too short'
|
||||
: '';
|
||||
return CreateUsernameError(this.username.errors);
|
||||
}
|
||||
|
||||
public get passwordError() {
|
||||
return this.password.hasError('required')
|
||||
? 'Password is required'
|
||||
: this.password.hasError('minlength')
|
||||
? 'Password is too short'
|
||||
: '';
|
||||
return CreatePasswordError(this.password.errors);
|
||||
}
|
||||
|
||||
public get passwordConfirmError() {
|
||||
return this.passwordConfirm.hasError('required')
|
||||
? 'Password confirmation is required'
|
||||
: this.passwordConfirm.hasError('minlength')
|
||||
? 'Password confirmation is too short'
|
||||
: this.passwordConfirm.hasError('compare')
|
||||
? 'Password confirmation does not match'
|
||||
: '';
|
||||
return CreatePasswordError(this.passwordConfirm.errors);
|
||||
}
|
||||
|
||||
public getData(): Failable<UserPassModel> {
|
||||
|
@ -51,14 +34,9 @@ export class RegisterControl {
|
|||
this.username.errors ||
|
||||
this.password.errors ||
|
||||
this.passwordConfirm.errors
|
||||
) {
|
||||
)
|
||||
return Fail('Invalid username or password');
|
||||
} else {
|
||||
return {
|
||||
username: this.username.value,
|
||||
password: this.password.value,
|
||||
};
|
||||
}
|
||||
else return this.getRawData();
|
||||
}
|
||||
|
||||
public getRawData(): UserPassModel {
|
||||
|
|
|
@ -1,19 +1,30 @@
|
|||
<h1>Users</h1>
|
||||
|
||||
<table mat-table [dataSource]="dataSubject">
|
||||
<mat-table [dataSource]="dataSubject" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef>ID</th>
|
||||
<td mat-cell *matCellDef="let user">{{ user.id }}</td>
|
||||
<mat-header-cell *matHeaderCellDef>ID</mat-header-cell>
|
||||
<mat-cell *matCellDef="let user">{{ user.id }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="username">
|
||||
<th mat-header-cell *matHeaderCellDef>Username</th>
|
||||
<td mat-cell *matCellDef="let user">{{ user.username }}</td>
|
||||
<mat-header-cell *matHeaderCellDef>Username</mat-header-cell>
|
||||
<mat-cell *matCellDef="let user">{{ user.username }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
|
||||
<mat-cell *matCellDef="let user">
|
||||
<button mat-icon-button>
|
||||
<mat-icon aria-label="Edit">edit</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns">
|
||||
pog
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator
|
||||
color="accent"
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
table {
|
||||
mat-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-column-actions {
|
||||
justify-content: end;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { UserManageService } from 'src/app/services/api/usermanage.service';
|
|||
styleUrls: ['./settings-users.component.scss'],
|
||||
})
|
||||
export class SettingsUsersComponent implements OnInit {
|
||||
public readonly displayedColumns: string[] = ['id', 'username'];
|
||||
public readonly displayedColumns: string[] = ['id', 'username', 'actions'];
|
||||
public readonly pageSizeOptions: number[] = [5, 10, 25, 100];
|
||||
public readonly startingPageSize = this.pageSizeOptions[2];
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { SettingsUsersComponent } from './settings-users.component';
|
||||
|
@ -9,6 +11,8 @@ import { SettingsUsersRoutingModule } from './settings-users.routing.module';
|
|||
imports: [
|
||||
CommonModule,
|
||||
SettingsUsersRoutingModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
],
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray, IsDefined,
|
||||
IsEnum, IsNotEmpty, IsString,
|
||||
IsEnum, IsString,
|
||||
ValidateNested
|
||||
} from 'class-validator';
|
||||
import { EUser } from '../../entities/user.entity';
|
||||
import { EUser, SimpleUser } from '../../entities/user.entity';
|
||||
import { Permissions, PermissionsList } from '../permissions';
|
||||
|
||||
// Api
|
||||
|
||||
// UserLogin
|
||||
export class UserLoginRequest {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
username: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
||||
export class UserLoginRequest extends SimpleUser {}
|
||||
|
||||
export class UserLoginResponse {
|
||||
@IsString()
|
||||
|
@ -27,15 +19,7 @@ export class UserLoginResponse {
|
|||
}
|
||||
|
||||
// UserRegister
|
||||
export class UserRegisterRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
export class UserRegisterRequest extends SimpleUser {}
|
||||
|
||||
export class UserRegisterResponse extends EUser {}
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsDefined, IsInt, IsNotEmpty, IsString, Min, ValidateNested } from 'class-validator';
|
||||
import { EUser } from '../../entities/user.entity';
|
||||
import {
|
||||
IsArray,
|
||||
IsDefined,
|
||||
IsInt, IsString,
|
||||
Min,
|
||||
ValidateNested
|
||||
} from 'class-validator';
|
||||
import { EUser, SimpleUser, SimpleUsername } from '../../entities/user.entity';
|
||||
import { Roles } from '../roles.dto';
|
||||
|
||||
// UserList
|
||||
|
@ -35,43 +41,19 @@ export class UserListResponse {
|
|||
}
|
||||
|
||||
// UserCreate
|
||||
export class UserCreateRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class UserCreateRequest extends SimpleUser {}
|
||||
export class UserCreateResponse extends EUser {}
|
||||
|
||||
|
||||
// UserDelete
|
||||
export class UserDeleteRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
}
|
||||
|
||||
export class UserDeleteRequest extends SimpleUsername {}
|
||||
export class UserDeleteResponse extends EUser {}
|
||||
|
||||
// UserInfo
|
||||
export class UserInfoRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
}
|
||||
|
||||
export class UserInfoRequest extends SimpleUsername {}
|
||||
export class UserInfoResponse extends EUser {}
|
||||
|
||||
// UserUpdateRoles
|
||||
export class UserUpdateRolesRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username: string;
|
||||
|
||||
export class UserUpdateRolesRequest extends SimpleUsername {
|
||||
@IsArray()
|
||||
@IsDefined()
|
||||
@IsString({ each: true })
|
||||
|
|
|
@ -1,20 +1,40 @@
|
|||
import { Exclude } from 'class-transformer';
|
||||
import {
|
||||
IsArray, IsInt, IsNotEmpty,
|
||||
IsAlphanumeric,
|
||||
IsArray,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString
|
||||
IsString,
|
||||
Length
|
||||
} from 'class-validator';
|
||||
import { Roles } from '../dto/roles.dto';
|
||||
|
||||
export class EUser {
|
||||
// Match this with user validators in frontend
|
||||
// (Not security focused, but it tells the user what is wrong)
|
||||
|
||||
export class SimpleUsername {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(4, 32)
|
||||
@IsAlphanumeric()
|
||||
username: string;
|
||||
}
|
||||
|
||||
// This is a simple user object with just the username and unhashed password
|
||||
export class SimpleUser extends SimpleUsername {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(4, 1024)
|
||||
password: string;
|
||||
}
|
||||
|
||||
// Actual entity that goes in the db
|
||||
export class EUser extends SimpleUsername {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
id?: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
username: string;
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
roles: Roles;
|
||||
|
|
Loading…
Reference in a new issue