Skip to content

Commit 939cc68

Browse files
authored
fix(profile): unblock linkedin add identity flow (#694)
Two issues prevented "Add Identity → LinkedIn" from working on /profile/identities: - ProfileAuthService and SocialVerificationService read env vars (PROFILE_CLIENT_ID, PROFILE_CLIENT_SECRET, etc.) into readonly instance fields in their constructors. Both are instantiated by ProfileController during ESM import resolution, which runs before dotenv.config() in server.ts, so the fields were captured as empty strings. isProfileAuthConfigured() returned false and the Flow C redirect silently bounced back to /profile/identities. Convert both services to the lazy-getter pattern already used by CdpService/CredlyService. - The identities GET fetch ran during SSR, but the server-side HTTP call doesn't carry the user's session cookie reliably and tended to fail. The SSR HTML rendered the "Identity service unavailable" banner, causing a flash on hydration. Gate the fetch with isPlatformBrowser so SSR renders a loading state and the browser owns the request. LFXV2-1762 Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
1 parent d19f160 commit 939cc68

3 files changed

Lines changed: 68 additions & 27 deletions

File tree

apps/lfx-one/src/app/modules/profile/identities/profile-identities.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ export class ProfileIdentitiesComponent implements OnInit {
216216
}
217217

218218
private initIdentitiesState(): Signal<IdentitiesState> {
219+
// Skip the fetch during SSR. The server's HTTP call doesn't carry the user's
220+
// session cookie reliably, so it tends to fail and renders the load-error
221+
// banner into the SSR HTML — producing a red-banner flash on hydration
222+
// before the browser's authenticated fetch resolves.
223+
if (!isPlatformBrowser(this.platformId)) {
224+
return signal({ identities: [] as EnrichedIdentity[], loaded: false });
225+
}
219226
return toSignal(
220227
this.refreshTrigger$.pipe(
221228
startWith(undefined),

apps/lfx-one/src/server/services/profile-auth.service.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,42 @@ interface TokenResponse {
2222
* update:current_user_metadata and update:current_user_identities scopes.
2323
*/
2424
export class ProfileAuthService {
25-
private readonly clientId: string;
26-
private readonly clientSecret: string;
27-
private readonly audience: string;
28-
private readonly scope: string;
29-
private readonly issuerBaseUrl: string;
30-
private readonly baseUrl: string;
31-
private readonly redirectUri: string;
32-
33-
public constructor() {
34-
this.clientId = process.env['PROFILE_CLIENT_ID'] || '';
35-
this.clientSecret = process.env['PROFILE_CLIENT_SECRET'] || '';
36-
this.audience = process.env['PROFILE_AUDIENCE'] || '';
37-
this.scope = process.env['PROFILE_SCOPE'] || '';
38-
this.issuerBaseUrl = (process.env['PCC_AUTH0_ISSUER_BASE_URL'] || '').replace(/\/+$/, '');
39-
this.baseUrl = process.env['PCC_BASE_URL'] || 'http://localhost:4000';
40-
this.redirectUri = process.env['PROFILE_REDIRECT_URI'] || `${this.baseUrl}/passwordless/callback`;
25+
// Resolved lazily on first access so dotenv has finished loading,
26+
// then memoized — env is stable after startup.
27+
private _clientId: string | undefined;
28+
private _clientSecret: string | undefined;
29+
private _audience: string | undefined;
30+
private _scope: string | undefined;
31+
private _issuerBaseUrl: string | undefined;
32+
private _baseUrl: string | undefined;
33+
private _redirectUri: string | undefined;
34+
35+
private get clientId(): string {
36+
return (this._clientId ??= process.env['PROFILE_CLIENT_ID'] || '');
37+
}
38+
39+
private get clientSecret(): string {
40+
return (this._clientSecret ??= process.env['PROFILE_CLIENT_SECRET'] || '');
41+
}
42+
43+
private get audience(): string {
44+
return (this._audience ??= process.env['PROFILE_AUDIENCE'] || '');
45+
}
46+
47+
private get scope(): string {
48+
return (this._scope ??= process.env['PROFILE_SCOPE'] || '');
49+
}
50+
51+
private get issuerBaseUrl(): string {
52+
return (this._issuerBaseUrl ??= (process.env['PCC_AUTH0_ISSUER_BASE_URL'] || '').replace(/\/+$/, ''));
53+
}
54+
55+
private get baseUrl(): string {
56+
return (this._baseUrl ??= process.env['PCC_BASE_URL'] || 'http://localhost:4000');
57+
}
58+
59+
private get redirectUri(): string {
60+
return (this._redirectUri ??= process.env['PROFILE_REDIRECT_URI'] || `${this.baseUrl}/passwordless/callback`);
4161
}
4262

4363
/**

apps/lfx-one/src/server/services/social-verification.service.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,34 @@ interface SocialTokenResponse {
3030
* - The `github`, `google-oauth2`, and `linkedin` social connections must be enabled on the Auth0 tenant
3131
*/
3232
export class SocialVerificationService {
33-
private readonly clientId: string;
34-
private readonly clientSecret: string;
35-
private readonly issuerBaseUrl: string;
36-
private readonly baseUrl: string;
37-
private readonly redirectUri: string;
33+
// Resolved lazily on first access so dotenv has finished loading,
34+
// then memoized — env is stable after startup.
35+
private _clientId: string | undefined;
36+
private _clientSecret: string | undefined;
37+
private _issuerBaseUrl: string | undefined;
38+
private _baseUrl: string | undefined;
39+
private _redirectUri: string | undefined;
3840

3941
private static readonly validProviders: readonly SocialProvider[] = ['github', 'google', 'linkedin'];
4042

41-
public constructor() {
42-
this.clientId = process.env['PROFILE_CLIENT_ID'] || '';
43-
this.clientSecret = process.env['PROFILE_CLIENT_SECRET'] || '';
44-
this.issuerBaseUrl = (process.env['PCC_AUTH0_ISSUER_BASE_URL'] || '').replace(/\/+$/, '');
45-
this.baseUrl = process.env['PCC_BASE_URL'] || 'http://localhost:4000';
46-
this.redirectUri = process.env['SOCIAL_REDIRECT_URI'] || `${this.baseUrl}/social/callback`;
43+
private get clientId(): string {
44+
return (this._clientId ??= process.env['PROFILE_CLIENT_ID'] || '');
45+
}
46+
47+
private get clientSecret(): string {
48+
return (this._clientSecret ??= process.env['PROFILE_CLIENT_SECRET'] || '');
49+
}
50+
51+
private get issuerBaseUrl(): string {
52+
return (this._issuerBaseUrl ??= (process.env['PCC_AUTH0_ISSUER_BASE_URL'] || '').replace(/\/+$/, ''));
53+
}
54+
55+
private get baseUrl(): string {
56+
return (this._baseUrl ??= process.env['PCC_BASE_URL'] || 'http://localhost:4000');
57+
}
58+
59+
private get redirectUri(): string {
60+
return (this._redirectUri ??= process.env['SOCIAL_REDIRECT_URI'] || `${this.baseUrl}/social/callback`);
4761
}
4862

4963
/**

0 commit comments

Comments
 (0)