refacto(invite|signin): remove unused code + fix signin on invite page. (#9745)

- Replace `window.location.replace` by `useRedirect` hook.
- Remove unused code: `switchWorkspace, addUserByInviteHash...`
- Refacto `Invite` component.
- Fix signin on invite modal.
This commit is contained in:
Antoine Moreaux
2025-01-21 16:33:31 +01:00
committed by GitHub
parent 2e9a77f702
commit 34afd73923
28 changed files with 67 additions and 715 deletions

View File

@ -17,7 +17,6 @@ import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/micr
// import { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service';
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
import { SwitchWorkspaceService } from 'src/engine/core-modules/auth/services/switch-workspace.service';
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
@ -103,7 +102,6 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
RefreshTokenService,
LoginTokenService,
ResetPasswordService,
SwitchWorkspaceService,
TransientTokenService,
ApiKeyService,
SocialSsoService,

View File

@ -16,7 +16,6 @@ import { ApiKeyService } from './services/api-key.service';
import { AuthService } from './services/auth.service';
// import { OAuthService } from './services/oauth.service';
import { ResetPasswordService } from './services/reset-password.service';
import { SwitchWorkspaceService } from './services/switch-workspace.service';
import { EmailVerificationTokenService } from './token/services/email-verification-token.service';
import { LoginTokenService } from './token/services/login-token.service';
import { RenewTokenService } from './token/services/renew-token.service';
@ -74,10 +73,6 @@ describe('AuthResolver', () => {
provide: LoginTokenService,
useValue: {},
},
{
provide: SwitchWorkspaceService,
useValue: {},
},
{
provide: TransientTokenService,
useValue: {},

View File

@ -25,9 +25,7 @@ import {
import { AvailableWorkspaceOutput } from 'src/engine/core-modules/auth/dto/available-workspaces.output';
import { GetLoginTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.input';
import { SignUpOutput } from 'src/engine/core-modules/auth/dto/sign-up.output';
import { SwitchWorkspaceInput } from 'src/engine/core-modules/auth/dto/switch-workspace.input';
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
import { SwitchWorkspaceService } from 'src/engine/core-modules/auth/services/switch-workspace.service';
import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service';
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
import { RenewTokenService } from 'src/engine/core-modules/auth/token/services/renew-token.service';
@ -38,7 +36,6 @@ import { EmailVerificationService } from 'src/engine/core-modules/email-verifica
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { PublicWorkspaceDataOutput } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
@ -70,7 +67,6 @@ export class AuthResolver {
private apiKeyService: ApiKeyService,
private resetPasswordService: ResetPasswordService,
private loginTokenService: LoginTokenService,
private switchWorkspaceService: SwitchWorkspaceService,
private transientTokenService: TransientTokenService,
private emailVerificationService: EmailVerificationService,
// private oauthService: OAuthService,
@ -307,18 +303,6 @@ export class AuthResolver {
);
}
@Mutation(() => PublicWorkspaceDataOutput)
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
async switchWorkspace(
@AuthUser() user: User,
@Args() args: SwitchWorkspaceInput,
): Promise<PublicWorkspaceDataOutput> {
return await this.switchWorkspaceService.switchWorkspace(
user,
args.workspaceId,
);
}
@Mutation(() => AuthTokens)
async renewToken(@Args() args: AppTokenInput): Promise<AuthTokens> {
const tokens = await this.renewTokenService.generateTokensFromRefreshToken(

View File

@ -12,9 +12,7 @@ export const PASSWORD_REGEX = /^.{8,}$/;
const saltRounds = 10;
export const hashPassword = async (password: string) => {
const hash = await bcrypt.hash(password, saltRounds);
return hash;
return await bcrypt.hash(password, saltRounds);
};
export const compareHash = async (password: string, passwordHash: string) => {

View File

@ -1,11 +0,0 @@
import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsString } from 'class-validator';
@ArgsType()
export class SwitchWorkspaceInput {
@Field(() => String)
@IsNotEmpty()
@IsString()
workspaceId: string;
}

View File

@ -137,10 +137,7 @@ export class SignInUpService {
password: string;
passwordHash: string;
}) {
const isValid = await compareHash(
await this.generateHash(password),
passwordHash,
);
const isValid = await compareHash(password, passwordHash);
if (!isValid) {
throw new AuthException(

View File

@ -1,186 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { SwitchWorkspaceService } from './switch-workspace.service';
describe('SwitchWorkspaceService', () => {
let service: SwitchWorkspaceService;
let userRepository: Repository<User>;
let workspaceRepository: Repository<Workspace>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SwitchWorkspaceService,
{
provide: getRepositoryToken(User, 'core'),
useClass: Repository,
},
{
provide: getRepositoryToken(Workspace, 'core'),
useClass: Repository,
},
{
provide: AccessTokenService,
useValue: {
generateAccessToken: jest.fn(),
},
},
{
provide: RefreshTokenService,
useValue: {
generateRefreshToken: jest.fn(),
},
},
{
provide: EnvironmentService,
useValue: {
get: jest.fn(),
},
},
{
provide: UserService,
useValue: {},
},
],
}).compile();
service = module.get<SwitchWorkspaceService>(SwitchWorkspaceService);
userRepository = module.get<Repository<User>>(
getRepositoryToken(User, 'core'),
);
workspaceRepository = module.get<Repository<Workspace>>(
getRepositoryToken(Workspace, 'core'),
);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('switchWorkspace', () => {
it('should throw an error if user does not exist', async () => {
jest.spyOn(userRepository, 'findBy').mockResolvedValue([]);
jest.spyOn(workspaceRepository, 'findOne').mockResolvedValue(null);
await expect(
service.switchWorkspace(
{ id: 'non-existent-user' } as User,
'workspace-id',
),
).rejects.toThrow(AuthException);
});
it('should throw an error if workspace does not exist', async () => {
jest
.spyOn(userRepository, 'findBy')
.mockResolvedValue([{ id: 'user-id' } as User]);
jest.spyOn(workspaceRepository, 'findOne').mockResolvedValue(null);
await expect(
service.switchWorkspace(
{ id: 'user-id' } as User,
'non-existent-workspace',
),
).rejects.toThrow(AuthException);
});
it('should throw an error if user does not belong to workspace', async () => {
const mockUser = { id: 'user-id' };
const mockWorkspace = {
id: 'workspace-id',
workspaceUsers: [{ userId: 'other-user-id' }],
};
jest
.spyOn(userRepository, 'findBy')
.mockResolvedValue([mockUser as User]);
jest
.spyOn(workspaceRepository, 'findOne')
.mockResolvedValue(mockWorkspace as any);
await expect(
service.switchWorkspace(mockUser as User, 'workspace-id'),
).rejects.toThrow(AuthException);
});
it('should return SSO auth info if workspace has SSO providers', async () => {
const mockUser = { id: 'user-id' };
const mockWorkspace = {
id: 'workspace-id',
workspaceUsers: [{ userId: 'user-id' }],
logo: 'logo',
displayName: 'displayName',
isGoogleAuthEnabled: true,
isPasswordAuthEnabled: true,
isMicrosoftAuthEnabled: false,
workspaceSSOIdentityProviders: [
{
id: 'sso-id',
},
],
};
jest
.spyOn(userRepository, 'findBy')
.mockResolvedValue([mockUser as User]);
jest.spyOn(userRepository, 'save').mockResolvedValue(mockUser as User);
jest
.spyOn(workspaceRepository, 'findOne')
.mockResolvedValue(mockWorkspace as any);
const result = await service.switchWorkspace(
mockUser as User,
'workspace-id',
);
expect(result).toEqual({
id: mockWorkspace.id,
logo: expect.any(String),
displayName: expect.any(String),
authProviders: expect.any(Object),
});
});
it('should return workspace info if workspace does not have SSO providers', async () => {
const mockUser = { id: 'user-id' };
const mockWorkspace = {
id: 'workspace-id',
workspaceUsers: [{ userId: 'user-id' }],
workspaceSSOIdentityProviders: [],
logo: 'logo',
displayName: 'displayName',
};
jest
.spyOn(userRepository, 'findBy')
.mockResolvedValue([mockUser as User]);
jest
.spyOn(workspaceRepository, 'findOne')
.mockResolvedValue(mockWorkspace as any);
jest.spyOn(userRepository, 'save').mockResolvedValue({} as User);
const result = await service.switchWorkspace(
mockUser as User,
'workspace-id',
);
expect(result).toEqual({
id: mockWorkspace.id,
logo: expect.any(String),
displayName: expect.any(String),
authProviders: expect.any(Object),
});
});
});
});

View File

@ -1,66 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
@Injectable()
export class SwitchWorkspaceService {
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
private readonly environmentService: EnvironmentService,
) {}
async switchWorkspace(user: User, workspaceId: string) {
const workspace = await this.workspaceRepository.findOne({
where: { id: workspaceId },
relations: ['workspaceUsers', 'workspaceSSOIdentityProviders'],
});
workspaceValidator.assertIsDefinedOrThrow(
workspace,
new AuthException('Workspace not found', AuthExceptionCode.INVALID_INPUT),
);
if (
!workspace.workspaceUsers
.map((userWorkspace) => userWorkspace.userId)
.includes(user.id)
) {
throw new AuthException(
'user does not belong to workspace',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
);
}
const systemEnabledProviders: AuthProviders = {
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
magicLink: false,
password: this.environmentService.get('AUTH_PASSWORD_ENABLED'),
microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'),
sso: [],
};
return {
id: workspace.id,
subdomain: workspace.subdomain,
logo: workspace.logo,
displayName: workspace.displayName,
authProviders: getAuthProvidersByWorkspace({
workspace,
systemEnabledProviders,
}),
};
}
}

View File

@ -1,18 +1,14 @@
import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { Resolver } from '@nestjs/graphql';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceInviteHashValidInput } from 'src/engine/core-modules/auth/dto/workspace-invite-hash.input';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
import { WorkspaceInviteTokenInput } from 'src/engine/core-modules/auth/dto/workspace-invite-token.input';
@UseGuards(WorkspaceAuthGuard)
@Resolver(() => UserWorkspace)
@ -23,36 +19,4 @@ export class UserWorkspaceResolver {
private readonly userWorkspaceService: UserWorkspaceService,
private readonly workspaceInvitationService: WorkspaceInvitationService,
) {}
@Mutation(() => User)
async addUserToWorkspace(
@AuthUser() user: User,
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
) {
const workspace = await this.workspaceRepository.findOneBy({
inviteHash: workspaceInviteHashValidInput.inviteHash,
});
if (!workspace) {
return;
}
await this.workspaceInvitationService.invalidateWorkspaceInvitation(
workspace.id,
user.email,
);
return await this.userWorkspaceService.addUserToWorkspace(user, workspace);
}
@Mutation(() => User)
async addUserToWorkspaceByInviteToken(
@AuthUser() user: User,
@Args() workspaceInviteTokenInput: WorkspaceInviteTokenInput,
) {
return this.userWorkspaceService.addUserToWorkspaceByInviteToken(
workspaceInviteTokenInput.inviteToken,
user,
);
}
}

View File

@ -124,21 +124,6 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
return user;
}
async addUserToWorkspaceByInviteToken(inviteToken: string, user: User) {
const appToken =
await this.workspaceInvitationService.validatePersonalInvitation({
workspacePersonalInviteToken: inviteToken,
email: user.email,
});
await this.workspaceInvitationService.invalidateWorkspaceInvitation(
appToken.workspace.id,
user.email,
);
return await this.addUserToWorkspace(user, appToken.workspace);
}
public async getUserCount(workspaceId: string): Promise<number | undefined> {
return await this.userWorkspaceRepository.countBy({
workspaceId,