fix and update

This commit is contained in:
lendry
2026-06-29 14:40:35 +03:00
parent 75ccbe5fc4
commit 0df7240dc8
21 changed files with 934 additions and 113 deletions

View File

@@ -1,8 +1,10 @@
import {
BadRequestException,
Controller,
Get,
Headers,
HttpCode,
NotFoundException,
Options,
Post,
Query,
@@ -15,9 +17,20 @@ import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { firstValueFrom } from 'rxjs';
import type { Request, Response } from 'express';
import { CoreGrpcService } from '../core-grpc.service';
import { applyFedcmCorsHeaders, applyFedcmPreflightHeaders } from '../lib/fedcm-cors';
import {
applyFedcmCorsHeaders,
applyFedcmLoginStatus,
applyFedcmPreflightHeaders,
assertFedcmWebIdentityRequest,
requireFedcmCorsOrigin
} from '../lib/fedcm-cors';
import {
buildFedcmDiscoverPayload,
buildFedcmProviderConfig,
readOneTapEnabled,
resolveFedcmEndpoints
} from '../lib/fedcm-config';
import { resolveFedcmSessionFromRequest, setFedcmSessionCookie } from '../lib/fedcm-cookie';
import { resolveFrontendUrl, resolveOAuthIssuer } from '../lib/oauth-issuer';
import { assertSessionUnlocked, verifyAccessToken } from '../session-auth';
type FedcmAccountsResponse = {
@@ -39,6 +52,12 @@ export class FedcmController {
private readonly jwt: JwtService
) {}
@Options('config.json')
@HttpCode(204)
configPreflight(@Headers('origin') origin: string | undefined, @Res({ passthrough: true }) res: Response) {
applyFedcmPreflightHeaders(res, origin);
}
@Options('accounts')
@HttpCode(204)
accountsPreflight(@Headers('origin') origin: string | undefined, @Res({ passthrough: true }) res: Response) {
@@ -59,40 +78,33 @@ export class FedcmController {
@Get('config.json')
@ApiOperation({ summary: 'FedCM provider config', description: 'Конфигурация Identity Provider для Federated Credential Management API.' })
async config(@Res({ passthrough: true }) res: Response) {
res.setHeader('Content-Type', 'application/json');
async config(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
assertFedcmWebIdentityRequest(req);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'public, max-age=300');
const issuer = await resolveOAuthIssuer(this.core);
const frontendUrl = await resolveFrontendUrl(this.core);
let projectName = 'MVK ID';
try {
const setting = (await firstValueFrom(this.core.settings.GetSetting({ key: 'PROJECT_NAME' }))) as { value?: string };
if (setting.value?.trim()) {
projectName = setting.value.trim();
}
} catch {
// fallback
const enabled = await readOneTapEnabled(this.core);
if (!enabled) {
throw new NotFoundException('One Tap Login отключён');
}
return {
accounts_endpoint: `${issuer}/fedcm/accounts`,
client_metadata_endpoint: `${issuer}/fedcm/client_metadata`,
id_assertion_endpoint: `${issuer}/fedcm/id_assertion`,
login_url: `${frontendUrl}/auth/login`,
signup_url: `${frontendUrl}/auth/register`,
branding: {
background_color: '#ffffff',
color: '#3390ec',
icons: [{ url: `${frontendUrl}/favicon.ico`, size: 32 }]
},
accounts: {
include_anonymous: false
},
fields: ['name', 'email', 'picture'],
display: projectName
};
const endpoints = await resolveFedcmEndpoints(this.core, req);
return buildFedcmProviderConfig(endpoints);
}
@Get('discover.json')
@ApiOperation({
summary: 'FedCM discovery',
description: 'Публичные URL FedCM для виджета и диагностики (apiBase, configUrl, webIdentityUrl).'
})
async discover(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'public, max-age=60');
res.setHeader('Access-Control-Allow-Origin', '*');
const enabled = await readOneTapEnabled(this.core);
const endpoints = await resolveFedcmEndpoints(this.core, req);
return buildFedcmDiscoverPayload(endpoints, enabled);
}
@Get('accounts')
@@ -102,33 +114,44 @@ export class FedcmController {
@Headers('origin') origin: string | undefined,
@Res({ passthrough: true }) res: Response
): Promise<FedcmAccountsResponse> {
assertFedcmWebIdentityRequest(req);
applyFedcmCorsHeaders(res, origin);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Type', 'application/json; charset=utf-8');
const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
if (!session) {
applyFedcmLoginStatus(res, false);
return { accounts: [] };
}
const result = (await firstValueFrom(
this.core.fedcm.GetAccounts({ userId: session.sub, sessionId: session.sessionId })
)) as FedcmAccountsResponse;
return result ?? { accounts: [] };
try {
const result = (await firstValueFrom(
this.core.fedcm.GetAccounts({ userId: session.sub, sessionId: session.sessionId })
)) as FedcmAccountsResponse;
const accounts = result?.accounts ?? [];
applyFedcmLoginStatus(res, accounts.length > 0);
return { accounts };
} catch {
applyFedcmLoginStatus(res, false);
return { accounts: [] };
}
}
@Get('client_metadata')
@ApiOperation({ summary: 'FedCM client metadata', description: 'Метаданные клиента для UI FedCM.' })
async clientMetadata(
@Req() req: Request,
@Query('client_id') clientId: string | undefined,
@Headers('origin') origin: string | undefined,
@Res({ passthrough: true }) res: Response
) {
applyFedcmCorsHeaders(res, origin);
res.setHeader('Content-Type', 'application/json');
assertFedcmWebIdentityRequest(req);
const rpOrigin = requireFedcmCorsOrigin(origin);
applyFedcmCorsHeaders(res, rpOrigin);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (!clientId?.trim()) {
throw new UnauthorizedException('Передайте client_id');
throw new BadRequestException('Передайте client_id');
}
return firstValueFrom(this.core.fedcm.GetClientMetadata({ clientId: clientId.trim() }));
@@ -142,8 +165,10 @@ export class FedcmController {
@Headers('origin') origin: string | undefined,
@Res({ passthrough: true }) res: Response
) {
applyFedcmCorsHeaders(res, origin);
res.setHeader('Content-Type', 'application/json');
assertFedcmWebIdentityRequest(req);
const rpOrigin = requireFedcmCorsOrigin(origin);
applyFedcmCorsHeaders(res, rpOrigin);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
if (!session) {
@@ -154,9 +179,11 @@ export class FedcmController {
const clientId = String(body.client_id ?? body.clientId ?? '').trim();
const accountId = String(body.account_id ?? body.accountId ?? '').trim();
if (!clientId || !accountId) {
throw new UnauthorizedException('Передайте client_id и account_id');
throw new BadRequestException('Передайте client_id и account_id');
}
applyFedcmLoginStatus(res, true);
return firstValueFrom(
this.core.fedcm.IssueIdAssertion({
userId: session.sub,
@@ -187,6 +214,7 @@ export class FedcmController {
sessionId: payload.sessionId,
pinVerified: true
});
applyFedcmLoginStatus(res, true);
return { synced: true };
}
}

View File

@@ -1,8 +1,9 @@
import { Controller, Get, Res } from '@nestjs/common';
import { Controller, Get, Req, Res } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import type { Response } from 'express';
import { firstValueFrom } from 'rxjs';
import type { Request, Response } from 'express';
import { CoreGrpcService } from '../core-grpc.service';
import { buildFedcmWebIdentityManifest, resolveFedcmEndpoints } from '../lib/fedcm-config';
import { assertFedcmWebIdentityRequest } from '../lib/fedcm-cors';
import { buildOpenIdConfiguration, resolveOAuthIssuer } from '../lib/oauth-issuer';
@ApiTags('OpenID Connect')
@@ -15,13 +16,12 @@ export class WellKnownController {
summary: 'FedCM web identity manifest',
description: 'Манифест Federated Credential Management API, указывающий на конфигурацию провайдера.'
})
async webIdentity(@Res({ passthrough: true }) res: Response) {
res.setHeader('Content-Type', 'application/json');
async webIdentity(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
assertFedcmWebIdentityRequest(req);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'public, max-age=300');
const issuer = await resolveOAuthIssuer(this.core);
return {
provider_urls: [`${issuer}/fedcm/config.json`]
};
const endpoints = await resolveFedcmEndpoints(this.core, req);
return buildFedcmWebIdentityManifest(endpoints);
}
@Get('openid-configuration')