update oauth
This commit is contained in:
@@ -2,7 +2,20 @@ import { BadRequestException, ForbiddenException, Injectable, NotFoundException
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { OAuthClientType } from '../generated/prisma/client';
|
||||
import { PrismaService } from '../infra/prisma.service';
|
||||
import { AccessService } from './access.service';
|
||||
import { AccessService, UserAccessContext } from './access.service';
|
||||
|
||||
export interface OAuthClientListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
clientId: string;
|
||||
redirectUris: string[];
|
||||
scopes: string[];
|
||||
isActive: boolean;
|
||||
type: OAuthClientType;
|
||||
createdById?: string | null;
|
||||
createdByDisplayName?: string | null;
|
||||
canManage: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RbacService {
|
||||
@@ -79,7 +92,7 @@ export class RbacService {
|
||||
}
|
||||
|
||||
async createOAuthClient(actorUserId: string, data: { name: string; redirectUris: string[]; scopes: string[]; type?: OAuthClientType }) {
|
||||
await this.assertOAuthPermission(actorUserId);
|
||||
await this.assertCanCreateOAuth(actorUserId);
|
||||
if (!data.redirectUris.length) {
|
||||
throw new BadRequestException('Укажите хотя бы один redirect URI');
|
||||
}
|
||||
@@ -95,13 +108,14 @@ export class RbacService {
|
||||
clientSecret,
|
||||
redirectUris: data.redirectUris,
|
||||
type: data.type ?? OAuthClientType.CONFIDENTIAL,
|
||||
createdById: actorUserId,
|
||||
scopes: {
|
||||
create: data.scopes.map((slug) => ({
|
||||
scope: { connect: { slug } }
|
||||
}))
|
||||
}
|
||||
},
|
||||
include: { scopes: { include: { scope: true } } }
|
||||
include: { scopes: { include: { scope: true } }, createdBy: true }
|
||||
});
|
||||
|
||||
return { client, clientSecret };
|
||||
@@ -112,7 +126,6 @@ export class RbacService {
|
||||
clientId: string,
|
||||
data: { name?: string; redirectUris?: string[]; scopes?: string[]; isActive?: boolean }
|
||||
) {
|
||||
await this.assertOAuthPermission(actorUserId);
|
||||
const existing = await this.prisma.oAuthClient.findUnique({
|
||||
where: { clientId },
|
||||
include: { scopes: true }
|
||||
@@ -120,6 +133,7 @@ export class RbacService {
|
||||
if (!existing) {
|
||||
throw new NotFoundException('OAuth-приложение не найдено');
|
||||
}
|
||||
await this.assertCanManageOAuthClient(actorUserId, existing);
|
||||
|
||||
if (data.redirectUris && !data.redirectUris.length) {
|
||||
throw new BadRequestException('Укажите хотя бы один redirect URI');
|
||||
@@ -143,18 +157,18 @@ export class RbacService {
|
||||
redirectUris: data.redirectUris,
|
||||
isActive: data.isActive
|
||||
},
|
||||
include: { scopes: { include: { scope: true } } }
|
||||
include: { scopes: { include: { scope: true } }, createdBy: true }
|
||||
});
|
||||
|
||||
return { client: updated, clientSecret: undefined as string | undefined };
|
||||
}
|
||||
|
||||
async rotateOAuthSecret(actorUserId: string, clientId: string) {
|
||||
await this.assertOAuthPermission(actorUserId);
|
||||
const existing = await this.prisma.oAuthClient.findUnique({ where: { clientId } });
|
||||
if (!existing) {
|
||||
throw new NotFoundException('OAuth-приложение не найдено');
|
||||
}
|
||||
await this.assertCanManageOAuthClient(actorUserId, existing);
|
||||
if (existing.type === OAuthClientType.PUBLIC) {
|
||||
throw new BadRequestException('У публичного клиента нет client secret');
|
||||
}
|
||||
@@ -168,24 +182,72 @@ export class RbacService {
|
||||
return { clientId, clientSecret };
|
||||
}
|
||||
|
||||
async listOAuthClients() {
|
||||
return this.prisma.oAuthClient.findMany({
|
||||
async listOAuthClients(actorUserId: string): Promise<OAuthClientListItem[]> {
|
||||
const { access } = await this.getActorAccess(actorUserId);
|
||||
if (!access.canViewOAuth) {
|
||||
throw new ForbiddenException('Недостаточно прав для просмотра OAuth-приложений');
|
||||
}
|
||||
|
||||
const clients = await this.prisma.oAuthClient.findMany({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { scopes: { include: { scope: true } } }
|
||||
include: { scopes: { include: { scope: true } }, createdBy: true }
|
||||
});
|
||||
|
||||
return clients.map((client) => this.toOAuthClientListItem(client, actorUserId, access));
|
||||
}
|
||||
|
||||
private async assertOAuthPermission(actorUserId: string) {
|
||||
private toOAuthClientListItem(
|
||||
client: {
|
||||
id: string;
|
||||
name: string;
|
||||
clientId: string;
|
||||
redirectUris: string[];
|
||||
isActive: boolean;
|
||||
type: OAuthClientType;
|
||||
createdById: string | null;
|
||||
createdBy?: { displayName: string } | null;
|
||||
scopes: Array<{ scope: { slug: string } }>;
|
||||
},
|
||||
actorUserId: string,
|
||||
access: UserAccessContext
|
||||
): OAuthClientListItem {
|
||||
return {
|
||||
id: client.id,
|
||||
name: client.name,
|
||||
clientId: client.clientId,
|
||||
redirectUris: client.redirectUris,
|
||||
scopes: client.scopes.map((link) => link.scope.slug),
|
||||
isActive: client.isActive,
|
||||
type: client.type,
|
||||
createdById: client.createdById,
|
||||
createdByDisplayName: client.createdBy?.displayName ?? null,
|
||||
canManage: this.access.canManageOAuthClient(access, actorUserId, client)
|
||||
};
|
||||
}
|
||||
|
||||
private async getActorAccess(actorUserId: string) {
|
||||
const actor = await this.prisma.user.findUnique({ where: { id: actorUserId } });
|
||||
if (!actor) {
|
||||
throw new NotFoundException('Пользователь не найден');
|
||||
}
|
||||
const access = await this.access.getUserAccess(actor.id, actor.isSuperAdmin);
|
||||
return { actor, access };
|
||||
}
|
||||
|
||||
private async assertCanCreateOAuth(actorUserId: string) {
|
||||
const { access } = await this.getActorAccess(actorUserId);
|
||||
if (!access.canManageOAuth) {
|
||||
throw new ForbiddenException('Недостаточно прав для управления OAuth-приложениями');
|
||||
}
|
||||
}
|
||||
|
||||
private async assertCanManageOAuthClient(actorUserId: string, client: { createdById: string | null }) {
|
||||
const { access } = await this.getActorAccess(actorUserId);
|
||||
if (!this.access.canManageOAuthClient(access, actorUserId, client)) {
|
||||
throw new ForbiddenException('Недостаточно прав для изменения этого OAuth-приложения');
|
||||
}
|
||||
}
|
||||
|
||||
private async getUserRoleSlugs(userId: string) {
|
||||
const links = await this.prisma.userRole.findMany({
|
||||
where: { userId },
|
||||
|
||||
Reference in New Issue
Block a user