feat: implement user impersonation feature (#976)

* feat: wip impersonate user

* feat: add ability to impersonate an user

* fix: remove console.log

* fix: unused import
This commit is contained in:
Jérémy M
2023-08-01 00:47:29 +02:00
committed by GitHub
parent b028d9fd2a
commit f111440e00
24 changed files with 547 additions and 30 deletions

View File

@ -1,5 +1,9 @@
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { BadRequestException } from '@nestjs/common';
import {
BadRequestException,
ForbiddenException,
UseGuards,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
@ -7,6 +11,10 @@ import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { AuthUser } from 'src/decorators/auth-user.decorator';
import { assert } from 'src/utils/assert';
import { User } from 'src/core/@generated/user/user.model';
import { AuthTokens } from './dto/token.entity';
import { TokenService } from './services/token.service';
@ -21,6 +29,7 @@ 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';
import { ImpersonateInput } from './dto/impersonate.input';
@Resolver()
export class AuthResolver {
@ -96,4 +105,30 @@ export class AuthResolver {
return { tokens: tokens };
}
@UseGuards(JwtAuthGuard)
@Mutation(() => Verify)
async impersonate(
@Args() impersonateInput: ImpersonateInput,
@AuthUser() user: User,
@PrismaSelector({
modelName: 'User',
defaultFields: {
User: {
id: true,
workspaceMember: { select: { allowImpersonation: true } },
},
},
})
prismaSelect: PrismaSelect<'User'>,
): Promise<Verify> {
// Check if user can impersonate
assert(user.canImpersonate, 'User cannot impersonate', ForbiddenException);
const select = prismaSelect.valueOf('user') as Prisma.UserSelect & {
id: true;
workspaceMember: { select: { allowImpersonation: true } };
};
return this.authService.impersonate(impersonateInput.userId, select);
}
}

View File

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

View File

@ -7,5 +7,5 @@ import { AuthTokens } from './token.entity';
@ObjectType()
export class Verify extends AuthTokens {
@Field(() => User)
user: Partial<User>;
user: DeepPartial<User>;
}

View File

@ -165,4 +165,41 @@ export class AuthService {
return { isValid: !!workspace };
}
async impersonate(
userId: string,
select: Prisma.UserSelect & {
id: true;
workspaceMember: {
select: {
allowImpersonation: true;
};
};
},
) {
const user = await this.userService.findUnique({
where: {
id: userId,
},
select,
});
assert(user, "This user doesn't exist", NotFoundException);
assert(
user.workspaceMember?.allowImpersonation,
'Impersonation not allowed',
ForbiddenException,
);
const accessToken = await this.tokenService.generateAccessToken(user.id);
const refreshToken = await this.tokenService.generateRefreshToken(user.id);
return {
user,
tokens: {
accessToken,
refreshToken,
},
};
}
}