78 lines
2.1 KiB
Go
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
|
|
}
|