{"record":{"id":"1449b8d756e59daf","repo":"vxcontrol/pentagi","slug":"auth-invaliduserdata","errorCode":"Auth.InvalidUserData","errorMessage":"user is external","messagePattern":"user is external","errorType":"http","errorClass":null,"httpStatus":500,"severity":"warning","filePath":"backend/pkg/server/services/auth.go","lineNumber":128,"sourceCode":"\t\t\terr = data.Valid()\n\t\t}\n\t\tlogger.FromContext(c).WithError(err).Errorf(\"error validating request data\")\n\t\tresponse.Error(c, response.ErrAuthInvalidLoginRequest, err)\n\t\treturn\n\t}\n\n\tvar user models.UserPassword\n\tif err := s.db.Take(&user, \"mail = ? AND password IS NOT NULL\", data.Mail).Error; err != nil {\n\t\tlogrus.WithError(err).Errorf(\"error getting user by mail '%s'\", data.Mail)\n\t\tresponse.Error(c, response.ErrAuthInvalidCredentials, err)\n\t\treturn\n\t} else if err = user.Valid(); err != nil {\n\t\tlogger.FromContext(c).WithError(err).Errorf(\"error validating user data '%s'\", user.Hash)\n\t\tresponse.Error(c, response.ErrAuthInvalidUserData, err)\n\t\treturn\n\t} else if user.RoleID == 100 {\n\t\tlogger.FromContext(c).WithError(err).Errorf(\"can't authorize external user '%s'\", user.Hash)\n\t\tresponse.Error(c, response.ErrAuthInvalidUserData, fmt.Errorf(\"user is external\"))\n\t\treturn\n\t}\n\n\tif err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(data.Password)); err != nil {\n\t\tlogger.FromContext(c).Errorf(\"error matching user input password\")\n\t\tresponse.Error(c, response.ErrAuthInvalidCredentials, err)\n\t\treturn\n\t}\n\n\tif user.Status != \"active\" {\n\t\tlogger.FromContext(c).Errorf(\"error checking active state for user '%s'\", user.Status)\n\t\tresponse.Error(c, response.ErrAuthInactiveUser, fmt.Errorf(\"user is inactive\"))\n\t\treturn\n\t}\n\n\tvar privs []string\n\terr := s.db.Table(\"privileges\").\n\t\tWhere(\"role_id = ?\", user.RoleID).","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/services/auth.go#L110-L146","documentation":"AuthLogin rejects authentication for users whose RoleID is 100, the designated 'external' role used for OAuth-provisioned accounts. External users must sign in via their OAuth2 provider (Google/GitHub); local password login is intentionally refused and mapped to the Auth.InvalidUserData error code.","triggerScenarios":"POST to the local login endpoint with credentials belonging to an account created through OAuth2 (role_id = 100), or after an admin manually assigned role 100 to an account.","commonSituations":"User originally signed up with Google/GitHub and later tries email+password login; admins importing users with role_id 100 by mistake; environment misconfiguration where the default external role ID differs.","solutions":["Sign in via the OAuth2 provider associated with the account (/auth/authorize?provider=google|github).","Have an admin change the user's role_id to a non-external role if local login is intended.","Verify the account's role assignment in the users table if you believe it's wrong.","Set a password for OAuth-created users only after changing their role away from 100."],"exampleFix":"-- allow local login for a user\nUPDATE users SET role_id = 1 WHERE id = 42;  -- 1 = non-external role, adjust to your scheme","handlingStrategy":"fallback","validationCode":"// before attempting local login, verify the account is not external\nvar user models.User\nif err := db.Where(\"email = ?\", email).First(&user).Error; err == nil {\n    if user.RoleID == 100 {\n        return fmt.Errorf(\"this account is OAuth-managed; sign in with %s\", oauthProviderFor(user))\n    }\n}","typeGuard":"func isExternalUser(u models.User) bool {\n    return u.RoleID == 100\n}","tryCatchPattern":"_, err := auth.Login(ctx, data)\nvar respErr *response.Error\nif errors.As(err, &respErr) && respErr.Code == response.ErrAuthInvalidUserData {\n    // redirect the user to the OAuth provider login instead\n    http.Redirect(w, r, \"/api/v1/auth/authorize?provider=google\", http.StatusFound)\n    return\n}\nif err != nil { return err }","preventionTips":["Detect OAuth-provisioned accounts by role_id 100 before offering a password form","Don't assign role 100 to accounts intended for password login","Show provider-specific login buttons for external accounts","Keep role assignments audited and documented"],"tags":["authentication","oauth","roles","login"],"backgroundTag":"external-user-local-login-blocked","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}