feat: add auth controller with internal login, registration, password change and logout

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-08-08 22:00:14 +02:00 committed by Yannick Bungers
parent 1a96900224
commit b153615637
3 changed files with 180 additions and 0 deletions

View file

@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { getConnectionToken, getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import appConfigMock from '../../../config/mock/app.config.mock';
import authConfigMock from '../../../config/mock/auth.config.mock';
import { Identity } from '../../../identity/identity.entity';
import { IdentityModule } from '../../../identity/identity.module';
import { LoggerModule } from '../../../logger/logger.module';
import { Session } from '../../../users/session.entity';
import { User } from '../../../users/user.entity';
import { UsersModule } from '../../../users/users.module';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
type MockConnection = {
transaction: () => void;
};
function mockConnection(): MockConnection {
return {
transaction: jest.fn(),
};
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getRepositoryToken(Identity),
useClass: Repository,
},
{
provide: getConnectionToken(),
useFactory: mockConnection,
},
],
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [appConfigMock, authConfigMock],
}),
LoggerModule,
UsersModule,
IdentityModule,
],
controllers: [AuthController],
})
.overrideProvider(getRepositoryToken(Identity))
.useClass(Repository)
.overrideProvider(getRepositoryToken(Session))
.useValue({})
.overrideProvider(getRepositoryToken(User))
.useValue({})
.compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View file

@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
BadRequestException,
Body,
Controller,
Delete,
NotFoundException,
Post,
Put,
Req,
UseGuards,
} from '@nestjs/common';
import { Session } from 'express-session';
import { AlreadyInDBError, NotInDBError } from '../../../errors/errors';
import { IdentityService } from '../../../identity/identity.service';
import { LocalAuthGuard } from '../../../identity/local/local.strategy';
import { LoginDto } from '../../../identity/local/login.dto';
import { RegisterDto } from '../../../identity/local/register.dto';
import { UpdatePasswordDto } from '../../../identity/local/update-password.dto';
import { SessionGuard } from '../../../identity/session.guard';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { User } from '../../../users/user.entity';
import { UsersService } from '../../../users/users.service';
import { LoginEnabledGuard } from '../../utils/login-enabled.guard';
import { RegistrationEnabledGuard } from '../../utils/registration-enabled.guard';
import { RequestUser } from '../../utils/request-user.decorator';
@Controller('auth')
export class AuthController {
constructor(
private readonly logger: ConsoleLoggerService,
private usersService: UsersService,
private identityService: IdentityService,
) {
this.logger.setContext(AuthController.name);
}
@UseGuards(RegistrationEnabledGuard)
@Post('local')
async registerUser(@Body() registerDto: RegisterDto): Promise<void> {
try {
const user = await this.usersService.createUser(
registerDto.username,
registerDto.displayname,
);
// ToDo: Figure out how to rollback user if anything with this calls goes wrong
await this.identityService.createLocalIdentity(
user,
registerDto.password,
);
return;
} catch (e) {
if (e instanceof AlreadyInDBError) {
throw new BadRequestException(e.message);
}
throw e;
}
}
@UseGuards(LoginEnabledGuard, SessionGuard)
@Put('local')
async updatePassword(
@RequestUser() user: User,
@Body() changePasswordDto: UpdatePasswordDto,
): Promise<void> {
try {
await this.identityService.updateLocalPassword(
user,
changePasswordDto.newPassword,
);
return;
} catch (e) {
if (e instanceof NotInDBError) {
throw new NotFoundException(e.message);
}
throw e;
}
}
@UseGuards(LoginEnabledGuard, LocalAuthGuard)
@Post('local/login')
login(
@Req() request: Request & { session: { user: string } },
@Body() loginDto: LoginDto,
): void {
// There is no further testing needed as we only get to this point if LocalAuthGuard was successful
request.session.user = loginDto.username;
}
@UseGuards(SessionGuard)
@Delete('logout')
logout(@Req() request: Request & { session: Session }): void {
request.session.destroy((err) => {
if (err) {
this.logger.error('Encountered an error while logging out: ${err}');
throw new BadRequestException('Unable to log out');
}
});
}
}

View file

@ -8,12 +8,14 @@ import { Module } from '@nestjs/common';
import { AuthModule } from '../../auth/auth.module';
import { FrontendConfigModule } from '../../frontend-config/frontend-config.module';
import { HistoryModule } from '../../history/history.module';
import { IdentityModule } from '../../identity/identity.module';
import { LoggerModule } from '../../logger/logger.module';
import { MediaModule } from '../../media/media.module';
import { NotesModule } from '../../notes/notes.module';
import { PermissionsModule } from '../../permissions/permissions.module';
import { RevisionsModule } from '../../revisions/revisions.module';
import { UsersModule } from '../../users/users.module';
import { AuthController } from './auth/auth.controller';
import { ConfigController } from './config/config.controller';
import { HistoryController } from './me/history/history.controller';
import { MeController } from './me/me.controller';
@ -32,6 +34,7 @@ import { TokensController } from './tokens/tokens.controller';
NotesModule,
MediaModule,
RevisionsModule,
IdentityModule,
],
controllers: [
TokensController,
@ -40,6 +43,7 @@ import { TokensController } from './tokens/tokens.controller';
HistoryController,
MeController,
NotesController,
AuthController,
],
})
export class PrivateApiModule {}