{"record":{"id":"9688c63ae98fa05d","repo":"gotify/server","slug":"a-local-user-with-the-username-s-already-exists-a","errorCode":null,"errorMessage":"a local user with the username %s already exists and linking by username is disabled","messagePattern":"a local user with the username (.+?) already exists and linking by username is disabled","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"api/oidc.go","lineNumber":487,"sourceCode":"\tusername := fmt.Sprint(usernameRaw)\n\tif username == \"\" || usernameRaw == nil {\n\t\treturn nil, http.StatusInternalServerError, errors.New(\"username claim was empty\")\n\t}\n\n\tbyUsername, err := a.DB.GetUserByName(username)\n\tif err != nil {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"database error: %w\", err)\n\t}\n\tif byUsername != nil {\n\t\treturn a.linkExistingUser(byUsername, oidcID, hasAdminGroup)\n\t}\n\treturn a.registerUser(username, oidcID, hasAdminGroup)\n}\n\nfunc (a *OIDCAPI) linkExistingUser(user *model.User, oidcID string, hasAdminGroup bool) (*model.User, int, error) {\n\tif !a.LinkByUsername {\n\t\tlog.Warn().Str(\"oidc_id\", oidcID).Str(\"username\", user.Name).Msgf(\"OIDC login rejected: a local user with the username already exists and %s is disabled\", config.EnvOIDCLinkByUsername)\n\t\treturn nil, http.StatusForbidden, fmt.Errorf(\"a local user with the username %s already exists and linking by username is disabled\", user.Name)\n\t}\n\tif user.OIDCID != nil {\n\t\tlog.Warn().Str(\"oidc_id\", oidcID).Str(\"bound_oidc_id\", *user.OIDCID).Str(\"username\", user.Name).Msg(\"OIDC login rejected: the username is already bound to a different OIDC identity\")\n\t\treturn nil, http.StatusForbidden, fmt.Errorf(\"the user %s is already bound to a different OIDC identity\", user.Name)\n\t}\n\tuser.OIDCID = &oidcID\n\tif len(a.GroupsAdmin) > 0 {\n\t\tuser.Admin = hasAdminGroup\n\t}\n\tif err := a.DB.UpdateUser(user); err != nil {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"failed to bind user to OIDC identity: %w\", err)\n\t}\n\tlog.Warn().Str(\"oidc_id\", oidcID).Str(\"username\", user.Name).Bool(\"admin\", user.Admin).Msg(\"OIDC link by username\")\n\treturn user, 0, nil\n}\n\nfunc (a *OIDCAPI) registerUser(username, oidcID string, hasAdminGroup bool) (*model.User, int, error) {\n\tif !a.AutoRegister {","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/oidc.go#L469-L505","documentation":"linkExistingUser rejects the login with HTTP 403 when an existing local user has the same username but LinkByUsername is disabled. The library will not silently attach an OIDC identity to a pre-existing local account without explicit opt-in, preventing account takeover via a colliding username claim.","triggerScenarios":"First-time OIDC login where the username claim matches an existing local user's Name, GetUserByName returns that user, and a.LinkByUsername is false.","commonSituations":"Local users were created before OIDC was enabled with the same usernames as IdP accounts; OIDC_LINK_BY_USERNAME env left unset/false; IdP email/preferred_username collides with legacy local accounts.","solutions":["Enable linking by username (set the OIDC_LINK_BY_USERNAME=true env / LinkByUsername: true)","Rename either the local user or the IdP username so they no longer collide","Manually bind the OIDC ID to the local account via admin tooling/DB","Pre-seed users via SCIM or provisioning so identities bind cleanly"],"exampleFix":"// before\nLinkByUsername: false\n// after\nLinkByUsername: true // or env OIDC_LINK_BY_USERNAME=true","handlingStrategy":"validation","validationCode":"// check before enabling OIDC login for existing installs\nexisting, _ := db.GetUserByName(usernameFromToken)\nif existing != nil && !linkByUsername {\n    // decide: enable linking or rename the account\n}","typeGuard":null,"tryCatchPattern":"user, status, err := resolveUser(...)\nif status == http.StatusForbidden && strings.Contains(err.Error(), \"linking by username is disabled\") {\n    // admin action required: enable LinkByUsername or rename the colliding user\n    http.Error(w, \"account linking required\", http.StatusForbidden)\n    return\n}","preventionTips":["Enable OIDC_LINK_BY_USERNAME when migrating pre-existing local users","Audit local usernames against IdP usernames before rollout","Provision users via the IdP/SCIM to avoid drift","Log the collision (the library already warns) and review regularly"],"tags":["oidc","authorization","http-403","configuration"],"backgroundTag":"oidc-account-linking-disabled","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}