global update and global fix
This commit is contained in:
@@ -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']
|
||||
};
|
||||
}
|
||||
|
||||
147
apps/api-gateway/src/lib/oauth-params.ts
Normal file
147
apps/api-gateway/src/lib/oauth-params.ts
Normal 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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user