80 lines
1.9 KiB
Go
80 lines
1.9 KiB
Go
|
//go:build pam
|
||
|
|
||
|
package auth
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/msteinert/pam"
|
||
|
|
||
|
"git.sr.ht/~sircmpwn/tokidoki/debug"
|
||
|
)
|
||
|
|
||
|
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) {
|
||
|
debug.Printf("%v %v", s, 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 {
|
||
|
debug.Printf("Failed to start PAM conversation: %v", err)
|
||
|
http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := t.Authenticate(0); err != nil {
|
||
|
debug.Printf("Auth error: %v", err)
|
||
|
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := t.AcctMgmt(0); err != nil {
|
||
|
debug.Printf("Account unavailable: %v", err)
|
||
|
http.Error(w, "Account unavailable", http.StatusUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
user, err = t.GetItem(pam.User)
|
||
|
if err != nil {
|
||
|
debug.Printf("Failed to get PAM username: %v", err)
|
||
|
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)
|
||
|
}
|