Skip to content

Commit

Permalink
Merge branch 'main' into N21-1285-launch-tool-on-board
Browse files Browse the repository at this point in the history
  • Loading branch information
MarvinOehlerkingCap authored Nov 2, 2023
2 parents f6fa565 + c4b1288 commit fb2f632
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface CreateJwtPayload {
systemId?: string; // without this the user needs to change his PW during first login
support?: boolean;
// support UserId is missed see featherJS
isExternalUser: boolean;
}

export interface JwtPayload extends CreateJwtPayload {
Expand Down
3 changes: 3 additions & 0 deletions apps/server/src/modules/authentication/interface/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export interface ICurrentUser {

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

/** True if the user is an external user e.g. an oauth user */
isExternalUser: boolean;
}

export interface OauthCurrentUser extends ICurrentUser {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('CurrentUserMapper', () => {
describe('when userDO has no ID', () => {
it('should throw error', () => {
const user: UserDO = userDoFactory.build({ createdAt: new Date(), updatedAt: new Date() });

expect(() => CurrentUserMapper.mapToOauthCurrentUser(accountId, user, undefined, 'idToken')).toThrow(
ValidationError
);
Expand Down Expand Up @@ -100,6 +101,7 @@ describe('CurrentUserMapper', () => {
schoolId: user.schoolId,
userId,
externalIdToken: idToken,
isExternalUser: true,
});
});
});
Expand Down Expand Up @@ -139,6 +141,7 @@ describe('CurrentUserMapper', () => {
schoolId: user.schoolId,
userId,
externalIdToken: idToken,
isExternalUser: true,
});
});
});
Expand Down Expand Up @@ -181,22 +184,33 @@ describe('CurrentUserMapper', () => {

describe('jwtToICurrentUser', () => {
describe('when JWT is provided with all claims', () => {
it('should return current user', () => {
const setup = () => {
const jwtPayload: JwtPayload = {
accountId: 'dummyAccountId',
systemId: 'dummySystemId',
roles: ['mockRoleId'],
schoolId: 'dummySchoolId',
userId: 'dummyUserId',
support: true,
isExternalUser: true,
sub: 'dummyAccountId',
jti: 'random string',
aud: 'some audience',
iss: 'feathers',
iat: Math.floor(new Date().getTime() / 1000),
exp: Math.floor(new Date().getTime() / 1000) + 3600,
};

return {
jwtPayload,
};
};

it('should return current user', () => {
const { jwtPayload } = setup();

const currentUser = CurrentUserMapper.jwtToICurrentUser(jwtPayload);

expect(currentUser).toMatchObject({
accountId: jwtPayload.accountId,
systemId: jwtPayload.systemId,
Expand All @@ -206,28 +220,60 @@ describe('CurrentUserMapper', () => {
impersonated: jwtPayload.support,
});
});

it('should return current user with default for isExternalUser', () => {
const { jwtPayload } = setup();

const currentUser = CurrentUserMapper.jwtToICurrentUser(jwtPayload);

expect(currentUser).toMatchObject({
isExternalUser: jwtPayload.isExternalUser,
});
});
});

describe('when JWT is provided without optional claims', () => {
it('should return current user', () => {
const setup = () => {
const jwtPayload: JwtPayload = {
accountId: 'dummyAccountId',
roles: ['mockRoleId'],
schoolId: 'dummySchoolId',
userId: 'dummyUserId',
isExternalUser: false,
sub: 'dummyAccountId',
jti: 'random string',
aud: 'some audience',
iss: 'feathers',
iat: Math.floor(new Date().getTime() / 1000),
exp: Math.floor(new Date().getTime() / 1000) + 3600,
};

return {
jwtPayload,
};
};

it('should return current user', () => {
const { jwtPayload } = setup();

const currentUser = CurrentUserMapper.jwtToICurrentUser(jwtPayload);

expect(currentUser).toMatchObject({
accountId: jwtPayload.accountId,
roles: [jwtPayload.roles[0]],
schoolId: jwtPayload.schoolId,
userId: jwtPayload.userId,
isExternalUser: false,
});
});

it('should return current user with default for isExternalUser', () => {
const { jwtPayload } = setup();

const currentUser = CurrentUserMapper.jwtToICurrentUser(jwtPayload);

expect(currentUser).toMatchObject({
isExternalUser: false,
});
});
});
Expand All @@ -242,6 +288,7 @@ describe('CurrentUserMapper', () => {
schoolId: 'dummySchoolId',
userId: 'dummyUserId',
impersonated: true,
isExternalUser: false,
};

const createJwtPayload: CreateJwtPayload = CurrentUserMapper.mapCurrentUserToCreateJwtPayload(currentUser);
Expand All @@ -253,6 +300,7 @@ describe('CurrentUserMapper', () => {
schoolId: currentUser.schoolId,
userId: currentUser.userId,
support: currentUser.impersonated,
isExternalUser: false,
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class CurrentUserMapper {
roles: user.roles.getItems().map((role: Role) => role.id),
schoolId: user.school.id,
userId: user.id,
isExternalUser: false,
};
}

Expand All @@ -33,6 +34,7 @@ export class CurrentUserMapper {
schoolId: user.schoolId,
userId: user.id,
externalIdToken,
isExternalUser: true,
};
}

Expand All @@ -44,6 +46,7 @@ export class CurrentUserMapper {
roles: currentUser.roles,
systemId: currentUser.systemId,
support: currentUser.impersonated,
isExternalUser: currentUser.isExternalUser,
};
}

Expand All @@ -55,6 +58,7 @@ export class CurrentUserMapper {
schoolId: jwtPayload.schoolId,
userId: jwtPayload.userId,
impersonated: jwtPayload.support,
isExternalUser: jwtPayload.isExternalUser,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ describe('AuthenticationService', () => {
roles: ['student'],
schoolId: 'mockSchoolId',
userId: 'mockUserId',
isExternalUser: false,
};
await authenticationService.generateJwt(mockCurrentUser);
expect(jwtService.sign).toBeCalledWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ describe('LdapStrategy', () => {
schoolId: school.id,
systemId: system.id,
accountId: account.id,
isExternalUser: false,
});
});
});
Expand Down Expand Up @@ -500,6 +501,7 @@ describe('LdapStrategy', () => {
schoolId: school.id,
systemId: system.id,
accountId: account.id,
isExternalUser: false,
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('Oauth2Strategy', () => {
schoolId: user.schoolId,
accountId: account.id,
externalIdToken: idToken,
isExternalUser: true,
});
});
});
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/modules/authentication/uc/login.uc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('LoginUc', () => {
userId: '',
systemId: '',
impersonated: false,
isExternalUser: false,
someProperty: 'shouldNotBeMapped',
};
const loginDto: LoginDto = new LoginDto({ accessToken: 'accessToken' });
Expand All @@ -58,6 +59,7 @@ describe('LoginUc', () => {
roles: userInfo.roles,
systemId: userInfo.systemId,
support: userInfo.impersonated,
isExternalUser: userInfo.isExternalUser,
});
});

Expand Down
22 changes: 11 additions & 11 deletions apps/server/src/modules/oauth/uc/oauth.uc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { UnauthorizedException, UnprocessableEntityException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { LegacySchoolDo, UserDO } from '@shared/domain';
import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy';
import { ISession } from '@shared/domain/types/session';
import { legacySchoolDoFactory, setupEntities } from '@shared/testing';
import { LegacyLogger } from '@src/core/logger';
import { ICurrentUser } from '@modules/authentication';
import { AuthenticationService } from '@modules/authentication/services/authentication.service';
import { LegacySchoolService } from '@modules/legacy-school';
import { OauthUc } from '@modules/oauth/uc/oauth.uc';
import { ProvisioningService } from '@modules/provisioning';
import { ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@modules/provisioning/dto';
import { LegacySchoolService } from '@modules/legacy-school';
import { SystemService } from '@modules/system';
import { OauthConfigDto, SystemDto } from '@modules/system/service';
import { UserService } from '@modules/user';
import { UserMigrationService } from '@modules/user-login-migration';
import { OAuthMigrationError } from '@modules/user-login-migration/error/oauth-migration.error';
import { SchoolMigrationService } from '@modules/user-login-migration/service';
import { MigrationDto } from '@modules/user-login-migration/service/dto';
import { OAuthSSOError } from '../loggable/oauth-sso.error';
import { UnauthorizedException, UnprocessableEntityException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { LegacySchoolDo, UserDO } from '@shared/domain';
import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy';
import { ISession } from '@shared/domain/types/session';
import { legacySchoolDoFactory, setupEntities } from '@shared/testing';
import { LegacyLogger } from '@src/core/logger';
import { OauthCurrentUser } from '@modules/authentication/interface';
import { AuthorizationParams } from '../controller/dto';
import { OAuthTokenDto } from '../interface';
import { OAuthSSOError } from '../loggable/oauth-sso.error';
import { OAuthProcessDto } from '../service/dto';
import { OAuthService } from '../service/oauth.service';
import { OauthLoginStateDto } from './dto/oauth-login-state.dto';
Expand Down Expand Up @@ -254,7 +254,7 @@ describe('OAuthUc', () => {
externalId: 'mockExternalId',
});

const currentUser: ICurrentUser = { userId: 'userId' } as ICurrentUser;
const currentUser: OauthCurrentUser = { userId: 'userId', isExternalUser: true } as OauthCurrentUser;
const testSystem: SystemDto = new SystemDto({
id: 'mockSystemId',
type: 'mock',
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/modules/oauth/uc/oauth.uc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Injectable, UnauthorizedException, UnprocessableEntityException } from
import { EntityId, LegacySchoolDo, UserDO } from '@shared/domain';
import { ISession } from '@shared/domain/types/session';
import { LegacyLogger } from '@src/core/logger';
import { ICurrentUser } from '@modules/authentication';
import { AuthenticationService } from '@modules/authentication/services/authentication.service';
import { ProvisioningService } from '@modules/provisioning';
import { OauthDataDto } from '@modules/provisioning/dto';
Expand All @@ -13,6 +12,7 @@ import { UserMigrationService } from '@modules/user-login-migration';
import { SchoolMigrationService } from '@modules/user-login-migration/service';
import { MigrationDto } from '@modules/user-login-migration/service/dto';
import { nanoid } from 'nanoid';
import { OauthCurrentUser } from '@modules/authentication/interface';
import { AuthorizationParams } from '../controller/dto';
import { OAuthTokenDto } from '../interface';
import { OAuthProcessDto } from '../service/dto';
Expand Down Expand Up @@ -140,9 +140,9 @@ export class OauthUc {
}

private async getJwtForUser(userId: EntityId): Promise<string> {
const currentUser: ICurrentUser = await this.userService.getResolvedUser(userId);
const oauthCurrentUser: OauthCurrentUser = await this.userService.getResolvedUser(userId);

const { accessToken } = await this.authenticationService.generateJwt(currentUser);
const { accessToken } = await this.authenticationService.generateJwt(oauthCurrentUser);

return accessToken;
}
Expand Down
Loading

0 comments on commit fb2f632

Please sign in to comment.