feat(api/private/me): include authProvider in UserInfo

This information is supposed to be used by the frontend
to identify the login method that was used.

The used login method is saved as a string into the session data
and extracted via a new SessionAuthProvider decorator.

Signed-off-by: David Mehren <git@herrmehren.de>
This commit is contained in:
David Mehren 2022-01-30 21:54:30 +01:00
parent 3f8e3b0589
commit d6ea4d29fe
7 changed files with 71 additions and 8 deletions

View file

@ -75,11 +75,18 @@ export class AuthController {
@Post('local/login')
@OpenApi(201, 400, 401)
login(
@Req() request: Request & { session: { user: string } },
@Req()
request: Request & {
session: {
authProvider: string;
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;
request.session.authProvider = 'local';
}
@UseGuards(SessionGuard)

View file

@ -10,11 +10,12 @@ import { SessionGuard } from '../../../identity/session.guard';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaUploadDto } from '../../../media/media-upload.dto';
import { MediaService } from '../../../media/media.service';
import { FullUserInfoDto } from '../../../users/user-info.dto';
import { UserLoginInfoDto } from '../../../users/user-info.dto';
import { User } from '../../../users/user.entity';
import { UsersService } from '../../../users/users.service';
import { OpenApi } from '../../utils/openapi.decorator';
import { RequestUser } from '../../utils/request-user.decorator';
import { SessionAuthProvider } from '../../utils/session-authprovider.decorator';
@UseGuards(SessionGuard)
@OpenApi(401)
@ -31,8 +32,11 @@ export class MeController {
@Get()
@OpenApi(200)
getMe(@RequestUser() user: User): FullUserInfoDto {
return this.userService.toFullUserDto(user);
getMe(
@RequestUser() user: User,
@SessionAuthProvider() authProvider: string,
): UserInfoDto {
return this.userService.toUserLoginInfoDto(user, authProvider);
}
@Get('media')

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
createParamDecorator,
ExecutionContext,
InternalServerErrorException,
} from '@nestjs/common';
import { Request } from 'express';
/**
* Extracts the auth provider identifier from a session inside a request
*
* Will throw an {@link InternalServerErrorException} if no identifier is present
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const SessionAuthProvider = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request: Request & {
session: {
authProvider: string;
};
} = ctx.switchToHttp().getRequest();
if (!request.session?.authProvider) {
// We should have an auth provider here, otherwise something is wrong
throw new InternalServerErrorException(
'Session is missing an auth provider identifier',
);
}
return request.session.authProvider;
},
);

View file

@ -51,3 +51,12 @@ export class FullUserInfoDto extends UserInfoDto {
@IsString()
email: string;
}
export class UserLoginInfoDto extends UserInfoDto {
/**
* Identifier of the auth provider that was used to log in
*/
@ApiProperty()
@IsString()
authProvider: string;
}

View file

@ -9,7 +9,11 @@ import { Repository } from 'typeorm';
import { AlreadyInDBError, NotInDBError } from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { FullUserInfoDto, UserInfoDto } from './user-info.dto';
import {
FullUserInfoDto,
UserInfoDto,
UserLoginInfoDto,
} from './user-info.dto';
import { UserRelationEnum } from './user-relation.enum';
import { User } from './user.entity';
@ -131,4 +135,8 @@ export class UsersService {
email: user.email ?? '',
};
}
toUserLoginInfoDto(user: User, authProvider: string): UserLoginInfoDto {
return { ...this.toUserDto(user), authProvider };
}
}

View file

@ -8,7 +8,7 @@ import request from 'supertest';
import { NotInDBError } from '../../src/errors/errors';
import { Note } from '../../src/notes/note.entity';
import { FullUserInfoDto } from '../../src/users/user-info.dto';
import { UserLoginInfoDto } from '../../src/users/user-info.dto';
import { User } from '../../src/users/user.entity';
import { TestSetup, TestSetupBuilder } from '../test-setup';
@ -50,12 +50,12 @@ describe('Me', () => {
});
it('GET /me', async () => {
const userInfo = testSetup.userService.toFullUserDto(user);
const userInfo = testSetup.userService.toUserLoginInfoDto(user, 'local');
const response = await agent
.get('/api/private/me')
.expect('Content-Type', /json/)
.expect(200);
const gotUser = response.body as FullUserInfoDto;
const gotUser = response.body as UserLoginInfoDto;
expect(gotUser).toEqual(userInfo);
});

View file

@ -55,6 +55,7 @@ describe('Register and Login', () => {
const profile = await session.get('/api/private/me').expect(200);
expect(profile.body.username).toEqual(USERNAME);
expect(profile.body.displayName).toEqual(DISPLAYNAME);
expect(profile.body.authProvider).toEqual('local');
// logout again
await session.delete('/api/private/auth/logout').expect(204);