fix: portable error reporting for PAM (#11296)

Linux PAM reports "Authentication Failure"
OpenPAM reports "authentication error"

This resulted in forgejo reporting error 500 on FreeBSD when pam
authentication failed.

Add a sentinel error to make this portable: ErrInvalidCredentials

Signed-off-by: Baptiste Daroussin <bapt@FreeBSD.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11296
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Baptiste Daroussin <bapt@FreeBSD.org>
Co-committed-by: Baptiste Daroussin <bapt@FreeBSD.org>
This commit is contained in:
Baptiste Daroussin 2026-02-16 05:57:01 +01:00 committed by Gusted
parent 9767cebc42
commit 9762f9ea20
4 changed files with 18 additions and 2 deletions

View file

@ -7,10 +7,15 @@ package pam
import (
"errors"
"fmt"
"github.com/msteinert/pam/v2"
)
// ErrInvalidCredentials is returned when PAM reports an authentication
// or account error (wrong password, unknown user, expired account, etc.).
var ErrInvalidCredentials = errors.New("invalid PAM credentials")
// Supported is true when built with PAM
var Supported = true
@ -31,10 +36,16 @@ func Auth(serviceName, userName, passwd string) (string, error) {
defer t.End()
if err = t.Authenticate(0); err != nil {
if errors.Is(err, pam.ErrAuth) || errors.Is(err, pam.ErrUserUnknown) {
return "", fmt.Errorf("%w: %v", ErrInvalidCredentials, err)
}
return "", err
}
if err = t.AcctMgmt(0); err != nil {
if errors.Is(err, pam.ErrAcctExpired) || errors.Is(err, pam.ErrPermDenied) {
return "", fmt.Errorf("%w: %v", ErrInvalidCredentials, err)
}
return "", err
}

View file

@ -9,6 +9,10 @@ import (
"errors"
)
// ErrInvalidCredentials is returned when PAM reports an authentication
// or account error (wrong password, unknown user, expired account, etc.).
var ErrInvalidCredentials = errors.New("invalid PAM credentials")
// Supported is false when built without PAM
var Supported = false

View file

@ -15,6 +15,6 @@ import (
func TestPamAuth(t *testing.T) {
result, err := Auth("gitea", "user1", "false-pwd")
require.Error(t, err)
assert.EqualError(t, err, "Authentication failure")
assert.ErrorIs(t, err, ErrInvalidCredentials)
assert.Len(t, result, 0)
}

View file

@ -5,6 +5,7 @@ package pam
import (
"context"
"errors"
"fmt"
"strings"
@ -23,7 +24,7 @@ import (
func (source *Source) Authenticate(ctx context.Context, user *user_model.User, userName, password string) (*user_model.User, error) {
pamLogin, err := pam.Auth(source.ServiceName, userName, password)
if err != nil {
if strings.Contains(err.Error(), "Authentication failure") {
if errors.Is(err, pam.ErrInvalidCredentials) {
return nil, user_model.ErrUserNotExist{Name: userName}
}
return nil, err