vxcontrol/pentagi · warning
Auth.InvalidUserData
Auth.InvalidUserData
Error message
user is external
What it means
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.
Source
Thrown at backend/pkg/server/services/auth.go:128
err = data.Valid()
}
logger.FromContext(c).WithError(err).Errorf("error validating request data")
response.Error(c, response.ErrAuthInvalidLoginRequest, err)
return
}
var user models.UserPassword
if err := s.db.Take(&user, "mail = ? AND password IS NOT NULL", data.Mail).Error; err != nil {
logrus.WithError(err).Errorf("error getting user by mail '%s'", data.Mail)
response.Error(c, response.ErrAuthInvalidCredentials, err)
return
} else if err = user.Valid(); err != nil {
logger.FromContext(c).WithError(err).Errorf("error validating user data '%s'", user.Hash)
response.Error(c, response.ErrAuthInvalidUserData, err)
return
} else if user.RoleID == 100 {
logger.FromContext(c).WithError(err).Errorf("can't authorize external user '%s'", user.Hash)
response.Error(c, response.ErrAuthInvalidUserData, fmt.Errorf("user is external"))
return
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(data.Password)); err != nil {
logger.FromContext(c).Errorf("error matching user input password")
response.Error(c, response.ErrAuthInvalidCredentials, err)
return
}
if user.Status != "active" {
logger.FromContext(c).Errorf("error checking active state for user '%s'", user.Status)
response.Error(c, response.ErrAuthInactiveUser, fmt.Errorf("user is inactive"))
return
}
var privs []string
err := s.db.Table("privileges").
Where("role_id = ?", user.RoleID).View on GitHub (pinned to ea665308ba)
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.
Example fix
-- allow local login for a user UPDATE users SET role_id = 1 WHERE id = 42; -- 1 = non-external role, adjust to your scheme
Defensive patterns
Strategy: fallback
Validate before calling
// before attempting local login, verify the account is not external
var user models.User
if err := db.Where("email = ?", email).First(&user).Error; err == nil {
if user.RoleID == 100 {
return fmt.Errorf("this account is OAuth-managed; sign in with %s", oauthProviderFor(user))
}
} Type guard
func isExternalUser(u models.User) bool {
return u.RoleID == 100
} Try / catch
_, err := auth.Login(ctx, data)
var respErr *response.Error
if errors.As(err, &respErr) && respErr.Code == response.ErrAuthInvalidUserData {
// redirect the user to the OAuth provider login instead
http.Redirect(w, r, "/api/v1/auth/authorize?provider=google", http.StatusFound)
return
}
if err != nil { return err } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/1449b8d756e59daf.
Report an issue: GitHub.