Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N21-1219 hide email section #4455

Merged
merged 16 commits into from
Nov 2, 2023
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
Loading