Skip to content

Commit 6011e9a

Browse files
Merge pull request #120 from linuxfoundation/feat/lfxv2-643-get-user-profile-from-auth-service
feat(auth-service): user profile from auth-service
2 parents 526162b + e84b172 commit 6011e9a

File tree

4 files changed

+118
-4
lines changed

4 files changed

+118
-4
lines changed

apps/lfx-one/src/server/controllers/profile.controller.ts

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
// Copyright The Linux Foundation and each contributor to LFX.
22
// SPDX-License-Identifier: MIT
33

4-
import { AddEmailRequest, CombinedProfile, ProfileUpdateRequest, UpdateEmailPreferencesRequest, UserMetadataUpdateRequest } from '@lfx-one/shared/interfaces';
4+
import {
5+
AddEmailRequest,
6+
CombinedProfile,
7+
ProfileUpdateRequest,
8+
UpdateEmailPreferencesRequest,
9+
UserMetadata,
10+
UserMetadataUpdateRequest,
11+
} from '@lfx-one/shared/interfaces';
512
import { NextFunction, Request, Response } from 'express';
613

714
import { AuthenticationError, AuthorizationError, MicroserviceError, ResourceNotFoundError, ServiceValidationError } from '../errors';
@@ -41,7 +48,27 @@ export class ProfileController {
4148

4249
let combinedProfile: CombinedProfile | null = null;
4350

44-
// Get combined profile using JOIN
51+
// Step 1: Try to get user metadata from NATS first (authoritative source)
52+
let natsUserData: UserMetadata | null = null;
53+
try {
54+
const natsResponse = await this.userService.getUserInfo(req, userId);
55+
req.log.info({ userId, natsSuccess: natsResponse.success }, 'Fetched user data from NATS');
56+
57+
if (natsResponse.success && natsResponse.data) {
58+
natsUserData = natsResponse.data;
59+
}
60+
} catch (error) {
61+
// Log but don't fail - we'll fall back to Supabase
62+
req.log.warn(
63+
{
64+
userId,
65+
error: error instanceof Error ? error.message : error,
66+
},
67+
'Failed to fetch user data from NATS, will use Supabase as fallback'
68+
);
69+
}
70+
71+
// Step 2: Get user from Supabase
4572
const user = await this.supabaseService.getUser(userId);
4673

4774
if (!user) {
@@ -54,12 +81,20 @@ export class ProfileController {
5481
return next(validationError);
5582
}
5683

84+
// Step 3: Merge NATS data into Supabase user if available
85+
if (natsUserData) {
86+
// Merge fields from NATS, preferring NATS data when available
87+
if (natsUserData.given_name) user.first_name = natsUserData.given_name;
88+
if (natsUserData.family_name) user.last_name = natsUserData.family_name;
89+
// Note: email and username are not part of UserMetadata, they come from the user table
90+
}
91+
5792
combinedProfile = {
5893
user,
5994
profile: null,
6095
};
6196

62-
// Get profile details
97+
// Step 4: Get profile details from Supabase
6398
const profile = await this.supabaseService.getProfile(user.id);
6499

65100
// If no profile details exist, create them
@@ -72,9 +107,38 @@ export class ProfileController {
72107
combinedProfile.profile = profile;
73108
}
74109

110+
// Step 5: Merge NATS metadata into profile if available
111+
if (natsUserData && combinedProfile.profile) {
112+
// Merge all available fields from NATS into the profile
113+
const fieldsToMerge: (keyof UserMetadata)[] = [
114+
'name',
115+
'given_name',
116+
'family_name',
117+
'picture',
118+
'phone_number',
119+
'address',
120+
'city',
121+
'state_province',
122+
'postal_code',
123+
'country',
124+
'organization',
125+
'job_title',
126+
't_shirt_size',
127+
'zoneinfo',
128+
];
129+
130+
const natsData = natsUserData;
131+
fieldsToMerge.forEach((field) => {
132+
if (natsData[field] !== undefined && natsData[field] !== null) {
133+
(combinedProfile.profile as UserMetadata)[field] = natsData[field];
134+
}
135+
});
136+
}
137+
75138
Logger.success(req, 'get_current_user_profile', startTime, {
76139
user_id: user.id,
77140
has_profile_details: !!combinedProfile.profile,
141+
used_nats_data: !!natsUserData,
78142
});
79143

80144
res.json(combinedProfile);

apps/lfx-one/src/server/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ const authConfig: ConfigParams = {
160160
authorizationParams: {
161161
response_type: 'code',
162162
audience: process.env['PCC_AUTH0_AUDIENCE'] || 'https://example.com',
163-
scope: 'openid email profile access:api offline_access update:current_user_metadata',
163+
scope: 'openid email profile access:api offline_access',
164164
},
165165
clientSecret: process.env['PCC_AUTH0_CLIENT_SECRET'] || 'bar',
166166
routes: {

apps/lfx-one/src/server/services/user.service.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { UserMetadata, UserMetadataUpdateRequest, UserMetadataUpdateResponse } f
77
import { Request } from 'express';
88

99
import { serverLogger } from '../server';
10+
import { ResourceNotFoundError } from '../errors';
1011
import { NatsService } from './nats.service';
1112

1213
/**
@@ -19,6 +20,54 @@ export class UserService {
1920
this.natsService = new NatsService();
2021
}
2122

23+
/**
24+
* Fetch user information by username or email using NATS request-reply pattern
25+
* The userArg is either a username or a sub (subject) or a user's token
26+
* @param req - Express request object for logging
27+
* @param userArg - Username, sub, or token
28+
* @returns UserMetadataUpdateResponse object with success, data, and error
29+
* @throws ResourceNotFoundError if user not found
30+
*/
31+
public async getUserInfo(req: Request, userArg: string): Promise<UserMetadataUpdateResponse> {
32+
const codec = this.natsService.getCodec();
33+
34+
try {
35+
req.log.info({ userArgProvided: !!userArg }, 'Fetching user metadata via NATS');
36+
37+
const response = await this.natsService.request(NatsSubjects.USER_METADATA_READ, codec.encode(userArg), { timeout: NATS_CONFIG.REQUEST_TIMEOUT });
38+
39+
const responseText = codec.decode(response.data);
40+
41+
const userMetadata: UserMetadataUpdateResponse = JSON.parse(responseText);
42+
43+
if (!userMetadata || typeof userMetadata !== 'object') {
44+
throw new ResourceNotFoundError('User', undefined, {
45+
operation: 'get_user_info',
46+
service: 'user_service',
47+
path: '/nats/user-metadata-read',
48+
});
49+
}
50+
51+
return userMetadata;
52+
} catch (error) {
53+
if (error instanceof ResourceNotFoundError) {
54+
throw error;
55+
}
56+
57+
req.log.error({ error: error instanceof Error ? error.message : error, userArgProvided: !!userArg }, 'Failed to fetch user metadata via NATS');
58+
59+
if (error instanceof Error && (error.message.includes('timeout') || error.message.includes('503'))) {
60+
throw new ResourceNotFoundError('User', undefined, {
61+
operation: 'get_user_info',
62+
service: 'user_service',
63+
path: '/nats/user-metadata-read',
64+
});
65+
}
66+
67+
throw error;
68+
}
69+
}
70+
2271
/**
2372
* Update user metadata through NATS
2473
* @param req - Express request object

packages/shared/src/interfaces/user-profile.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,6 @@ export interface UserMetadataUpdateResponse {
160160
username: string;
161161
message?: string;
162162
updated_fields?: string[];
163+
data?: UserMetadata;
163164
error?: string;
164165
}

0 commit comments

Comments
 (0)