1043 timebox prepare zapier integration (#1967)
* Add create api-key route * Import module * Remove required mutation parameter * Fix Authentication * Generate random key * Update Read ApiKeyAbility handler * Add findMany apiKey route * Remove useless attribute * Use signed token for apiKeys * Authenticate with api keys * Fix typo * Add a test for apiKey module * Revoke token when api key does not exist * Handler expiresAt parameter * Fix user passport * Code review returns: Add API_TOKEN_SECRET * Code review returns: Rename variable * Code review returns: Update code style * Update apiKey schema * Update create token route * Update delete token route * Filter revoked api keys from listApiKeys * Rename endpoint * Set default expiry to 2 years * Code review returns: Update comment * Generate token after create apiKey * Code review returns: Update env variable * Code review returns: Move method to proper service --------- Co-authored-by: martmull <martmull@hotmail.com>
This commit is contained in:
@ -6,6 +6,7 @@ import {
|
||||
Activity,
|
||||
ActivityTarget,
|
||||
Attachment,
|
||||
ApiKey,
|
||||
Comment,
|
||||
Company,
|
||||
Favorite,
|
||||
@ -30,6 +31,7 @@ type SubjectsAbility = Subjects<{
|
||||
Activity: Activity;
|
||||
ActivityTarget: ActivityTarget;
|
||||
Attachment: Attachment;
|
||||
ApiKey: ApiKey;
|
||||
Comment: Comment;
|
||||
Company: Company;
|
||||
Favorite: Favorite;
|
||||
@ -55,7 +57,7 @@ export type AppAbility = PureAbility<
|
||||
|
||||
@Injectable()
|
||||
export class AbilityFactory {
|
||||
defineAbility(user: User, workspace: Workspace) {
|
||||
defineAbility(workspace: Workspace, user?: User) {
|
||||
const { can, cannot, build } = new AbilityBuilder<AppAbility>(
|
||||
createPrismaAbility,
|
||||
);
|
||||
@ -66,8 +68,18 @@ export class AbilityFactory {
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
});
|
||||
can(AbilityAction.Update, 'User', { id: user.id });
|
||||
can(AbilityAction.Delete, 'User', { id: user.id });
|
||||
if (user) {
|
||||
can(AbilityAction.Update, 'User', { id: user.id });
|
||||
can(AbilityAction.Delete, 'User', { id: user.id });
|
||||
} else {
|
||||
cannot(AbilityAction.Update, 'User');
|
||||
cannot(AbilityAction.Delete, 'User');
|
||||
}
|
||||
|
||||
// ApiKey
|
||||
can(AbilityAction.Read, 'ApiKey', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Create, 'ApiKey');
|
||||
can(AbilityAction.Update, 'ApiKey', { workspaceId: workspace.id });
|
||||
|
||||
// Workspace
|
||||
can(AbilityAction.Read, 'Workspace');
|
||||
@ -76,12 +88,19 @@ export class AbilityFactory {
|
||||
|
||||
// Workspace Member
|
||||
can(AbilityAction.Read, 'WorkspaceMember', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Delete, 'WorkspaceMember', { workspaceId: workspace.id });
|
||||
cannot(AbilityAction.Delete, 'WorkspaceMember', { userId: user.id });
|
||||
can(AbilityAction.Update, 'WorkspaceMember', {
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
if (user) {
|
||||
can(AbilityAction.Delete, 'WorkspaceMember', {
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
cannot(AbilityAction.Delete, 'WorkspaceMember', { userId: user.id });
|
||||
can(AbilityAction.Update, 'WorkspaceMember', {
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
} else {
|
||||
cannot(AbilityAction.Delete, 'WorkspaceMember');
|
||||
cannot(AbilityAction.Update, 'WorkspaceMember');
|
||||
}
|
||||
|
||||
// Company
|
||||
can(AbilityAction.Read, 'Company', { workspaceId: workspace.id });
|
||||
@ -107,14 +126,19 @@ export class AbilityFactory {
|
||||
// Comment
|
||||
can(AbilityAction.Read, 'Comment', { workspaceId: workspace.id });
|
||||
can(AbilityAction.Create, 'Comment');
|
||||
can(AbilityAction.Update, 'Comment', {
|
||||
workspaceId: workspace.id,
|
||||
authorId: user.id,
|
||||
});
|
||||
can(AbilityAction.Delete, 'Comment', {
|
||||
workspaceId: workspace.id,
|
||||
authorId: user.id,
|
||||
});
|
||||
if (user) {
|
||||
can(AbilityAction.Update, 'Comment', {
|
||||
workspaceId: workspace.id,
|
||||
authorId: user.id,
|
||||
});
|
||||
can(AbilityAction.Delete, 'Comment', {
|
||||
workspaceId: workspace.id,
|
||||
authorId: user.id,
|
||||
});
|
||||
} else {
|
||||
cannot(AbilityAction.Update, 'Comment');
|
||||
cannot(AbilityAction.Delete, 'Comment');
|
||||
}
|
||||
|
||||
// ActivityTarget
|
||||
can(AbilityAction.Read, 'ActivityTarget');
|
||||
|
||||
@ -122,6 +122,12 @@ import {
|
||||
ReadViewFilterAbilityHandler,
|
||||
UpdateViewFilterAbilityHandler,
|
||||
} from './handlers/view-filter.ability-handler';
|
||||
import {
|
||||
CreateApiKeyAbilityHandler,
|
||||
UpdateApiKeyAbilityHandler,
|
||||
ManageApiKeyAbilityHandler,
|
||||
ReadApiKeyAbilityHandler,
|
||||
} from './handlers/api-key.ability-handler';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@ -229,6 +235,11 @@ import {
|
||||
CreateViewSortAbilityHandler,
|
||||
UpdateViewSortAbilityHandler,
|
||||
DeleteViewSortAbilityHandler,
|
||||
// ApiKey
|
||||
ReadApiKeyAbilityHandler,
|
||||
ManageApiKeyAbilityHandler,
|
||||
CreateApiKeyAbilityHandler,
|
||||
UpdateApiKeyAbilityHandler,
|
||||
],
|
||||
exports: [
|
||||
AbilityFactory,
|
||||
@ -333,6 +344,11 @@ import {
|
||||
CreateViewSortAbilityHandler,
|
||||
UpdateViewSortAbilityHandler,
|
||||
DeleteViewSortAbilityHandler,
|
||||
// ApiKey
|
||||
ReadApiKeyAbilityHandler,
|
||||
ManageApiKeyAbilityHandler,
|
||||
CreateApiKeyAbilityHandler,
|
||||
UpdateApiKeyAbilityHandler,
|
||||
],
|
||||
})
|
||||
export class AbilityModule {}
|
||||
|
||||
85
server/src/ability/handlers/api-key.ability-handler.ts
Normal file
85
server/src/ability/handlers/api-key.ability-handler.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
|
||||
import { subject } from '@casl/ability';
|
||||
|
||||
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
|
||||
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import { AbilityAction } from 'src/ability/ability.action';
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
import { ApiKeyWhereUniqueInput } from 'src/core/@generated/api-key/api-key-where-unique.input';
|
||||
import { ApiKeyWhereInput } from 'src/core/@generated/api-key/api-key-where.input';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import {
|
||||
convertToWhereInput,
|
||||
relationAbilityChecker,
|
||||
} from 'src/ability/ability.util';
|
||||
|
||||
class ApiKeyArgs {
|
||||
where?: ApiKeyWhereUniqueInput | ApiKeyWhereInput;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ManageApiKeyAbilityHandler implements IAbilityHandler {
|
||||
async handle(ability: AppAbility) {
|
||||
return ability.can(AbilityAction.Manage, 'ApiKey');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ReadApiKeyAbilityHandler implements IAbilityHandler {
|
||||
async handle(ability: AppAbility) {
|
||||
return ability.can(AbilityAction.Read, 'ApiKey');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CreateApiKeyAbilityHandler implements IAbilityHandler {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const args = gqlContext.getArgs();
|
||||
const allowed = await relationAbilityChecker(
|
||||
'ApiKey',
|
||||
ability,
|
||||
this.prismaService.client,
|
||||
args,
|
||||
);
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
return ability.can(AbilityAction.Create, 'ApiKey');
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UpdateApiKeyAbilityHandler implements IAbilityHandler {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const args = gqlContext.getArgs<ApiKeyArgs>();
|
||||
const where = convertToWhereInput(args.where);
|
||||
const apiKey = await this.prismaService.client.apiKey.findFirst({
|
||||
where,
|
||||
});
|
||||
assert(apiKey, '', NotFoundException);
|
||||
const allowed = await relationAbilityChecker(
|
||||
'ApiKey',
|
||||
ability,
|
||||
this.prismaService.client,
|
||||
args,
|
||||
);
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
return ability.can(AbilityAction.Update, subject('ApiKey', apiKey));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user