Configuring access rights in the CRM Super Admin

This commit is contained in:
2026-09-20 11:24:14 +03:30
parent 04d37a9d12
commit 4a9fe0b07c
29 changed files with 1235 additions and 35 deletions
+2
View File
@@ -3,6 +3,7 @@ import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MembershipsModule } from '../memberships/memberships.module';
import { BillingModule } from '../billing/billing.module';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
@@ -13,6 +14,7 @@ import { SmsService } from '../common/sms.service';
imports: [
ConfigModule,
MembershipsModule,
BillingModule,
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.registerAsync({
imports: [ConfigModule],
+33 -2
View File
@@ -10,12 +10,19 @@ import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import * as path from 'path';
import type { AuthUserDto, LoginResponse, LoginResult } from '@mento/shared';
import type {
AuthUserDto,
LoginResponse,
LoginResult,
OrgModuleKey,
SubscriptionSummaryDto,
} from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { SmsService } from '../common/sms.service';
import { LocalFileStorage } from '../storage/local-file.storage';
import { MembershipsService } from '../memberships/memberships.service';
import { BillingService } from '../billing/billing.service';
import {
LoginDto,
LoginOtpRequestDto,
@@ -97,6 +104,7 @@ export class AuthService {
private readonly sms: SmsService,
private readonly files: LocalFileStorage,
private readonly memberships: MembershipsService,
private readonly billing: BillingService,
) {}
/**
@@ -260,6 +268,11 @@ export class AuthService {
organizationId: organization.id,
role: 'owner',
});
await this.billing.ensureTrialForOwner(owner.id, { maxOrganizations: 1 });
await this.prisma.organization.update({
where: { id: organization.id },
data: { enabledModules: this.billing.defaultModulesForNewOrg() },
});
await this.prisma.authChallenge.update({
where: { id: challenge.id },
@@ -578,6 +591,9 @@ export class AuthService {
if (!user || user.isActive === false) {
throw new UnauthorizedException('حساب کاربری غیرفعال است');
}
const subscription = await this.billing.requireActiveForOrganization(
organizationId,
);
await this.memberships.syncUserLastOrg(
userId,
organizationId,
@@ -597,6 +613,7 @@ export class AuthService {
user: await this.toAuthUserDto(
{ ...user, role: membership.role, organizationId },
organizationId,
subscription,
),
};
}
@@ -611,19 +628,29 @@ export class AuthService {
organizationId: string;
},
organizationId: string,
subscription?: SubscriptionSummaryDto | null,
): Promise<AuthUserDto> {
const membership = await this.prisma.membership.findFirst({
where: { userId: user.id, organizationId, isActive: true },
include: { organization: { select: { name: true } } },
include: {
organization: { select: { name: true, enabledModules: true } },
},
});
const role = membership?.role ?? user.role;
const orgName = membership?.organization.name;
const sub =
subscription ??
(await this.billing.requireActiveForOrganization(organizationId));
return this.toAuthUser({
...user,
role,
organizationId,
organizationName: orgName,
avatarStorageKey: await this.readAvatarKey(user.id),
enabledModules: this.billing.normalizeModules(
membership?.organization.enabledModules,
),
subscription: sub,
});
}
@@ -708,6 +735,8 @@ export class AuthService {
organizationId: string;
organizationName?: string;
avatarStorageKey?: string | null;
enabledModules?: OrgModuleKey[];
subscription?: SubscriptionSummaryDto | null;
}): AuthUserDto {
return {
id: user.id,
@@ -718,6 +747,8 @@ export class AuthService {
organizationName: user.organizationName,
hasAvatar: Boolean(user.avatarStorageKey),
...(user.mobile ? { mobile: user.mobile } : {}),
...(user.enabledModules ? { enabledModules: user.enabledModules } : {}),
...(user.subscription ? { subscription: user.subscription } : {}),
};
}
}
+3
View File
@@ -2,6 +2,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { BillingService } from '../billing/billing.service';
import { PrismaService } from '../prisma/prisma.service';
export type JwtPayload = {
@@ -15,6 +16,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
config: ConfigService,
private readonly prisma: PrismaService,
private readonly billing: BillingService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
@@ -53,6 +55,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
if (!membership) {
throw new UnauthorizedException();
}
await this.billing.requireActiveForOrganization(membership.organizationId);
return {
userId: user.id,
organizationId: membership.organizationId,