Add possibility to invite members to workspace (#579)

* Add possibility to invite members to workspace

* Update endpoints

* Wrap up front end

* Fix according to review

* Fix lint
This commit is contained in:
Charles Bochet
2023-07-10 23:33:15 -07:00
committed by GitHub
parent e1161e96a9
commit 55576cb638
75 changed files with 629 additions and 63 deletions

View File

@ -9,6 +9,7 @@ import { VerifyAuthController } from './controllers/verify-auth.controller';
import { TokenService } from './services/token.service';
import { AuthResolver } from './auth.resolver';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { WorkspaceModule } from '../workspace/workspace.module';
const jwtModule = JwtModule.registerAsync({
useFactory: async (environmentService: EnvironmentService) => {
@ -23,7 +24,7 @@ const jwtModule = JwtModule.registerAsync({
});
@Module({
imports: [jwtModule, UserModule],
imports: [jwtModule, UserModule, WorkspaceModule],
controllers: [GoogleAuthController, VerifyAuthController],
providers: [
AuthService,

View File

@ -15,6 +15,9 @@ import {
import { Prisma } from '@prisma/client';
import { UserExists } from './dto/user-exists.entity';
import { CheckUserExistsInput } from './dto/user-exists.input';
import { WorkspaceInviteHashValid } from './dto/workspace-invite-hash-valid.entity';
import { WorkspaceInviteHashValidInput } from './dto/workspace-invite-hash.input';
import { SignUpInput } from './dto/sign-up.input';
@Resolver()
export class AuthResolver {
@ -33,6 +36,15 @@ export class AuthResolver {
return { exists };
}
@Query(() => WorkspaceInviteHashValid)
async checkWorkspaceInviteHashIsValid(
@Args() workspaceInviteHashValidInput: WorkspaceInviteHashValidInput,
): Promise<WorkspaceInviteHashValid> {
return await this.authService.checkWorkspaceInviteHashIsValid(
workspaceInviteHashValidInput.inviteHash,
);
}
@Mutation(() => LoginToken)
async challenge(@Args() challengeInput: ChallengeInput): Promise<LoginToken> {
const user = await this.authService.challenge(challengeInput);
@ -41,6 +53,14 @@ export class AuthResolver {
return { loginToken };
}
@Mutation(() => LoginToken)
async signUp(@Args() signUpInput: SignUpInput): Promise<LoginToken> {
const user = await this.authService.signUp(signUpInput);
const loginToken = await this.tokenService.generateLoginToken(user.email);
return { loginToken };
}
@Mutation(() => Verify)
async verify(
@Args() verifyInput: VerifyInput,

View File

@ -0,0 +1,20 @@
import { ArgsType, Field } from '@nestjs/graphql';
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
@ArgsType()
export class SignUpInput {
@Field(() => String)
@IsNotEmpty()
@IsEmail()
email: string;
@Field(() => String)
@IsNotEmpty()
@IsString()
password: string;
@Field(() => String, { nullable: true })
@IsString()
@IsOptional()
workspaceInviteHash?: string;
}

View File

@ -0,0 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class WorkspaceInviteHashValid {
@Field(() => Boolean)
isValid: boolean;
}

View File

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

View File

@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { TokenService } from './token.service';
import { UserService } from 'src/core/user/user.service';
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
describe('AuthService', () => {
let service: AuthService;
@ -18,6 +19,10 @@ describe('AuthService', () => {
provide: UserService,
useValue: {},
},
{
provide: WorkspaceService,
useValue: {},
},
],
}).compile();

View File

@ -12,6 +12,9 @@ import { Verify } from '../dto/verify.entity';
import { TokenService } from './token.service';
import { Prisma } from '@prisma/client';
import { UserExists } from '../dto/user-exists.entity';
import { WorkspaceService } from 'src/core/workspace/services/workspace.service';
import { WorkspaceInviteHashValid } from '../dto/workspace-invite-hash-valid.entity';
import { SignUpInput } from '../dto/sign-up.input';
export type UserPayload = {
firstName: string;
@ -24,31 +27,16 @@ export class AuthService {
constructor(
private readonly tokenService: TokenService,
private readonly userService: UserService,
private readonly workspaceService: WorkspaceService,
) {}
async challenge(challengeInput: ChallengeInput) {
let user = await this.userService.findUnique({
const user = await this.userService.findUnique({
where: {
email: challengeInput.email,
},
});
const isPasswordValid = PASSWORD_REGEX.test(challengeInput.password);
assert(!!user || isPasswordValid, 'Password too weak', BadRequestException);
if (!user) {
const passwordHash = await hashPassword(challengeInput.password);
user = await this.userService.createUser({
data: {
email: challengeInput.email,
passwordHash,
locale: 'en',
},
} as Prisma.UserCreateArgs);
}
assert(user, "This user doesn't exist", NotFoundException);
assert(user.passwordHash, 'Incorrect login method', ForbiddenException);
@ -62,6 +50,53 @@ export class AuthService {
return user;
}
async signUp(signUpInput: SignUpInput) {
const existingUser = await this.userService.findUnique({
where: {
email: signUpInput.email,
},
});
assert(!existingUser, 'This user already exists', ForbiddenException);
const isPasswordValid = PASSWORD_REGEX.test(signUpInput.password);
assert(isPasswordValid, 'Password too weak', BadRequestException);
const passwordHash = await hashPassword(signUpInput.password);
if (signUpInput.workspaceInviteHash) {
const workspace = await this.workspaceService.findFirst({
where: {
inviteHash: signUpInput.workspaceInviteHash,
},
});
assert(
workspace,
'This workspace inviteHash is invalid',
ForbiddenException,
);
return await this.userService.createUser(
{
data: {
email: signUpInput.email,
passwordHash,
locale: 'en',
},
} as Prisma.UserCreateArgs,
workspace.id,
);
}
return await this.userService.createUser({
data: {
email: signUpInput.email,
passwordHash,
locale: 'en',
},
} as Prisma.UserCreateArgs);
}
async verify(
email: string,
select: Prisma.UserSelect & {
@ -101,4 +136,16 @@ export class AuthService {
return { exists: !!user };
}
async checkWorkspaceInviteHashIsValid(
inviteHash: string,
): Promise<WorkspaceInviteHashValid> {
const workspace = await this.workspaceService.findFirst({
where: {
inviteHash,
},
});
return { isValid: !!workspace };
}
}