Files

78 lines
2.1 KiB
Go

package api
import (
"context"
"crypto/sha256"
"encoding/hex"
"net/http"
"strings"
"time"
"rawmark/internal/auth"
)
type principal struct {
Token string
SessionHash string
Scope string
ExpiresAt time.Time
}
type principalKey struct{}
func identity(r *http.Request) principal { return r.Context().Value(principalKey{}).(principal) }
func withIdentity(r *http.Request, p principal) *http.Request {
return r.WithContext(context.WithValue(r.Context(), principalKey{}, p))
}
func tokenHash(token string) string {
sum := sha256.Sum256([]byte(token))
return hex.EncodeToString(sum[:])
}
func (s *Server) requireAuth(next http.Handler, allowAPI bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
token, ok := strings.CutPrefix(header, "Bearer ")
if !ok || token == "" || strings.ContainsAny(token, " \t\r\n") {
writeError(w, 401, message(w, "please_sign_in_48144f"))
return
}
p := principal{Token: token}
var valid bool
var err error
if strings.HasPrefix(token, auth.SessionPrefix) {
p.ExpiresAt, valid, err = s.auth.SessionExpiry(token)
p.SessionHash = tokenHash(token)
} else {
p.Scope, valid, err = s.auth.ValidateAPIToken(token)
}
if err != nil {
internalError(w, r, err)
return
}
if !valid {
writeError(w, 401, message(w, "credentials_are_invalid_or_expired_e1f63b"))
return
}
if p.SessionHash == "" {
if !allowAPI {
writeError(w, 403, message(w, "this_endpoint_requires_a_login_d34b96"))
return
}
if p.Scope != "write" && r.Method != http.MethodGet && r.Method != http.MethodHead && !(r.Method == http.MethodPost && r.URL.Path == "/api/v1/export") {
writeError(w, 403, message(w, "read_only_tokens_cannot_modify_6cf8e2"))
return
}
}
next.ServeHTTP(w, withIdentity(r, p))
})
}
func (s *Server) validPrincipal(p principal) (bool, error) {
if p.SessionHash != "" {
_, ok, err := s.store.SessionExpiry(p.SessionHash, time.Now())
return ok, err
}
_, ok, err := s.auth.ValidateAPIToken(p.Token)
return ok, err
}