tokidoki/auth/pam.go
2024-04-19 17:37:55 +02:00

85 lines
2.1 KiB
Go

//go:build pam
package auth
import (
"fmt"
"net/http"
"github.com/msteinert/pam/v2"
"github.com/rs/zerolog/log"
)
type pamProvider struct{}
func NewPAM() (AuthProvider, error) {
return pamProvider{}, nil
}
func (pamProvider) Middleware() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pamAuth(next, w, r)
})
}
}
func pamAuth(next http.Handler, w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok {
w.Header().Add("WWW-Authenticate", `Basic realm="Please provide your system credentials", charset="UTF-8"`)
http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized)
return
}
t, err := pam.StartFunc("login", user, func(s pam.Style, msg string) (string, error) {
log.Debug().Str("style", fmt.Sprintf("%v", s)).Msg(msg)
switch s {
case pam.PromptEchoOff:
return pass, nil
case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo:
return "", nil
default:
return "", fmt.Errorf("unsupported PAM conversation style: %v", s)
}
})
if err != nil {
log.Warn().Err(err).Msg("failed to start PAM conversation")
http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable)
return
}
defer func() {
err := t.End()
if err != nil {
log.Warn().Err(err).Msg("failed to end PAM transaction")
}
}()
if err := t.Authenticate(0); err != nil {
log.Debug().Str("user", user).Err(err).Msg("auth error")
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
return
}
if err := t.AcctMgmt(0); err != nil {
log.Debug().Str("user", user).Err(err).Msg("account unavailable")
http.Error(w, "Account unavailable", http.StatusUnauthorized)
return
}
user, err = t.GetItem(pam.User)
if err != nil {
log.Warn().Str("user", user).Err(err).Msg("failed to get PAM username")
http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable)
return
}
authCtx := AuthContext{
AuthMethod: "pam",
UserName: user,
}
ctx := NewContext(r.Context(), &authCtx)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
}