mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-12-22 15:01:37 +00:00
feat(backend): handle username always in lowercase
This should make all usernames of new users into lowercase. Usernames are also searched in the DB as lowercase. Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
9625900d1c
commit
0a8945d934
23 changed files with 99 additions and 58 deletions
|
@ -29,6 +29,7 @@ import { ConsoleLoggerService } from '../../../logger/console-logger.service';
|
|||
import { SessionState } from '../../../session/session.service';
|
||||
import { User } from '../../../users/user.entity';
|
||||
import { UsersService } from '../../../users/users.service';
|
||||
import { makeUsernameLowercase } from '../../../utils/username';
|
||||
import { LoginEnabledGuard } from '../../utils/login-enabled.guard';
|
||||
import { OpenApi } from '../../utils/openapi.decorator';
|
||||
import { RegistrationEnabledGuard } from '../../utils/registration-enabled.guard';
|
||||
|
@ -107,8 +108,8 @@ export class AuthController {
|
|||
@Param('ldapIdentifier') ldapIdentifier: string,
|
||||
@Body() loginDto: LdapLoginDto,
|
||||
): void {
|
||||
// There is no further testing needed as we only get to this point if LocalAuthGuard was successful
|
||||
request.session.username = loginDto.username;
|
||||
// There is no further testing needed as we only get to this point if LdapAuthGuard was successful
|
||||
request.session.username = makeUsernameLowercase(loginDto.username);
|
||||
request.session.authProvider = 'ldap';
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import { RevisionDto } from '../../../revisions/revision.dto';
|
|||
import { RevisionsService } from '../../../revisions/revisions.service';
|
||||
import { User } from '../../../users/user.entity';
|
||||
import { UsersService } from '../../../users/users.service';
|
||||
import { Username } from '../../../utils/username';
|
||||
import { GetNoteInterceptor } from '../../utils/get-note.interceptor';
|
||||
import { MarkdownBody } from '../../utils/markdown-body.decorator';
|
||||
import { OpenApi } from '../../utils/openapi.decorator';
|
||||
|
@ -203,7 +204,7 @@ export class NotesController {
|
|||
async setUserPermission(
|
||||
@RequestUser() user: User,
|
||||
@RequestNote() note: Note,
|
||||
@Param('userName') username: string,
|
||||
@Param('userName') username: Username,
|
||||
@Body('canEdit') canEdit: boolean,
|
||||
): Promise<NotePermissionsDto> {
|
||||
const permissionUser = await this.userService.getUserByUsername(username);
|
||||
|
@ -221,7 +222,7 @@ export class NotesController {
|
|||
async removeUserPermission(
|
||||
@RequestUser() user: User,
|
||||
@RequestNote() note: Note,
|
||||
@Param('userName') username: string,
|
||||
@Param('userName') username: Username,
|
||||
): Promise<NotePermissionsDto> {
|
||||
try {
|
||||
const permissionUser = await this.userService.getUserByUsername(username);
|
||||
|
@ -281,7 +282,7 @@ export class NotesController {
|
|||
async changeOwner(
|
||||
@RequestUser() user: User,
|
||||
@RequestNote() note: Note,
|
||||
@Body('newOwner') newOwner: string,
|
||||
@Body('newOwner') newOwner: Username,
|
||||
): Promise<NoteDto> {
|
||||
const owner = await this.userService.getUserByUsername(newOwner);
|
||||
return await this.noteService.toNoteDto(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -9,6 +9,7 @@ import { ApiTags } from '@nestjs/swagger';
|
|||
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
|
||||
import { UserInfoDto } from '../../../users/user-info.dto';
|
||||
import { UsersService } from '../../../users/users.service';
|
||||
import { Username } from '../../../utils/username';
|
||||
import { OpenApi } from '../../utils/openapi.decorator';
|
||||
|
||||
@ApiTags('users')
|
||||
|
@ -23,7 +24,7 @@ export class UsersController {
|
|||
|
||||
@Get(':username')
|
||||
@OpenApi(200)
|
||||
async getUser(@Param('username') username: string): Promise<UserInfoDto> {
|
||||
async getUser(@Param('username') username: Username): Promise<UserInfoDto> {
|
||||
return this.userService.toUserDto(
|
||||
await this.userService.getUserByUsername(username),
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -42,6 +42,7 @@ import { RevisionDto } from '../../../revisions/revision.dto';
|
|||
import { RevisionsService } from '../../../revisions/revisions.service';
|
||||
import { User } from '../../../users/user.entity';
|
||||
import { UsersService } from '../../../users/users.service';
|
||||
import { Username } from '../../../utils/username';
|
||||
import { GetNoteInterceptor } from '../../utils/get-note.interceptor';
|
||||
import { MarkdownBody } from '../../utils/markdown-body.decorator';
|
||||
import { OpenApi } from '../../utils/openapi.decorator';
|
||||
|
@ -264,7 +265,7 @@ export class NotesController {
|
|||
async setUserPermission(
|
||||
@RequestUser() user: User,
|
||||
@RequestNote() note: Note,
|
||||
@Param('userName') username: string,
|
||||
@Param('userName') username: Username,
|
||||
@Body('canEdit') canEdit: boolean,
|
||||
): Promise<NotePermissionsDto> {
|
||||
const permissionUser = await this.userService.getUserByUsername(username);
|
||||
|
@ -291,7 +292,7 @@ export class NotesController {
|
|||
async removeUserPermission(
|
||||
@RequestUser() user: User,
|
||||
@RequestNote() note: Note,
|
||||
@Param('userName') username: string,
|
||||
@Param('userName') username: Username,
|
||||
): Promise<NotePermissionsDto> {
|
||||
try {
|
||||
const permissionUser = await this.userService.getUserByUsername(username);
|
||||
|
@ -377,7 +378,7 @@ export class NotesController {
|
|||
async changeOwner(
|
||||
@RequestUser() user: User,
|
||||
@RequestNote() note: Note,
|
||||
@Body('newOwner') newOwner: string,
|
||||
@Body('newOwner') newOwner: Username,
|
||||
): Promise<NoteDto> {
|
||||
const owner = await this.userService.getUserByUsername(newOwner);
|
||||
return await this.noteService.toNoteDto(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -7,7 +7,7 @@ import { IsString } from 'class-validator';
|
|||
|
||||
export class LdapLoginDto {
|
||||
@IsString()
|
||||
username: string;
|
||||
username: string; // This is not of type Username, because LDAP server may use mixed case usernames
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -22,6 +22,7 @@ import authConfiguration, {
|
|||
import { NotInDBError } from '../../errors/errors';
|
||||
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
||||
import { UsersService } from '../../users/users.service';
|
||||
import { makeUsernameLowercase } from '../../utils/username';
|
||||
import { Identity } from '../identity.entity';
|
||||
import { IdentityService } from '../identity.service';
|
||||
import { ProviderType } from '../provider-type.enum';
|
||||
|
@ -85,7 +86,7 @@ export class LdapStrategy extends PassportStrategy(Strategy, 'ldap') {
|
|||
*/
|
||||
private loginWithLDAP(
|
||||
ldapConfig: LDAPConfig,
|
||||
username: string,
|
||||
username: string, // This is not of type Username, because LDAP server may use mixed case usernames
|
||||
password: string,
|
||||
doneCallBack: VerifiedCallback,
|
||||
): void {
|
||||
|
@ -146,7 +147,7 @@ export class LdapStrategy extends PassportStrategy(Strategy, 'ldap') {
|
|||
userId: string,
|
||||
ldapConfig: LDAPConfig,
|
||||
user: Record<string, string>,
|
||||
username: string,
|
||||
username: string, // This is not of type Username, because LDAP server may use mixed case usernames
|
||||
): void {
|
||||
this.identityService
|
||||
.getIdentityFromUserIdAndProviderType(userId, ProviderType.LDAP)
|
||||
|
@ -162,8 +163,9 @@ export class LdapStrategy extends PassportStrategy(Strategy, 'ldap') {
|
|||
.catch(async (error) => {
|
||||
if (error instanceof NotInDBError) {
|
||||
// The user/identity does not yet exist
|
||||
const usernameLowercase = makeUsernameLowercase(username); // This ensures ldap user can be given permission via usernames
|
||||
const newUser = await this.usersService.createUser(
|
||||
username,
|
||||
usernameLowercase,
|
||||
// if there is no displayName we use the username
|
||||
user[ldapConfig.displayNameField] ?? username,
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
|||
import { UserRelationEnum } from '../../users/user-relation.enum';
|
||||
import { User } from '../../users/user.entity';
|
||||
import { UsersService } from '../../users/users.service';
|
||||
import { Username } from '../../utils/username';
|
||||
import { IdentityService } from '../identity.service';
|
||||
|
||||
@Injectable()
|
||||
|
@ -31,7 +32,7 @@ export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
|
|||
logger.setContext(LocalStrategy.name);
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<User> {
|
||||
async validate(username: Username, password: string): Promise<User> {
|
||||
try {
|
||||
const user = await this.userService.getUserByUsername(username, [
|
||||
UserRelationEnum.IDENTITIES,
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { IsString } from 'class-validator';
|
||||
import { IsLowercase, IsString } from 'class-validator';
|
||||
|
||||
import { Username } from '../../utils/username';
|
||||
|
||||
export class LoginDto {
|
||||
@IsString()
|
||||
username: string;
|
||||
@IsLowercase()
|
||||
username: Username;
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { IsString } from 'class-validator';
|
||||
import { IsLowercase, IsString } from 'class-validator';
|
||||
|
||||
import { Username } from '../../utils/username';
|
||||
|
||||
export class RegisterDto {
|
||||
@IsString()
|
||||
username: string;
|
||||
@IsLowercase()
|
||||
username: Username;
|
||||
|
||||
@IsString()
|
||||
displayName: string;
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsDate, IsOptional, IsString } from 'class-validator';
|
||||
import { IsDate, IsLowercase, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
import { BaseDto } from '../utils/base.dto.';
|
||||
import { Username } from '../utils/username';
|
||||
|
||||
export class MediaUploadDto extends BaseDto {
|
||||
/**
|
||||
|
@ -41,6 +42,7 @@ export class MediaUploadDto extends BaseDto {
|
|||
* @example "testuser5"
|
||||
*/
|
||||
@IsString()
|
||||
@IsLowercase()
|
||||
@ApiProperty()
|
||||
username: string | null;
|
||||
username: Username | null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -8,20 +8,23 @@ import { Type } from 'class-transformer';
|
|||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsLowercase,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
import { BaseDto } from '../utils/base.dto.';
|
||||
import { Username } from '../utils/username';
|
||||
|
||||
export class NoteUserPermissionEntryDto extends BaseDto {
|
||||
/**
|
||||
* Username of the User this permission applies to
|
||||
*/
|
||||
@IsString()
|
||||
@IsLowercase()
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
username: Username;
|
||||
|
||||
/**
|
||||
* True if the user is allowed to edit the note
|
||||
|
@ -38,8 +41,9 @@ export class NoteUserPermissionUpdateDto {
|
|||
* @example "john.smith"
|
||||
*/
|
||||
@IsString()
|
||||
@IsLowercase()
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
username: Username;
|
||||
|
||||
/**
|
||||
* True if the user should be allowed to edit the note
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Mock } from 'ts-mockery';
|
|||
|
||||
import { Note } from '../../notes/note.entity';
|
||||
import { User } from '../../users/user.entity';
|
||||
import { Username } from '../../utils/username';
|
||||
import * as NameRandomizerModule from './random-word-lists/name-randomizer';
|
||||
import { RealtimeConnection } from './realtime-connection';
|
||||
import { RealtimeNote } from './realtime-note';
|
||||
|
@ -39,7 +40,7 @@ describe('websocket connection', () => {
|
|||
let mockedUser: User;
|
||||
let mockedMessageTransporter: MessageTransporter;
|
||||
|
||||
const mockedUserName = 'mockedUserName';
|
||||
const mockedUserName: Username = 'mocked-user-name';
|
||||
const mockedDisplayName = 'mockedDisplayName';
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -40,9 +40,9 @@ describe('RealtimeNoteService', () => {
|
|||
let clientWithoutReadWrite: RealtimeConnection;
|
||||
let deleteIntervalSpy: jest.SpyInstance;
|
||||
|
||||
const readWriteUsername = 'canReadWriteUser';
|
||||
const onlyReadUsername = 'canOnlyReadUser';
|
||||
const noAccessUsername = 'noReadWriteUser';
|
||||
const readWriteUsername = 'can-read-write-user';
|
||||
const onlyReadUsername = 'can-only-read-user';
|
||||
const noAccessUsername = 'no-read-write-user';
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
} from '@hedgedoc/commons';
|
||||
import { Listener } from 'eventemitter2';
|
||||
|
||||
import { Username } from '../../utils/username';
|
||||
|
||||
export type OtherAdapterCollector = () => RealtimeUserStatusAdapter[];
|
||||
|
||||
/**
|
||||
|
@ -20,7 +22,7 @@ export class RealtimeUserStatusAdapter {
|
|||
private readonly realtimeUser: RealtimeUser;
|
||||
|
||||
constructor(
|
||||
private readonly username: string | null,
|
||||
private readonly username: Username | null,
|
||||
private readonly displayName: string,
|
||||
private collectOtherAdapters: OtherAdapterCollector,
|
||||
private messageTransporter: MessageTransporter,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { Mock } from 'ts-mockery';
|
||||
|
||||
import { User } from '../../../users/user.entity';
|
||||
import { Username } from '../../../utils/username';
|
||||
import { RealtimeConnection } from '../realtime-connection';
|
||||
import { RealtimeNote } from '../realtime-note';
|
||||
import { RealtimeUserStatusAdapter } from '../realtime-user-status-adapter';
|
||||
|
@ -21,13 +22,13 @@ enum RealtimeUserState {
|
|||
WITH_READONLY,
|
||||
}
|
||||
|
||||
const MOCK_FALLBACK_USERNAME = 'mock';
|
||||
const MOCK_FALLBACK_USERNAME: Username = 'mock';
|
||||
|
||||
/**
|
||||
* Creates a mocked {@link RealtimeConnection realtime connection}.
|
||||
*/
|
||||
export class MockConnectionBuilder {
|
||||
private username: string | null;
|
||||
private username: Username | null;
|
||||
private displayName: string | undefined;
|
||||
private includeRealtimeUserStatus: RealtimeUserState =
|
||||
RealtimeUserState.WITHOUT;
|
||||
|
@ -50,7 +51,7 @@ export class MockConnectionBuilder {
|
|||
*
|
||||
* @param username the username of the mocked user. If this value is omitted then the builder will user a {@link MOCK_FALLBACK_USERNAME fallback}.
|
||||
*/
|
||||
public withLoggedInUser(username?: string): this {
|
||||
public withLoggedInUser(username?: Username): this {
|
||||
const newUsername = username ?? MOCK_FALLBACK_USERNAME;
|
||||
this.username = newUsername;
|
||||
this.displayName = newUsername;
|
||||
|
|
|
@ -41,6 +41,7 @@ import { Session } from '../../users/session.entity';
|
|||
import { User } from '../../users/user.entity';
|
||||
import { UsersModule } from '../../users/users.module';
|
||||
import { UsersService } from '../../users/users.service';
|
||||
import { Username } from '../../utils/username';
|
||||
import * as websocketConnectionModule from '../realtime-note/realtime-connection';
|
||||
import { RealtimeConnection } from '../realtime-note/realtime-connection';
|
||||
import { RealtimeNote } from '../realtime-note/realtime-note';
|
||||
|
@ -165,7 +166,7 @@ describe('Websocket gateway', () => {
|
|||
),
|
||||
);
|
||||
|
||||
const mockUsername = 'mockUsername';
|
||||
const mockUsername: Username = 'mock-username';
|
||||
jest
|
||||
.spyOn(sessionService, 'fetchUsernameForSessionId')
|
||||
.mockImplementation((sessionId: string) =>
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('SessionService', () => {
|
|||
let authConfigMock: AuthConfig;
|
||||
let typeormStoreConstructorMock: jest.SpyInstance;
|
||||
const mockedExistingSessionId = 'mockedExistingSessionId';
|
||||
const mockUsername = 'mockUser';
|
||||
const mockUsername = 'mock-user';
|
||||
const mockSecret = 'mockSecret';
|
||||
let sessionService: SessionService;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -19,10 +19,11 @@ import databaseConfiguration, {
|
|||
} from '../config/database.config';
|
||||
import { Session } from '../users/session.entity';
|
||||
import { HEDGEDOC_SESSION } from '../utils/session';
|
||||
import { Username } from '../utils/username';
|
||||
|
||||
export interface SessionState {
|
||||
cookie: unknown;
|
||||
username?: string;
|
||||
username?: Username;
|
||||
authProvider: string;
|
||||
}
|
||||
|
||||
|
@ -58,10 +59,10 @@ export class SessionService {
|
|||
* @param sessionId The session id for which the owning user should be found
|
||||
* @return A Promise that either resolves with the username or rejects with an error
|
||||
*/
|
||||
fetchUsernameForSessionId(sessionId: string): Promise<string | undefined> {
|
||||
fetchUsernameForSessionId(sessionId: string): Promise<Username | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.typeormStore.get(sessionId, (error?: Error, result?: SessionState) =>
|
||||
error || !result ? reject(error) : resolve(result.username),
|
||||
error || !result ? reject(error) : resolve(result.username as Username),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString } from 'class-validator';
|
||||
import { IsLowercase, IsString } from 'class-validator';
|
||||
|
||||
import { BaseDto } from '../utils/base.dto.';
|
||||
import { Username } from '../utils/username';
|
||||
|
||||
export class UserInfoDto extends BaseDto {
|
||||
/**
|
||||
|
@ -14,8 +15,9 @@ export class UserInfoDto extends BaseDto {
|
|||
* @example "john.smith"
|
||||
*/
|
||||
@IsString()
|
||||
@IsLowercase()
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
username: Username;
|
||||
|
||||
/**
|
||||
* The display name
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -20,6 +20,7 @@ import { HistoryEntry } from '../history/history-entry.entity';
|
|||
import { Identity } from '../identity/identity.entity';
|
||||
import { MediaUpload } from '../media/media-upload.entity';
|
||||
import { Note } from '../notes/note.entity';
|
||||
import { Username } from '../utils/username';
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
|
@ -29,7 +30,7 @@ export class User {
|
|||
@Column({
|
||||
unique: true,
|
||||
})
|
||||
username: string;
|
||||
username: Username;
|
||||
|
||||
@Column()
|
||||
displayName: string;
|
||||
|
@ -77,7 +78,7 @@ export class User {
|
|||
private constructor() {}
|
||||
|
||||
public static create(
|
||||
username: string,
|
||||
username: Username,
|
||||
displayName: string,
|
||||
): Omit<User, 'id' | 'createdAt' | 'updatedAt'> {
|
||||
const newUser = new User();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -9,6 +9,7 @@ import { Repository } from 'typeorm';
|
|||
|
||||
import { AlreadyInDBError, NotInDBError } from '../errors/errors';
|
||||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||
import { Username } from '../utils/username';
|
||||
import {
|
||||
FullUserInfoDto,
|
||||
UserInfoDto,
|
||||
|
@ -29,12 +30,12 @@ export class UsersService {
|
|||
/**
|
||||
* @async
|
||||
* Create a new user with a given username and displayName
|
||||
* @param username - the username the new user shall have
|
||||
* @param displayName - the display name the new user shall have
|
||||
* @param {Username} username - the username the new user shall have
|
||||
* @param {string} displayName - the display name the new user shall have
|
||||
* @return {User} the user
|
||||
* @throws {AlreadyInDBError} the username is already taken.
|
||||
*/
|
||||
async createUser(username: string, displayName: string): Promise<User> {
|
||||
async createUser(username: Username, displayName: string): Promise<User> {
|
||||
const user = User.create(username, displayName);
|
||||
try {
|
||||
return await this.userRepository.save(user);
|
||||
|
@ -77,12 +78,12 @@ export class UsersService {
|
|||
/**
|
||||
* @async
|
||||
* Get the user specified by the username
|
||||
* @param {string} username the username by which the user is specified
|
||||
* @param {Username} username the username by which the user is specified
|
||||
* @param {UserRelationEnum[]} [withRelations=[]] if the returned user object should contain certain relations
|
||||
* @return {User} the specified user
|
||||
*/
|
||||
async getUserByUsername(
|
||||
username: string,
|
||||
username: Username,
|
||||
withRelations: UserRelationEnum[] = [],
|
||||
): Promise<User> {
|
||||
const user = await this.userRepository.findOne({
|
||||
|
|
11
backend/src/utils/username.ts
Normal file
11
backend/src/utils/username.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export type Username = Lowercase<string>;
|
||||
|
||||
export function makeUsernameLowercase(username: string): Username {
|
||||
return username.toLowerCase() as Username;
|
||||
}
|
|
@ -16,12 +16,13 @@ import { RegisterDto } from '../../src/identity/local/register.dto';
|
|||
import { UpdatePasswordDto } from '../../src/identity/local/update-password.dto';
|
||||
import { UserRelationEnum } from '../../src/users/user-relation.enum';
|
||||
import { checkPassword } from '../../src/utils/password';
|
||||
import { Username } from '../../src/utils/username';
|
||||
import { TestSetup, TestSetupBuilder } from '../test-setup';
|
||||
|
||||
describe('Auth', () => {
|
||||
let testSetup: TestSetup;
|
||||
|
||||
let username: string;
|
||||
let username: Username;
|
||||
let displayName: string;
|
||||
let password: string;
|
||||
|
||||
|
|
Loading…
Reference in a new issue