add oauth app connected
This commit is contained in:
@@ -791,6 +791,7 @@ export class AuthGrpcController {
|
||||
scope: string;
|
||||
state?: string;
|
||||
nonce?: string;
|
||||
grantConsent?: boolean;
|
||||
}) {
|
||||
return this.oauthCore.authorize(command);
|
||||
}
|
||||
@@ -805,6 +806,26 @@ export class AuthGrpcController {
|
||||
return this.oauthCore.userInfo(command.accessToken);
|
||||
}
|
||||
|
||||
@GrpcMethod('OAuthCoreService', 'CheckOAuthConsent')
|
||||
checkOAuthConsent(command: { userId: string; clientId: string; scope: string }) {
|
||||
return this.oauthCore.checkConsent(command.userId, command.clientId, command.scope);
|
||||
}
|
||||
|
||||
@GrpcMethod('OAuthCoreService', 'GrantOAuthConsent')
|
||||
grantOAuthConsent(command: { userId: string; clientId: string; scope: string }) {
|
||||
return this.oauthCore.grantConsent(command.userId, command.clientId, command.scope);
|
||||
}
|
||||
|
||||
@GrpcMethod('OAuthCoreService', 'ListUserOAuthConsents')
|
||||
listUserOAuthConsents(command: { userId: string }) {
|
||||
return this.oauthCore.listUserConsents(command.userId);
|
||||
}
|
||||
|
||||
@GrpcMethod('OAuthCoreService', 'RevokeOAuthConsent')
|
||||
revokeOAuthConsent(command: { userId: string; consentId: string }) {
|
||||
return this.oauthCore.revokeConsent(command.userId, command.consentId);
|
||||
}
|
||||
|
||||
@GrpcMethod('OtpService', 'SendOtp')
|
||||
sendOtp(command: { target: string; channel: string; purpose: string; userId?: string }) {
|
||||
return this.otp.send(command);
|
||||
|
||||
@@ -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: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user