Skip to content

Commit 5261dce

Browse files
committed
fix(oauth): add Microsoft Graph API field mapping support
Support Microsoft-specific user info fields: - email: check 'mail' and 'userPrincipalName' as fallbacks - name: check 'displayName' (camelCase) for Microsoft Graph API
1 parent bd46d6b commit 5261dce

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

backend/internal/infrastructure/auth/oauth_provider.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,21 @@ func (p *OAuthProvider) parseUserInfo(resp *http.Response) (*models.User, error)
344344
return nil, fmt.Errorf("missing user ID in response")
345345
}
346346

347-
if email, ok := rawUser["email"].(string); ok {
347+
// Check for email in various provider-specific fields
348+
// - "email": Standard OIDC claim (Google, GitHub, GitLab, etc.)
349+
// - "mail": Microsoft Graph API
350+
// - "userPrincipalName": Microsoft fallback (UPN format)
351+
if email, ok := rawUser["email"].(string); ok && email != "" {
348352
user.Email = email
353+
} else if mail, ok := rawUser["mail"].(string); ok && mail != "" {
354+
user.Email = mail
355+
} else if upn, ok := rawUser["userPrincipalName"].(string); ok && upn != "" {
356+
user.Email = upn
349357
} else {
350-
return nil, fmt.Errorf("missing email in user info response")
358+
return nil, fmt.Errorf("missing email in user info response (checked: email, mail, userPrincipalName)")
351359
}
352360

361+
// Extract display name from various provider-specific fields
353362
var name string
354363
if fullName, ok := rawUser["name"].(string); ok && fullName != "" {
355364
name = fullName
@@ -359,10 +368,12 @@ func (p *OAuthProvider) parseUserInfo(resp *http.Response) (*models.User, error)
359368
} else {
360369
name = firstName
361370
}
371+
} else if displayName, ok := rawUser["displayName"].(string); ok && displayName != "" {
372+
name = displayName
362373
} else if cn, ok := rawUser["cn"].(string); ok && cn != "" {
363374
name = cn
364-
} else if displayName, ok := rawUser["display_name"].(string); ok && displayName != "" {
365-
name = displayName
375+
} else if displayNameSnake, ok := rawUser["display_name"].(string); ok && displayNameSnake != "" {
376+
name = displayNameSnake
366377
} else if preferredName, ok := rawUser["preferred_username"].(string); ok && preferredName != "" {
367378
name = preferredName
368379
}

backend/internal/infrastructure/auth/oauth_provider_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,55 @@ func TestOAuthProvider_parseUserInfo(t *testing.T) {
272272
}
273273
},
274274
},
275+
{
276+
name: "Microsoft Graph API - mail field",
277+
responseObj: map[string]interface{}{
278+
"id": "microsoft-id-12345",
279+
"mail": "[email protected]",
280+
"displayName": "Microsoft User",
281+
"userPrincipalName": "[email protected]",
282+
},
283+
wantErr: false,
284+
checkUser: func(t *testing.T, user *models.User) {
285+
if user.Sub != "microsoft-id-12345" {
286+
t.Errorf("Sub = %v, expected microsoft-id-12345", user.Sub)
287+
}
288+
if user.Email != "[email protected]" {
289+
t.Errorf("Email = %v, expected [email protected] (from mail field)", user.Email)
290+
}
291+
if user.Name != "Microsoft User" {
292+
t.Errorf("Name = %v, expected Microsoft User (from displayName)", user.Name)
293+
}
294+
},
295+
},
296+
{
297+
name: "Microsoft Graph API - userPrincipalName fallback",
298+
responseObj: map[string]interface{}{
299+
"id": "microsoft-id-67890",
300+
"displayName": "UPN User",
301+
"userPrincipalName": "[email protected]",
302+
},
303+
wantErr: false,
304+
checkUser: func(t *testing.T, user *models.User) {
305+
if user.Email != "[email protected]" {
306+
t.Errorf("Email = %v, expected [email protected] (from userPrincipalName)", user.Email)
307+
}
308+
},
309+
},
310+
{
311+
name: "email field takes priority over mail",
312+
responseObj: map[string]interface{}{
313+
"sub": "12345",
314+
"email": "[email protected]",
315+
"mail": "[email protected]",
316+
},
317+
wantErr: false,
318+
checkUser: func(t *testing.T, user *models.User) {
319+
if user.Email != "[email protected]" {
320+
t.Errorf("Email = %v, expected [email protected] (email should take priority)", user.Email)
321+
}
322+
},
323+
},
275324
{
276325
name: "missing email",
277326
responseObj: map[string]interface{}{

0 commit comments

Comments
 (0)