9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
Post-repair hardware validation pipeline for Proxmox cluster hosts. Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
101 lines
2.2 KiB
Go
101 lines
2.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const cookieName = "vetting_session"
|
|
|
|
type Manager struct {
|
|
PasswordHash string
|
|
Secret []byte
|
|
TTL time.Duration
|
|
}
|
|
|
|
func (m *Manager) VerifyPassword(password string) bool {
|
|
if m.PasswordHash == "" {
|
|
return false
|
|
}
|
|
return bcrypt.CompareHashAndPassword([]byte(m.PasswordHash), []byte(password)) == nil
|
|
}
|
|
|
|
// Issue writes a signed session cookie valid for m.TTL.
|
|
func (m *Manager) Issue(w http.ResponseWriter, r *http.Request) {
|
|
expiry := time.Now().Add(m.TTL).Unix()
|
|
payload := strconv.FormatInt(expiry, 10)
|
|
sig := m.sign(payload)
|
|
value := payload + "." + sig
|
|
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: cookieName,
|
|
Value: value,
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
Secure: r.TLS != nil,
|
|
SameSite: http.SameSiteLaxMode,
|
|
Expires: time.Unix(expiry, 0),
|
|
})
|
|
}
|
|
|
|
func (m *Manager) Clear(w http.ResponseWriter) {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: cookieName,
|
|
Value: "",
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
MaxAge: -1,
|
|
})
|
|
}
|
|
|
|
var errInvalidSession = errors.New("invalid session")
|
|
|
|
// Validate returns nil if the request's cookie is present, signed, and not expired.
|
|
func (m *Manager) Validate(r *http.Request) error {
|
|
c, err := r.Cookie(cookieName)
|
|
if err != nil {
|
|
return errInvalidSession
|
|
}
|
|
parts := strings.SplitN(c.Value, ".", 2)
|
|
if len(parts) != 2 {
|
|
return errInvalidSession
|
|
}
|
|
payload, sig := parts[0], parts[1]
|
|
expected := m.sign(payload)
|
|
if !hmac.Equal([]byte(sig), []byte(expected)) {
|
|
return errInvalidSession
|
|
}
|
|
expiry, err := strconv.ParseInt(payload, 10, 64)
|
|
if err != nil {
|
|
return errInvalidSession
|
|
}
|
|
if time.Now().Unix() >= expiry {
|
|
return errInvalidSession
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) sign(payload string) string {
|
|
mac := hmac.New(sha256.New, m.Secret)
|
|
_, _ = mac.Write([]byte(payload))
|
|
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
|
}
|
|
|
|
// BcryptHash is a helper used by the gen-admin-password tool.
|
|
func BcryptHash(password string) (string, error) {
|
|
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", fmt.Errorf("bcrypt: %w", err)
|
|
}
|
|
return string(b), nil
|
|
}
|