fix and update

This commit is contained in:
lendry
2026-07-01 16:26:43 +03:00
parent f7c01a3963
commit ee8aaf9889
9 changed files with 50 additions and 8 deletions

View File

@@ -40,6 +40,7 @@ type FedcmAccountsResponse = {
given_name?: string; given_name?: string;
email?: string; email?: string;
picture?: string; picture?: string;
tel?: string;
approved_clients?: string[]; approved_clients?: string[];
}>; }>;
}; };

View File

@@ -105,15 +105,20 @@ export async function resolveFedcmEndpoints(core: CoreGrpcService, _req?: Reques
export function buildFedcmProviderConfig(endpoints: FedcmEndpoints) { export function buildFedcmProviderConfig(endpoints: FedcmEndpoints) {
const base = normalizePublicBaseUrl(endpoints.issuer); const base = normalizePublicBaseUrl(endpoints.issuer);
const front = normalizePublicBaseUrl(endpoints.frontendUrl);
return { return {
accounts_endpoint: `${base}/fedcm/accounts`, accounts_endpoint: `${base}/fedcm/accounts`,
client_metadata_endpoint: `${base}/fedcm/client_metadata`, client_metadata_endpoint: `${base}/fedcm/client_metadata`,
id_assertion_endpoint: `${base}/fedcm/id_assertion`, id_assertion_endpoint: `${base}/fedcm/id_assertion`,
login_url: endpoints.loginUrl, login_url: endpoints.loginUrl,
branding: { branding: {
name: endpoints.projectName,
background_color: '#ffffff', background_color: '#ffffff',
color: '#1f2430', color: '#1f2430',
icons: [{ url: `${base}/favicon.ico`, size: 32 }] icons: [
{ url: `${front}/icon.svg`, size: 40 },
{ url: `${base}/favicon.ico`, size: 32 }
]
} }
}; };
} }
@@ -135,7 +140,9 @@ export function buildFedcmDiscoverPayload(endpoints: FedcmEndpoints, enabled = t
configUrl: endpoints.configUrl, configUrl: endpoints.configUrl,
webIdentityUrl: endpoints.webIdentityUrl, webIdentityUrl: endpoints.webIdentityUrl,
accountsEndpoint: endpoints.accountsEndpoint, accountsEndpoint: endpoints.accountsEndpoint,
loginUrl: endpoints.loginUrl loginUrl: endpoints.loginUrl,
// Поля для navigator.credentials.get({ identity: { fields: [...] } }) на стороне RP (Chrome 132+).
suggestedFields: ['name', 'email', 'picture', 'tel']
}; };
} }

View File

@@ -71,7 +71,8 @@ async function loginWithFedCM() {
identity: { identity: {
providers: [{ providers: [{
configURL: '${fedcmConfigUrl}', configURL: '${fedcmConfigUrl}',
clientId: '${clientIdPlaceholder}' clientId: '${clientIdPlaceholder}',
fields: ['name', 'email', 'picture', 'tel']
}] }]
}, },
mediation: 'optional' mediation: 'optional'

View File

@@ -104,6 +104,10 @@ export function OAuthClientOneTapSection({
<code>configURL</code> для FedCM: <code>{endpoints.fedcmConfigUrl}</code> (не{' '} <code>configURL</code> для FedCM: <code>{endpoints.fedcmConfigUrl}</code> (не{' '}
<code>{frontendBase}/idp-api/fedcm/config.json</code>). <code>{frontendBase}/idp-api/fedcm/config.json</code>).
</li> </li>
<li>
Для disclosure (имя, email, аватар, телефон) RP должен передавать{' '}
<code>{`fields: ['name', 'email', 'picture', 'tel']`}</code> в вызове FedCM (Chrome 132+).
</li>
<li> <li>
При ошибке NetworkError проверьте, что <code>PUBLIC_API_URL</code> = <code>{endpoints.issuer}</code>. При ошибке NetworkError проверьте, что <code>PUBLIC_API_URL</code> = <code>{endpoints.issuer}</code>.
</li> </li>

View File

@@ -95,7 +95,9 @@ export function buildOneTapExamples(urls: OneTapUrls, clientId: string, redirect
identity: { identity: {
providers: [{ providers: [{
configURL: '${fedcmConfigUrl}', configURL: '${fedcmConfigUrl}',
clientId: '${clientId}' clientId: '${clientId}',
// Chrome 132+: disclosure в диалоге FedCM (имя, email, аватар, телефон)
fields: ['name', 'email', 'picture', 'tel']
}] }]
}, },
mediation: 'optional' mediation: 'optional'

View File

@@ -95,7 +95,8 @@
{ {
configURL: configUrl, configURL: configUrl,
configUrl: configUrl, configUrl: configUrl,
clientId: clientId clientId: clientId,
fields: ['name', 'email', 'picture', 'tel']
} }
] ]
}, },

View File

@@ -1,5 +1,6 @@
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common'; import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service'; import { PrismaService } from '../infra/prisma.service';
import { MediaService } from './media.service';
import { OAuthCoreService } from './oauth-core.service'; import { OAuthCoreService } from './oauth-core.service';
import { SessionService } from './session.service'; import { SessionService } from './session.service';
import { SettingsService } from './settings.service'; import { SettingsService } from './settings.service';
@@ -11,7 +12,8 @@ export class FedcmService {
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly session: SessionService, private readonly session: SessionService,
private readonly oauthCore: OAuthCoreService, private readonly oauthCore: OAuthCoreService,
private readonly settings: SettingsService private readonly settings: SettingsService,
private readonly media: MediaService
) {} ) {}
async getAccounts(userId: string, sessionId: string) { async getAccounts(userId: string, sessionId: string) {
@@ -35,6 +37,9 @@ export class FedcmService {
.map((item) => item.client.clientId); .map((item) => item.client.clientId);
const givenName = user.displayName.trim().split(/\s+/)[0] || user.displayName; const givenName = user.displayName.trim().split(/\s+/)[0] || user.displayName;
const picture = await this.media.createFedcmAvatarUrl(user.id);
const email = user.email?.trim() || undefined;
const tel = user.phone?.trim() || undefined;
return { return {
accounts: [ accounts: [
@@ -42,8 +47,9 @@ export class FedcmService {
id: user.id, id: user.id,
name: user.displayName, name: user.displayName,
given_name: givenName, given_name: givenName,
email: user.email ?? undefined, email,
picture: user.avatarUrl ?? undefined, picture,
tel,
approved_clients: approvedClients approved_clients: approvedClients
} }
] ]

View File

@@ -68,6 +68,25 @@ export class MediaService {
return { userId, hasAvatar: true }; return { userId, hasAvatar: true };
} }
async createFedcmAvatarUrl(userId: string): Promise<string | undefined> {
const user = await this.prisma.user.findUnique({ where: { id: userId } });
if (!user) {
return undefined;
}
const legacyUrl = user.avatarUrl?.trim();
if (legacyUrl) {
return legacyUrl;
}
if (!user.avatarStorageKey) {
return undefined;
}
const { accessUrl } = await this.createStreamAccessUrl(user.avatarStorageKey, userId);
return accessUrl;
}
async getAvatarAccessUrl(requesterId: string, targetUserId: string) { async getAvatarAccessUrl(requesterId: string, targetUserId: string) {
const user = await this.prisma.user.findUnique({ where: { id: targetUserId } }); const user = await this.prisma.user.findUnique({ where: { id: targetUserId } });
if (!user?.avatarStorageKey) { if (!user?.avatarStorageKey) {

View File

@@ -125,6 +125,7 @@ message FedcmAccount {
optional string email = 4; optional string email = 4;
optional string picture = 5; optional string picture = 5;
repeated string approved_clients = 6; repeated string approved_clients = 6;
optional string tel = 7;
} }
message FedcmAccountsResponse { message FedcmAccountsResponse {