docent/server/auth.go
Pen Anderson 9884ccd9ed
Some checks are pending
check / frontend (push) Waiting to run
check / server (push) Waiting to run
Prototype commit
2026-05-05 09:29:40 -05:00

97 lines
2.1 KiB
Go

package main
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"net/http"
"strings"
"time"
)
const (
cookieName = "docent_session"
cookieMaxAge = 30 * 24 * time.Hour
)
type session struct {
Exp int64 `json:"exp"`
}
func (s *server) signCookieValue() (string, error) {
payload := session{Exp: time.Now().Add(cookieMaxAge).Unix()}
data, err := json.Marshal(payload)
if err != nil {
return "", err
}
enc := base64.RawURLEncoding.EncodeToString(data)
mac := hmac.New(sha256.New, s.secret)
mac.Write([]byte(enc))
sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
return enc + "." + sig, nil
}
func (s *server) verifyCookieValue(value string) bool {
parts := strings.SplitN(value, ".", 2)
if len(parts) != 2 {
return false
}
mac := hmac.New(sha256.New, s.secret)
mac.Write([]byte(parts[0]))
expected := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(parts[1])) {
return false
}
data, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return false
}
var sess session
if err := json.Unmarshal(data, &sess); err != nil {
return false
}
return time.Now().Unix() < sess.Exp
}
func (s *server) setSessionCookie(w http.ResponseWriter) {
value, err := s.signCookieValue()
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: value,
Path: "/",
MaxAge: int(cookieMaxAge.Seconds()),
HttpOnly: true,
Secure: s.secure,
SameSite: http.SameSiteLaxMode,
})
}
func (s *server) clearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
Secure: s.secure,
SameSite: http.SameSiteLaxMode,
})
}
func (s *server) isAuthed(r *http.Request) bool {
c, err := r.Cookie(cookieName)
if err != nil {
return false
}
return s.verifyCookieValue(c.Value)
}
func constantTimeEqualString(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}