postiz/apps/backend/src/services/auth/permissions/permissions.service.ts

177 lines
5.0 KiB
TypeScript

import { Ability, AbilityBuilder, AbilityClass } from '@casl/ability';
import { Injectable } from '@nestjs/common';
import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
import dayjs from 'dayjs';
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
export enum Sections {
CHANNEL = 'channel',
POSTS_PER_MONTH = 'posts_per_month',
TEAM_MEMBERS = 'team_members',
COMMUNITY_FEATURES = 'community_features',
FEATURED_BY_GITROOM = 'featured_by_gitroom',
AI = 'ai',
IMPORT_FROM_CHANNELS = 'import_from_channels',
ADMIN = 'admin',
WEBHOOKS = 'webhooks',
}
export enum AuthorizationActions {
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
export type AppAbility = Ability<[AuthorizationActions, Sections]>;
@Injectable()
export class PermissionsService {
constructor(
private _subscriptionService: SubscriptionService,
private _postsService: PostsService,
private _integrationService: IntegrationService,
private _webhooksService: WebhooksService
) {}
async getPackageOptions(orgId: string) {
const subscription =
await this._subscriptionService.getSubscriptionByOrganizationId(orgId);
const tier =
subscription?.subscriptionTier ||
(!process.env.STRIPE_PUBLISHABLE_KEY ? 'PRO' : 'FREE');
const { channel, ...all } = pricing[tier];
return {
subscription,
options: {
...all,
...{ channel: tier === 'FREE' ? channel : -10 },
},
};
}
async check(
orgId: string,
created_at: Date,
permission: 'USER' | 'ADMIN' | 'SUPERADMIN',
requestedPermission: Array<[AuthorizationActions, Sections]>
) {
const { can, build } = new AbilityBuilder<
Ability<[AuthorizationActions, Sections]>
>(Ability as AbilityClass<AppAbility>);
if (
requestedPermission.length === 0 ||
!process.env.STRIPE_PUBLISHABLE_KEY
) {
for (const [action, section] of requestedPermission) {
can(action, section);
}
return build({
detectSubjectType: (item) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
item.constructor,
});
}
const { subscription, options } = await this.getPackageOptions(orgId);
for (const [action, section] of requestedPermission) {
// check for the amount of channels
if (section === Sections.CHANNEL) {
const totalChannels = (
await this._integrationService.getIntegrationsList(orgId)
).filter((f) => !f.refreshNeeded).length;
if (
(options.channel && options.channel > totalChannels) ||
(subscription?.totalChannels || 0) > totalChannels
) {
can(action, section);
continue;
}
}
if (section === Sections.WEBHOOKS) {
const totalWebhooks = await this._webhooksService.getTotal(orgId);
if (totalWebhooks < options.webhooks) {
can(AuthorizationActions.Create, section);
continue;
}
}
// check for posts per month
if (section === Sections.POSTS_PER_MONTH) {
const createdAt =
(await this._subscriptionService.getSubscription(orgId))?.createdAt ||
created_at;
const totalMonthPast = Math.abs(
dayjs(createdAt).diff(dayjs(), 'month')
);
const checkFrom = dayjs(createdAt).add(totalMonthPast, 'month');
const count = await this._postsService.countPostsFromDay(
orgId,
checkFrom.toDate()
);
if (count < options.posts_per_month) {
can(action, section);
continue;
}
}
if (section === Sections.TEAM_MEMBERS && options.team_members) {
can(action, section);
continue;
}
if (
section === Sections.ADMIN &&
['ADMIN', 'SUPERADMIN'].includes(permission)
) {
can(action, section);
continue;
}
if (
section === Sections.COMMUNITY_FEATURES &&
options.community_features
) {
can(action, section);
continue;
}
if (
section === Sections.FEATURED_BY_GITROOM &&
options.featured_by_gitroom
) {
can(action, section);
continue;
}
if (section === Sections.AI && options.ai) {
can(action, section);
continue;
}
if (
section === Sections.IMPORT_FROM_CHANNELS &&
options.import_from_channels
) {
can(action, section);
}
}
return build({
detectSubjectType: (item) =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
item.constructor,
});
}
}