update oauth
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { PrismaService } from '../infra/prisma.service';
|
||||
import { OAuthIssuerService } from './oauth-issuer.service';
|
||||
|
||||
@Injectable()
|
||||
export class OAuthCoreService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly jwt: JwtService,
|
||||
private readonly config: ConfigService
|
||||
private readonly config: ConfigService,
|
||||
private readonly oauthIssuer: OAuthIssuerService
|
||||
) {}
|
||||
|
||||
async authorize(command: { userId: string; clientId: string; redirectUri: string; scope: string; state?: string }) {
|
||||
@@ -70,9 +71,10 @@ export class OAuthCoreService {
|
||||
}
|
||||
|
||||
private async issueOAuthTokens(userId: string, clientId: string, scopes: string[]) {
|
||||
const accessToken = await this.jwt.signAsync({ sub: userId, aud: clientId, scopes, typ: 'access_token' }, { secret: this.config.getOrThrow<string>('JWT_ACCESS_SECRET'), expiresIn: '15m', issuer: 'id.lendry.ru' });
|
||||
const refreshToken = await this.jwt.signAsync({ sub: userId, aud: clientId, scopes, typ: 'refresh_token' }, { secret: this.config.getOrThrow<string>('JWT_REFRESH_SECRET'), expiresIn: '30d', issuer: 'id.lendry.ru' });
|
||||
const idToken = await this.jwt.signAsync({ sub: userId, aud: clientId }, { secret: this.config.getOrThrow<string>('JWT_ACCESS_SECRET'), expiresIn: '15m', issuer: 'id.lendry.ru' });
|
||||
const issuer = await this.oauthIssuer.resolveIssuer();
|
||||
const accessToken = await this.jwt.signAsync({ sub: userId, aud: clientId, scopes, typ: 'access_token' }, { secret: this.config.getOrThrow<string>('JWT_ACCESS_SECRET'), expiresIn: '15m', issuer });
|
||||
const refreshToken = await this.jwt.signAsync({ sub: userId, aud: clientId, scopes, typ: 'refresh_token' }, { secret: this.config.getOrThrow<string>('JWT_REFRESH_SECRET'), expiresIn: '30d', issuer });
|
||||
const idToken = await this.jwt.signAsync({ sub: userId, aud: clientId }, { secret: this.config.getOrThrow<string>('JWT_ACCESS_SECRET'), expiresIn: '15m', issuer });
|
||||
return { accessToken, tokenType: 'Bearer', expiresIn: 900, refreshToken, idToken };
|
||||
}
|
||||
}
|
||||
|
||||
30
apps/sso-core/src/domain/oauth-issuer.service.ts
Normal file
30
apps/sso-core/src/domain/oauth-issuer.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { SettingsService } from './settings.service';
|
||||
|
||||
@Injectable()
|
||||
export class OAuthIssuerService {
|
||||
constructor(
|
||||
private readonly settings: SettingsService,
|
||||
private readonly config: ConfigService
|
||||
) {}
|
||||
|
||||
async resolveIssuer(): Promise<string> {
|
||||
const fromDb = (await this.settings.getValue('PUBLIC_API_URL', '')).trim();
|
||||
if (fromDb) {
|
||||
return fromDb.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
const fromEnv = this.config.get<string>('PUBLIC_API_URL')?.trim();
|
||||
if (fromEnv) {
|
||||
return fromEnv.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
const domain = (await this.settings.getValue('PROJECT_DOMAIN', 'id.lendry.ru')).trim();
|
||||
if (domain.startsWith('http://') || domain.startsWith('https://')) {
|
||||
return domain.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
return `https://${domain.replace(/^\/+/, '')}`;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,12 @@ import { PrismaService } from '../infra/prisma.service';
|
||||
export const DEFAULT_SYSTEM_SETTINGS = [
|
||||
{ key: 'PROJECT_NAME', value: 'MVK ID', description: 'Название проекта в интерфейсе и заголовке страницы' },
|
||||
{ key: 'PROJECT_TAGLINE', value: 'Единый аккаунт для сервисов Lendry', description: 'Краткий слоган под названием проекта' },
|
||||
{ key: 'PROJECT_DOMAIN', value: 'id.lendry.ru', description: 'Основной домен IdP' },
|
||||
{ key: 'PROJECT_DOMAIN', value: 'id.lendry.ru', description: 'Основной домен IdP (без пути)' },
|
||||
{
|
||||
key: 'PUBLIC_API_URL',
|
||||
value: 'http://localhost:3000',
|
||||
description: 'Публичный базовый URL API для OAuth и OpenID Connect (issuer). Пример: https://sso.example.ru/idp-api'
|
||||
},
|
||||
{ key: 'PIN_LOCK_TIMEOUT_MINUTES', value: '15', description: 'Через сколько минут неактивности сессия блокируется PIN-кодом' },
|
||||
{ key: 'PIN_DELETE_GRACE_MINUTES', value: '1440', description: 'Задержка перед окончательным удалением PIN-кода после запроса (минуты)' },
|
||||
{ key: 'PIN_REQUIRE_ON_DELETE', value: 'true', description: 'Требовать текущий PIN-код при запросе удаления защиты' },
|
||||
@@ -91,6 +96,7 @@ export const PUBLIC_SETTING_KEYS = [
|
||||
'PROJECT_NAME',
|
||||
'PROJECT_TAGLINE',
|
||||
'PROJECT_DOMAIN',
|
||||
'PUBLIC_API_URL',
|
||||
'REGISTRATION_ENABLED',
|
||||
'MAINTENANCE_MODE',
|
||||
'LDAP_ENABLED',
|
||||
@@ -109,5 +115,18 @@ export class SystemSettingsSeedService implements OnModuleInit {
|
||||
update: { description: setting.description, isSecret: SECRET_SETTING_KEYS.has(setting.key) }
|
||||
});
|
||||
}
|
||||
|
||||
const envPublicApiUrl = process.env.PUBLIC_API_URL?.trim();
|
||||
if (envPublicApiUrl) {
|
||||
await this.prisma.systemSetting.upsert({
|
||||
where: { key: 'PUBLIC_API_URL' },
|
||||
create: {
|
||||
key: 'PUBLIC_API_URL',
|
||||
value: envPublicApiUrl,
|
||||
description: 'Публичный базовый URL API для OAuth и OpenID Connect (issuer). Пример: https://sso.example.ru/idp-api'
|
||||
},
|
||||
update: { value: envPublicApiUrl }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user