auth: adds token-auth to public api

adds auth service
adds auth module
adds token-auth strategy
adds token-auth to all public api calls

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-01-15 18:53:09 +01:00 committed by David Mehren
parent 4784a1aea2
commit 2ab950c5c3
No known key found for this signature in database
GPG key ID: 185982BA4C42B7C3
11 changed files with 174 additions and 18 deletions

View file

@ -31,8 +31,8 @@
"@nestjs/platform-express": "7.6.5",
"@nestjs/swagger": "4.7.12",
"@nestjs/typeorm": "7.1.5",
"@types/bcrypt": "^3.0.0",
"@types/passport-http-bearer": "^1.0.36",
"@types/bcrypt": "^3.0.0",
"bcrypt": "^5.0.0",
"class-transformer": "0.3.2",
"class-validator": "0.13.1",

View file

@ -13,6 +13,8 @@ import {
NotFoundException,
Param,
Put,
UseGuards,
Request,
} from '@nestjs/common';
import { HistoryEntryUpdateDto } from '../../../history/history-entry-update.dto';
import { HistoryEntryDto } from '../../../history/history-entry.dto';
@ -22,6 +24,7 @@ import { NoteMetadataDto } from '../../../notes/note-metadata.dto';
import { NotesService } from '../../../notes/notes.service';
import { UserInfoDto } from '../../../users/user-info.dto';
import { UsersService } from '../../../users/users.service';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
@Controller('me')
export class MeController {
@ -34,29 +37,36 @@ export class MeController {
this.logger.setContext(MeController.name);
}
@UseGuards(TokenAuthGuard)
@Get()
async getMe(): Promise<UserInfoDto> {
async getMe(@Request() req): Promise<UserInfoDto> {
return this.usersService.toUserDto(
await this.usersService.getUserByUsername('hardcoded'),
await this.usersService.getUserByUsername(req.user.userName),
);
}
@UseGuards(TokenAuthGuard)
@Get('history')
getUserHistory(): HistoryEntryDto[] {
return this.historyService.getUserHistory('someone');
getUserHistory(@Request() req): HistoryEntryDto[] {
return this.historyService.getUserHistory(req.user.userName);
}
@UseGuards(TokenAuthGuard)
@Put('history/:note')
updateHistoryEntry(
@Request() req,
@Param('note') note: string,
@Body() entryUpdateDto: HistoryEntryUpdateDto,
): HistoryEntryDto {
// ToDo: Check if user is allowed to pin this history entry
return this.historyService.updateHistoryEntry(note, entryUpdateDto);
}
@UseGuards(TokenAuthGuard)
@Delete('history/:note')
@HttpCode(204)
deleteHistoryEntry(@Param('note') note: string) {
deleteHistoryEntry(@Request() req, @Param('note') note: string) {
// ToDo: Check if user is allowed to delete note
try {
return this.historyService.deleteHistoryEntry(note);
} catch (e) {
@ -64,8 +74,9 @@ export class MeController {
}
}
@UseGuards(TokenAuthGuard)
@Get('notes')
getMyNotes(): NoteMetadataDto[] {
return this.notesService.getUserNotes('someone');
getMyNotes(@Request() req): NoteMetadataDto[] {
return this.notesService.getUserNotes(req.user.userName);
}
}

View file

@ -12,8 +12,10 @@ import {
NotFoundException,
Param,
Post,
Request,
UnauthorizedException,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
@ -25,6 +27,7 @@ import {
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaService } from '../../../media/media.service';
import { MulterFile } from '../../../media/multer-file.interface';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
@Controller('media')
export class MediaController {
@ -35,14 +38,16 @@ export class MediaController {
this.logger.setContext(MediaController.name);
}
@UseGuards(TokenAuthGuard)
@Post()
@UseInterceptors(FileInterceptor('file'))
async uploadMedia(
@Request() req,
@UploadedFile() file: MulterFile,
@Headers('HedgeDoc-Note') noteId: string,
) {
//TODO: Get user from request
const username = 'hardcoded';
const username = req.user.userName;
this.logger.debug(
`Recieved filename '${file.originalname}' for note '${noteId}' from user '${username}'`,
'uploadImage',
@ -64,10 +69,11 @@ export class MediaController {
}
}
@UseGuards(TokenAuthGuard)
@Delete(':filename')
async deleteMedia(@Param('filename') filename: string) {
async deleteMedia(@Request() req, @Param('filename') filename: string) {
//TODO: Get user from request
const username = 'hardcoded';
const username = req.user.userName;
try {
await this.mediaService.deleteFile(filename, username);
} catch (e) {

View file

@ -4,18 +4,21 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Controller, Get } from '@nestjs/common';
import { Controller, Get, UseGuards } from '@nestjs/common';
import { MonitoringService } from '../../../monitoring/monitoring.service';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
@Controller('monitoring')
export class MonitoringController {
constructor(private monitoringService: MonitoringService) {}
@UseGuards(TokenAuthGuard)
@Get()
getStatus() {
return this.monitoringService.getServerStatus();
}
@UseGuards(TokenAuthGuard)
@Get('prometheus')
getPrometheusStatus() {
return '';

View file

@ -14,6 +14,8 @@ import {
Param,
Post,
Put,
Request,
UseGuards,
} from '@nestjs/common';
import { NotInDBError } from '../../../errors/errors';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
@ -21,6 +23,7 @@ import { NotePermissionsUpdateDto } from '../../../notes/note-permissions.dto';
import { NotesService } from '../../../notes/notes.service';
import { RevisionsService } from '../../../revisions/revisions.service';
import { MarkdownBody } from '../../utils/markdownbody-decorator';
import { TokenAuthGuard } from '../../../auth/token-auth.guard';
@Controller('notes')
export class NotesController {
@ -32,14 +35,18 @@ export class NotesController {
this.logger.setContext(NotesController.name);
}
@UseGuards(TokenAuthGuard)
@Post()
async createNote(@MarkdownBody() text: string) {
async createNote(@Request() req, @MarkdownBody() text: string) {
// ToDo: provide user for createNoteDto
this.logger.debug('Got raw markdown:\n' + text);
return this.noteService.createNoteDto(text);
}
@UseGuards(TokenAuthGuard)
@Get(':noteIdOrAlias')
async getNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
async getNote(@Request() req, @Param('noteIdOrAlias') noteIdOrAlias: string) {
// ToDo: check if user is allowed to view this note
try {
return await this.noteService.getNoteDtoByIdOrAlias(noteIdOrAlias);
} catch (e) {
@ -50,17 +57,25 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Post(':noteAlias')
async createNamedNote(
@Request() req,
@Param('noteAlias') noteAlias: string,
@MarkdownBody() text: string,
) {
// ToDo: check if user is allowed to view this note
this.logger.debug('Got raw markdown:\n' + text);
return this.noteService.createNoteDto(text, noteAlias);
}
@UseGuards(TokenAuthGuard)
@Delete(':noteIdOrAlias')
async deleteNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
async deleteNote(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
// ToDo: check if user is allowed to delete this note
this.logger.debug('Deleting note: ' + noteIdOrAlias);
try {
await this.noteService.deleteNoteByIdOrAlias(noteIdOrAlias);
@ -74,11 +89,14 @@ export class NotesController {
return;
}
@UseGuards(TokenAuthGuard)
@Put(':noteIdOrAlias')
async updateNote(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
@MarkdownBody() text: string,
) {
// ToDo: check if user is allowed to change this note
this.logger.debug('Got raw markdown:\n' + text);
try {
return await this.noteService.updateNoteByIdOrAlias(noteIdOrAlias, text);
@ -90,9 +108,14 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Get(':noteIdOrAlias/content')
@Header('content-type', 'text/markdown')
async getNoteContent(@Param('noteIdOrAlias') noteIdOrAlias: string) {
async getNoteContent(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
// ToDo: check if user is allowed to view this notes content
try {
return await this.noteService.getNoteContent(noteIdOrAlias);
} catch (e) {
@ -103,8 +126,13 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Get(':noteIdOrAlias/metadata')
async getNoteMetadata(@Param('noteIdOrAlias') noteIdOrAlias: string) {
async getNoteMetadata(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
// ToDo: check if user is allowed to view this notes metadata
try {
return await this.noteService.getNoteMetadata(noteIdOrAlias);
} catch (e) {
@ -115,11 +143,14 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Put(':noteIdOrAlias/metadata/permissions')
async updateNotePermissions(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
@Body() updateDto: NotePermissionsUpdateDto,
) {
// ToDo: check if user is allowed to view this notes permissions
try {
return await this.noteService.updateNotePermissions(
noteIdOrAlias,
@ -133,8 +164,13 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Get(':noteIdOrAlias/revisions')
async getNoteRevisions(@Param('noteIdOrAlias') noteIdOrAlias: string) {
async getNoteRevisions(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
) {
// ToDo: check if user is allowed to view this notes revisions
try {
return await this.revisionsService.getNoteRevisionMetadatas(
noteIdOrAlias,
@ -147,11 +183,14 @@ export class NotesController {
}
}
@UseGuards(TokenAuthGuard)
@Get(':noteIdOrAlias/revisions/:revisionId')
async getNoteRevision(
@Request() req,
@Param('noteIdOrAlias') noteIdOrAlias: string,
@Param('revisionId') revisionId: number,
) {
// ToDo: check if user is allowed to view this notes revision
try {
return await this.revisionsService.getNoteRevision(
noteIdOrAlias,

View file

@ -18,6 +18,7 @@ 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 { AuthModule } from './auth/auth.module';
import appConfig from './config/app.config';
import mediaConfig from './config/media.config';
import hstsConfig from './config/hsts.config';
@ -55,6 +56,7 @@ import authConfig from './config/auth.config';
GroupsModule,
LoggerModule,
MediaModule,
AuthModule,
],
controllers: [],
providers: [],

11
src/auth/auth.module.ts Normal file
View file

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { TokenStrategy } from './token.strategy';
@Module({
imports: [UsersModule, PassportModule],
providers: [AuthService, TokenStrategy],
})
export class AuthModule {}

View file

@ -0,0 +1,31 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from '../users/user.entity';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{
provide: getRepositoryToken(User),
useValue: {},
},
],
imports: [UsersModule],
})
.overrideProvider(getRepositoryToken(User))
.useValue({})
.compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

16
src/auth/auth.service.ts Normal file
View file

@ -0,0 +1,16 @@
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { User } from '../users/user.entity';
@Injectable()
export class AuthService {
constructor(private usersService: UsersService) {}
async validateToken(token: string): Promise<User> {
const user = await this.usersService.getUserByAuthToken(token);
if (user) {
return user;
}
return null;
}
}

View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class TokenAuthGuard extends AuthGuard('token') {}

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Strategy } from 'passport-http-bearer';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { User } from '../users/user.entity';
@Injectable()
export class TokenStrategy extends PassportStrategy(Strategy, 'token') {
constructor(private authService: AuthService) {
super();
}
async validate(token: string): Promise<User> {
const user = await this.authService.validateToken(token);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}