Skip to content

Commit

Permalink
N21-1273 introduces oauth current user
Browse files Browse the repository at this point in the history
  • Loading branch information
arnegns committed Oct 11, 2023
1 parent 030bd71 commit 2658bc6
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AuthGuard } from '@nestjs/passport';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ForbiddenOperationError, ValidationError } from '@shared/common';
import { CurrentUser } from '../decorator/auth.decorator';
import type { ICurrentUser } from '../interface';
import type { ICurrentUser, OauthCurrentUser } from '../interface';
import { LoginDto } from '../uc/dto';
import { LoginUc } from '../uc/login.uc';
import {
Expand All @@ -30,7 +30,7 @@ export class LoginController {
async loginLdap(@CurrentUser() user: ICurrentUser, @Body() _: LdapAuthorizationBodyParams): Promise<LoginResponse> {
const loginDto: LoginDto = await this.loginUc.getLoginData(user);

const mapped: LoginResponse = LoginResponseMapper.mapToLoginResponse(loginDto, user.externalIdToken);
const mapped: LoginResponse = LoginResponseMapper.mapToLoginResponse(loginDto);

return mapped;
}
Expand All @@ -46,7 +46,7 @@ export class LoginController {
async loginLocal(@CurrentUser() user: ICurrentUser, @Body() _: LocalAuthorizationBodyParams): Promise<LoginResponse> {
const loginDto: LoginDto = await this.loginUc.getLoginData(user);

const mapped: LoginResponse = LoginResponseMapper.mapToLoginResponse(loginDto, user.externalIdToken);
const mapped: LoginResponse = LoginResponseMapper.mapToLoginResponse(loginDto);

return mapped;
}
Expand All @@ -59,7 +59,7 @@ export class LoginController {
@ApiResponse({ status: 400, type: ValidationError, description: 'Request data has invalid format.' })
@ApiResponse({ status: 403, type: ForbiddenOperationError, description: 'Invalid user credentials.' })
async loginOauth2(
@CurrentUser() user: ICurrentUser,
@CurrentUser() user: OauthCurrentUser,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Body() _: Oauth2AuthorizationBodyParams
): Promise<LoginResponse> {
Expand Down
4 changes: 3 additions & 1 deletion apps/server/src/modules/authentication/interface/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export interface ICurrentUser {

/** True if a support member impersonates the user */
impersonated?: boolean;
}

/** Ccntains the idToken of the external idp. Will be set during oAuth2 login and used for rp initiated logout */
export interface OauthCurrentUser extends ICurrentUser {
/** Contains the idToken of the external idp. Will be set during oAuth2 login and used for rp initiated logout */
externalIdToken?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ValidationError } from '@shared/common';
import { Permission, RoleName } from '@shared/domain';
import { UserDO } from '@shared/domain/domainobject/user.do';
import { roleFactory, schoolFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing';
import { ICurrentUser } from '../interface';
import { ICurrentUser, OauthCurrentUser } from '../interface';
import { JwtPayload } from '../interface/jwt-payload';
import { CurrentUserMapper } from './current-user.mapper';

Expand Down Expand Up @@ -56,68 +56,88 @@ describe('CurrentUserMapper', () => {
});
});

describe('userDoToICurrentUser', () => {
const userId = 'mockUserId';
describe('OauthCurrentUser', () => {
const userIdMock = 'mockUserId';
describe('when userDO has no ID', () => {
it('should throw error', () => {
const user: UserDO = userDoFactory.build({ createdAt: new Date(), updatedAt: new Date() });
expect(() => CurrentUserMapper.userDoToICurrentUser(accountId, user, undefined, 'idToken')).toThrow(
expect(() => CurrentUserMapper.mapToOauthCurrentUser(accountId, user, undefined, 'idToken')).toThrow(
ValidationError
);
});
});

describe('when userDO is valid', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId({ id: userId, createdAt: new Date(), updatedAt: new Date() });
const user: UserDO = userDoFactory.buildWithId({
id: userIdMock,
createdAt: new Date(),
updatedAt: new Date(),
});
const idToken = 'idToken';

return {
user,
userId: user.id as string,
idToken,
};
};

it('should return valid ICurrentUser instance', () => {
const { user, idToken } = setup();
it('should return valid oauth current user instance', () => {
const { user, userId, idToken } = setup();

const currentUser = CurrentUserMapper.userDoToICurrentUser(accountId, user, undefined, idToken);
const currentUser: OauthCurrentUser = CurrentUserMapper.mapToOauthCurrentUser(
accountId,
user,
undefined,
idToken
);

expect(currentUser).toMatchObject({
expect(currentUser).toMatchObject<OauthCurrentUser>({
accountId,
systemId: undefined,
roles: [],
schoolId: user.schoolId,
userId: user.id,
userId,
externalIdToken: idToken,
});
});
});

describe('when userDO is valid and a systemId is provided', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId({ id: userId, createdAt: new Date(), updatedAt: new Date() });
const user: UserDO = userDoFactory.buildWithId({
id: userIdMock,
createdAt: new Date(),
updatedAt: new Date(),
});
const systemId = 'mockSystemId';
const idToken = 'idToken';

return {
user,
userId: user.id as string,
idToken,
systemId,
};
};

it('should return valid ICurrentUser instance with systemId', () => {
const { user, systemId, idToken } = setup();
const { user, userId, systemId, idToken } = setup();

const currentUser = CurrentUserMapper.userDoToICurrentUser(accountId, user, systemId, idToken);
const currentUser: OauthCurrentUser = CurrentUserMapper.mapToOauthCurrentUser(
accountId,
user,
systemId,
idToken
);

expect(currentUser).toMatchObject({
expect(currentUser).toMatchObject<OauthCurrentUser>({
accountId,
systemId,
roles: [],
schoolId: user.schoolId,
userId: user.id,
userId,
externalIdToken: idToken,
});
});
Expand All @@ -133,7 +153,7 @@ describe('CurrentUserMapper', () => {
},
])
.buildWithId({
id: userId,
id: userIdMock,
createdAt: new Date(),
updatedAt: new Date(),
});
Expand All @@ -146,7 +166,7 @@ describe('CurrentUserMapper', () => {
it('should return valid ICurrentUser instance without systemId', () => {
const { user } = setup();

const currentUser = CurrentUserMapper.userDoToICurrentUser(accountId, user, undefined, 'idToken');
const currentUser = CurrentUserMapper.mapToOauthCurrentUser(accountId, user, undefined, 'idToken');

expect(currentUser).toMatchObject({
accountId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ValidationError } from '@shared/common';
import { Role, User } from '@shared/domain';
import { RoleReference } from '@shared/domain/domainobject';
import { UserDO } from '@shared/domain/domainobject/user.do';
import { ICurrentUser } from '../interface';
import { ICurrentUser, OauthCurrentUser } from '../interface';
import { JwtPayload } from '../interface/jwt-payload';

export class CurrentUserMapper {
Expand All @@ -16,12 +16,12 @@ export class CurrentUserMapper {
};
}

static userDoToICurrentUser(
static mapToOauthCurrentUser(
accountId: string,
user: UserDO,
systemId?: string,
externalIdToken?: string
): ICurrentUser {
): OauthCurrentUser {
if (!user.id) {
throw new ValidationError('user has no ID');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Oauth2Strategy extends PassportStrategy(Strategy, 'oauth2') {
throw new UnauthorizedException('no account found');
}

const currentUser: ICurrentUser = CurrentUserMapper.userDoToICurrentUser(
const currentUser: ICurrentUser = CurrentUserMapper.mapToOauthCurrentUser(
account.id,
user,
systemId,
Expand Down

0 comments on commit 2658bc6

Please sign in to comment.