add oauth app connected

This commit is contained in:
lendry
2026-06-26 10:49:34 +03:00
parent d5e6b58955
commit b81c0cedbb
18 changed files with 838 additions and 59 deletions

View File

@@ -44,6 +44,7 @@ export class OAuthCoreService {
scope: string;
state?: string;
nonce?: string;
grantConsent?: boolean;
}) {
const user = await this.prisma.user.findFirst({
where: { id: command.userId, deletedAt: null, status: 'ACTIVE', ...HUMAN_USER_WHERE }
@@ -58,6 +59,15 @@ export class OAuthCoreService {
throw new BadRequestException('OAuth приложение не найдено или redirect_uri запрещен');
}
if (command.grantConsent) {
await this.persistConsent(command.userId, client.id, command.scope);
} else {
const consent = await this.checkConsent(command.userId, command.clientId, command.scope);
if (!consent.granted) {
throw new BadRequestException('Согласие пользователя на доступ не получено');
}
}
const code = randomBytes(32).toString('base64url');
await this.prisma.oAuthAuthorizationCode.create({
data: {
@@ -230,4 +240,119 @@ export class OAuthCoreService {
return { accessToken, tokenType: 'Bearer', expiresIn: 900, refreshToken, idToken };
}
async checkConsent(userId: string, clientId: string, scope: string) {
const client = await this.prisma.oAuthClient.findUnique({ where: { clientId } });
if (!client?.isActive) {
throw new BadRequestException('OAuth приложение не найдено');
}
const requestedSlugs = this.parseScopeSlugs(scope);
const requestedScopes = await this.resolveScopeInfos(requestedSlugs);
const grantedSlugs = await this.loadGrantedScopeSlugs(userId, client.id);
const granted = requestedSlugs.every((slug) => grantedSlugs.has(slug));
return {
granted,
client: { clientId: client.clientId, name: client.name },
requestedScopes
};
}
async grantConsent(userId: string, clientId: string, scope: string) {
const client = await this.prisma.oAuthClient.findUnique({ where: { clientId } });
if (!client?.isActive) {
throw new BadRequestException('OAuth приложение не найдено');
}
await this.persistConsent(userId, client.id, scope);
return { granted: true };
}
async listUserConsents(userId: string) {
const consents = await this.prisma.oAuthConsent.findMany({
where: { userId },
include: {
client: true,
scopes: { include: { scope: true } }
},
orderBy: { updatedAt: 'desc' }
});
return {
consents: consents.map((consent) => ({
id: consent.id,
clientId: consent.client.clientId,
clientName: consent.client.name,
scopes: consent.scopes.map((item) => ({
slug: item.scope.slug,
name: item.scope.name,
description: item.scope.description ?? undefined
})),
grantedAt: consent.createdAt.toISOString(),
updatedAt: consent.updatedAt.toISOString()
}))
};
}
async revokeConsent(userId: string, consentId: string) {
const consent = await this.prisma.oAuthConsent.findFirst({
where: { id: consentId, userId }
});
if (!consent) {
throw new BadRequestException('Согласие не найдено');
}
await this.prisma.oAuthConsent.delete({ where: { id: consent.id } });
return { count: 1 };
}
private parseScopeSlugs(scope: string) {
return [...new Set(scope.split(/\s+/).map((item) => item.trim()).filter(Boolean))];
}
private async resolveScopeInfos(slugs: string[]) {
if (slugs.length === 0) {
return [{ slug: 'openid', name: 'OpenID', description: 'Базовая идентификация OpenID Connect' }];
}
const records = await this.prisma.oAuthScope.findMany({ where: { slug: { in: slugs } } });
const bySlug = new Map(records.map((item) => [item.slug, item]));
return slugs.map((slug) => {
const record = bySlug.get(slug);
return {
slug,
name: record?.name ?? slug,
description: record?.description ?? undefined
};
});
}
private async loadGrantedScopeSlugs(userId: string, clientInternalId: string) {
const consent = await this.prisma.oAuthConsent.findUnique({
where: { userId_clientId: { userId, clientId: clientInternalId } },
include: { scopes: { include: { scope: true } } }
});
return new Set(consent?.scopes.map((item) => item.scope.slug) ?? []);
}
private async persistConsent(userId: string, clientInternalId: string, scope: string) {
const slugs = this.parseScopeSlugs(scope);
const scopeRecords = await this.prisma.oAuthScope.findMany({ where: { slug: { in: slugs } } });
const consent = await this.prisma.oAuthConsent.upsert({
where: { userId_clientId: { userId, clientId: clientInternalId } },
create: { userId, clientId: clientInternalId },
update: { updatedAt: new Date() }
});
for (const scopeRecord of scopeRecords) {
await this.prisma.oAuthConsentScope.upsert({
where: { consentId_scopeId: { consentId: consent.id, scopeId: scopeRecord.id } },
create: { consentId: consent.id, scopeId: scopeRecord.id },
update: {}
});
}
}
}