diff --git a/apps/api-gateway/src/controllers/fedcm.controller.ts b/apps/api-gateway/src/controllers/fedcm.controller.ts
index 504aad9..139d055 100644
--- a/apps/api-gateway/src/controllers/fedcm.controller.ts
+++ b/apps/api-gateway/src/controllers/fedcm.controller.ts
@@ -40,6 +40,7 @@ type FedcmAccountsResponse = {
given_name?: string;
email?: string;
picture?: string;
+ tel?: string;
approved_clients?: string[];
}>;
};
diff --git a/apps/api-gateway/src/lib/fedcm-config.ts b/apps/api-gateway/src/lib/fedcm-config.ts
index 225fb16..0761f58 100644
--- a/apps/api-gateway/src/lib/fedcm-config.ts
+++ b/apps/api-gateway/src/lib/fedcm-config.ts
@@ -105,15 +105,20 @@ export async function resolveFedcmEndpoints(core: CoreGrpcService, _req?: Reques
export function buildFedcmProviderConfig(endpoints: FedcmEndpoints) {
const base = normalizePublicBaseUrl(endpoints.issuer);
+ const front = normalizePublicBaseUrl(endpoints.frontendUrl);
return {
accounts_endpoint: `${base}/fedcm/accounts`,
client_metadata_endpoint: `${base}/fedcm/client_metadata`,
id_assertion_endpoint: `${base}/fedcm/id_assertion`,
login_url: endpoints.loginUrl,
branding: {
+ name: endpoints.projectName,
background_color: '#ffffff',
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,
webIdentityUrl: endpoints.webIdentityUrl,
accountsEndpoint: endpoints.accountsEndpoint,
- loginUrl: endpoints.loginUrl
+ loginUrl: endpoints.loginUrl,
+ // Поля для navigator.credentials.get({ identity: { fields: [...] } }) на стороне RP (Chrome 132+).
+ suggestedFields: ['name', 'email', 'picture', 'tel']
};
}
diff --git a/apps/docs/lib/one-tap-examples.ts b/apps/docs/lib/one-tap-examples.ts
index 1a4812f..302f615 100644
--- a/apps/docs/lib/one-tap-examples.ts
+++ b/apps/docs/lib/one-tap-examples.ts
@@ -71,7 +71,8 @@ async function loginWithFedCM() {
identity: {
providers: [{
configURL: '${fedcmConfigUrl}',
- clientId: '${clientIdPlaceholder}'
+ clientId: '${clientIdPlaceholder}',
+ fields: ['name', 'email', 'picture', 'tel']
}]
},
mediation: 'optional'
diff --git a/apps/frontend/components/id/oauth-client-one-tap-section.tsx b/apps/frontend/components/id/oauth-client-one-tap-section.tsx
index 4c93861..8eef2c8 100644
--- a/apps/frontend/components/id/oauth-client-one-tap-section.tsx
+++ b/apps/frontend/components/id/oauth-client-one-tap-section.tsx
@@ -104,6 +104,10 @@ export function OAuthClientOneTapSection({
configURL для FedCM: {endpoints.fedcmConfigUrl} (не{' '}
{frontendBase}/idp-api/fedcm/config.json).
+
+ Для disclosure (имя, email, аватар, телефон) RP должен передавать{' '}
+ {`fields: ['name', 'email', 'picture', 'tel']`} в вызове FedCM (Chrome 132+).
+
При ошибке NetworkError проверьте, что PUBLIC_API_URL = {endpoints.issuer}.
diff --git a/apps/frontend/lib/one-tap-examples.ts b/apps/frontend/lib/one-tap-examples.ts
index d4d7c92..c732c1e 100644
--- a/apps/frontend/lib/one-tap-examples.ts
+++ b/apps/frontend/lib/one-tap-examples.ts
@@ -95,7 +95,9 @@ export function buildOneTapExamples(urls: OneTapUrls, clientId: string, redirect
identity: {
providers: [{
configURL: '${fedcmConfigUrl}',
- clientId: '${clientId}'
+ clientId: '${clientId}',
+ // Chrome 132+: disclosure в диалоге FedCM (имя, email, аватар, телефон)
+ fields: ['name', 'email', 'picture', 'tel']
}]
},
mediation: 'optional'
diff --git a/apps/frontend/public/sso-widget.js b/apps/frontend/public/sso-widget.js
index 855c97d..faeaf54 100644
--- a/apps/frontend/public/sso-widget.js
+++ b/apps/frontend/public/sso-widget.js
@@ -95,7 +95,8 @@
{
configURL: configUrl,
configUrl: configUrl,
- clientId: clientId
+ clientId: clientId,
+ fields: ['name', 'email', 'picture', 'tel']
}
]
},
diff --git a/apps/sso-core/src/domain/fedcm.service.ts b/apps/sso-core/src/domain/fedcm.service.ts
index ab5a1e6..363d3f1 100644
--- a/apps/sso-core/src/domain/fedcm.service.ts
+++ b/apps/sso-core/src/domain/fedcm.service.ts
@@ -1,5 +1,6 @@
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service';
+import { MediaService } from './media.service';
import { OAuthCoreService } from './oauth-core.service';
import { SessionService } from './session.service';
import { SettingsService } from './settings.service';
@@ -11,7 +12,8 @@ export class FedcmService {
private readonly prisma: PrismaService,
private readonly session: SessionService,
private readonly oauthCore: OAuthCoreService,
- private readonly settings: SettingsService
+ private readonly settings: SettingsService,
+ private readonly media: MediaService
) {}
async getAccounts(userId: string, sessionId: string) {
@@ -35,6 +37,9 @@ export class FedcmService {
.map((item) => item.client.clientId);
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 {
accounts: [
@@ -42,8 +47,9 @@ export class FedcmService {
id: user.id,
name: user.displayName,
given_name: givenName,
- email: user.email ?? undefined,
- picture: user.avatarUrl ?? undefined,
+ email,
+ picture,
+ tel,
approved_clients: approvedClients
}
]
diff --git a/apps/sso-core/src/domain/media.service.ts b/apps/sso-core/src/domain/media.service.ts
index 826fea6..6f91708 100644
--- a/apps/sso-core/src/domain/media.service.ts
+++ b/apps/sso-core/src/domain/media.service.ts
@@ -68,6 +68,25 @@ export class MediaService {
return { userId, hasAvatar: true };
}
+ async createFedcmAvatarUrl(userId: string): Promise {
+ 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) {
const user = await this.prisma.user.findUnique({ where: { id: targetUserId } });
if (!user?.avatarStorageKey) {
diff --git a/shared/proto/identity.proto b/shared/proto/identity.proto
index af21d59..e71c6a3 100644
--- a/shared/proto/identity.proto
+++ b/shared/proto/identity.proto
@@ -125,6 +125,7 @@ message FedcmAccount {
optional string email = 4;
optional string picture = 5;
repeated string approved_clients = 6;
+ optional string tel = 7;
}
message FedcmAccountsResponse {