@@ -224,19 +224,24 @@ func (h *OIDCHandler) HandleCallback(w http.ResponseWriter, r *http.Request) {
224224 username = sub
225225 }
226226
227+ // Resolve roles before creating user to avoid orphaned records on rejection
228+ resolvedRoles := h .resolveClaimRoles (claims )
229+ if len (resolvedRoles ) == 0 && h .config .RejectUnmapped {
230+ h .log .Warn ("OIDC: login rejected — no mapped roles for user %s" , username )
231+ http .Redirect (w , r , "/login?error=no_mapped_roles" , http .StatusFound )
232+ return
233+ }
234+
227235 user , err := h .findOrCreateOIDCUser (ctx , sub , username , email )
228236 if err != nil {
229237 h .log .Error ("OIDC: failed to find or create user (sub=%s, username=%s): %v" , sub , username , err )
230238 http .Error (w , "Failed to authenticate user" , http .StatusInternalServerError )
231239 return
232240 }
233241
234- // Map OIDC claims to roles
235- mapped := h .mapClaimsToRoles (ctx , user .ID , claims )
236- if ! mapped && h .config .RejectUnmapped {
237- h .log .Warn ("OIDC: login rejected — no mapped roles for user %s" , username )
238- http .Redirect (w , r , "/login?error=no_mapped_roles" , http .StatusFound )
239- return
242+ // Assign resolved roles to user
243+ for _ , roleName := range resolvedRoles {
244+ _ = h .store .AssignRole (ctx , user .ID , roleName , "oidc" )
240245 }
241246
242247 // Get user roles
@@ -321,18 +326,18 @@ func (h *OIDCHandler) findOrCreateOIDCUser(ctx context.Context, sub, username, e
321326 return user , nil
322327}
323328
324- // Maps OIDC claim values to local roles
325- func (h * OIDCHandler ) mapClaimsToRoles ( ctx context. Context , userID string , claims map [string ]any ) bool {
329+ // Resolve OIDC claim values to local roles
330+ func (h * OIDCHandler ) resolveClaimRoles ( claims map [string ]any ) [] string {
326331 if h .config .RoleClaim == "" {
327- return true
332+ return nil
328333 }
329334
330335 // Extract groups/roles from claims
331336 var claimValues []string
332337 claimValue , ok := claims [h .config .RoleClaim ]
333338 if ! ok {
334339 h .log .Warn ("OIDC: role claim %q not found in token claims" , h .config .RoleClaim )
335- return false
340+ return nil
336341 }
337342 switch v := claimValue .(type ) {
338343 case []any :
@@ -342,7 +347,6 @@ func (h *OIDCHandler) mapClaimsToRoles(ctx context.Context, userID string, claim
342347 }
343348 }
344349 case string :
345- // Try JSON array
346350 var arr []string
347351 if err := json .Unmarshal ([]byte (v ), & arr ); err == nil {
348352 claimValues = arr
@@ -351,11 +355,11 @@ func (h *OIDCHandler) mapClaimsToRoles(ctx context.Context, userID string, claim
351355 }
352356 }
353357
354- // Resolve claim values to local role names without writing anything yet
358+ // Resolve claim values to local role names
355359 var resolvedRoles []string
356360 if len (h .config .RoleMapping ) > 0 {
357361 for _ , claimVal := range claimValues {
358- for mapKey , localRole := range h .config .RoleMapping { // yaml map keys apparently lower case themselves?
362+ for mapKey , localRole := range h .config .RoleMapping {
359363 if strings .EqualFold (claimVal , mapKey ) {
360364 resolvedRoles = append (resolvedRoles , localRole )
361365 break
@@ -367,11 +371,7 @@ func (h *OIDCHandler) mapClaimsToRoles(ctx context.Context, userID string, claim
367371 resolvedRoles = claimValues
368372 }
369373
370- // Assign only after we know what (if anything) resolved
371- for _ , roleName := range resolvedRoles {
372- _ = h .store .AssignRole (ctx , userID , roleName , "oidc" )
373- }
374- return len (resolvedRoles ) > 0
374+ return resolvedRoles
375375}
376376
377377// Calls the configured extra claims URL with the access token.
0 commit comments