global update and global fix

This commit is contained in:
lendry
2026-06-25 23:48:57 +03:00
parent 3880c68d59
commit b0ea87e898
121 changed files with 13759 additions and 663 deletions

View File

@@ -37,6 +37,38 @@ export async function resolveOAuthIssuer(core: CoreGrpcService, fallback = 'http
return normalizeBaseUrl(fallback);
}
export async function resolveFrontendUrl(core: CoreGrpcService, fallback = 'http://localhost:3002'): Promise<string> {
const envFrontend = process.env.PUBLIC_FRONTEND_URL?.trim();
if (envFrontend) {
return normalizeBaseUrl(envFrontend);
}
try {
const response = (await firstValueFrom(core.settings.GetSetting({ key: 'PUBLIC_FRONTEND_URL' }))) as { value?: string };
const fromDb = response.value?.trim();
if (fromDb) {
return normalizeBaseUrl(fromDb);
}
} catch {
// fallback ниже
}
try {
const response = (await firstValueFrom(core.settings.GetSetting({ key: 'PROJECT_DOMAIN' }))) as { value?: string };
const domain = response.value?.trim();
if (domain) {
if (domain.startsWith('http://') || domain.startsWith('https://')) {
return normalizeBaseUrl(domain);
}
return `https://${domain.replace(/^\/+/, '')}`;
}
} catch {
// fallback ниже
}
return normalizeBaseUrl(fallback);
}
export function buildOpenIdConfiguration(issuer: string) {
const base = normalizeBaseUrl(issuer);
return {
@@ -49,8 +81,9 @@ export function buildOpenIdConfiguration(issuer: string) {
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['HS256'],
scopes_supported: ['openid', 'profile', 'email', 'phone', 'address', 'documents'],
token_endpoint_auth_methods_supported: ['client_secret_post'],
token_endpoint_auth_methods_supported: ['client_secret_post', 'client_secret_basic'],
grant_types_supported: ['authorization_code', 'refresh_token'],
code_challenge_methods_supported: ['S256', 'plain'],
claims_supported: ['sub', 'email', 'phone', 'name', 'picture']
};
}

View File

@@ -0,0 +1,147 @@
import { BadRequestException } from '@nestjs/common';
export interface NormalizedAuthorizeQuery {
userId?: string;
clientId: string;
redirectUri: string;
scope: string;
state?: string;
responseType?: string;
codeChallenge?: string;
codeChallengeMethod?: string;
nonce?: string;
}
export interface NormalizedTokenBody {
grantType: string;
code?: string;
refreshToken?: string;
clientId: string;
clientSecret?: string;
redirectUri?: string;
codeVerifier?: string;
}
function readString(value: unknown) {
if (typeof value === 'string') return value.trim();
if (Array.isArray(value) && typeof value[0] === 'string') return value[0].trim();
return undefined;
}
export function normalizeAuthorizeQuery(query: Record<string, unknown>): NormalizedAuthorizeQuery {
const clientId = readString(query.clientId) ?? readString(query.client_id);
const redirectUri = readString(query.redirectUri) ?? readString(query.redirect_uri);
const scope = readString(query.scope) ?? 'openid profile';
const userId = readString(query.userId) ?? readString(query.user_id);
const state = readString(query.state);
const responseType = readString(query.response_type) ?? readString(query.responseType);
const codeChallenge = readString(query.code_challenge) ?? readString(query.codeChallenge);
const codeChallengeMethod = readString(query.code_challenge_method) ?? readString(query.codeChallengeMethod);
const nonce = readString(query.nonce);
if (!clientId) {
throw new BadRequestException('Укажите client_id');
}
if (!redirectUri) {
throw new BadRequestException('Укажите redirect_uri');
}
if (responseType && responseType !== 'code') {
throw new BadRequestException('Поддерживается только response_type=code');
}
return {
userId,
clientId,
redirectUri,
scope,
state,
responseType,
codeChallenge,
codeChallengeMethod,
nonce
};
}
export function normalizeTokenBody(body: Record<string, unknown>): NormalizedTokenBody {
const grantType = readString(body.grantType) ?? readString(body.grant_type);
const clientId = readString(body.clientId) ?? readString(body.client_id);
const clientSecret = readString(body.clientSecret) ?? readString(body.client_secret);
const code = readString(body.code);
const refreshToken = readString(body.refreshToken) ?? readString(body.refresh_token);
const redirectUri = readString(body.redirectUri) ?? readString(body.redirect_uri);
const codeVerifier = readString(body.codeVerifier) ?? readString(body.code_verifier);
if (!grantType) {
throw new BadRequestException('Укажите grant_type');
}
if (!clientId) {
throw new BadRequestException('Укажите client_id');
}
return {
grantType,
clientId,
clientSecret,
code,
refreshToken,
redirectUri,
codeVerifier
};
}
export function appendQueryParams(baseUrl: string, query: Record<string, unknown>) {
const url = new URL(baseUrl);
for (const [key, value] of Object.entries(query)) {
if (value === undefined || value === null || value === '') continue;
if (Array.isArray(value)) {
value.forEach((item) => url.searchParams.append(key, String(item)));
continue;
}
url.searchParams.set(key, String(value));
}
return url.toString();
}
export function parseBasicClientCredentials(authorization?: string) {
if (!authorization?.startsWith('Basic ')) return null;
try {
const decoded = Buffer.from(authorization.slice(6), 'base64').toString('utf8');
const separator = decoded.indexOf(':');
if (separator < 0) return null;
return {
clientId: decoded.slice(0, separator),
clientSecret: decoded.slice(separator + 1)
};
} catch {
return null;
}
}
export function mapTokenResponseToStandard(result: {
accessToken?: string;
tokenType?: string;
expiresIn?: number;
refreshToken?: string;
idToken?: string;
}) {
return {
access_token: result.accessToken,
token_type: result.tokenType ?? 'Bearer',
expires_in: result.expiresIn ?? 900,
refresh_token: result.refreshToken,
id_token: result.idToken
};
}
export function mergeTokenCredentials(
body: NormalizedTokenBody,
authorization?: string
): NormalizedTokenBody {
const basic = parseBasicClientCredentials(authorization);
if (!basic) return body;
return {
...body,
clientId: body.clientId || basic.clientId,
clientSecret: body.clientSecret || basic.clientSecret
};
}