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

  1. Sign in via the OAuth2 provider associated with the account (/auth/authorize?provider=google|github).
  2. Have an admin change the user's role_id to a non-external role if local login is intended.
  3. Verify the account's role assignment in the users table if you believe it's wrong.
  4. 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

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.