- Login endpoint (password → bearer token + session cookie) - Auth middleware (bearer header or session cookie) - Notebook list endpoint (authenticated) - Page SVG/JPG rendering endpoints (authenticated) - Notebook PDF download endpoint (authenticated) - Share link endpoints: view, page SVG, page JPG, PDF (no auth) - Route registration with chi groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
55 lines
1.2 KiB
Go
55 lines
1.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.wntrmute.dev/kyle/eng-pad-server/internal/auth"
|
|
)
|
|
|
|
type contextKey string
|
|
|
|
const userIDKey contextKey = "user_id"
|
|
|
|
func UserIDFromContext(ctx context.Context) (int64, bool) {
|
|
id, ok := ctx.Value(userIDKey).(int64)
|
|
return id, ok
|
|
}
|
|
|
|
func AuthMiddleware(database *sql.DB) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
token := ""
|
|
|
|
// Check Authorization header
|
|
authHeader := r.Header.Get("Authorization")
|
|
if strings.HasPrefix(authHeader, "Bearer ") {
|
|
token = strings.TrimPrefix(authHeader, "Bearer ")
|
|
}
|
|
|
|
// Check cookie
|
|
if token == "" {
|
|
if cookie, err := r.Cookie("session"); err == nil {
|
|
token = cookie.Value
|
|
}
|
|
}
|
|
|
|
if token == "" {
|
|
http.Error(w, `{"error":"unauthenticated"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
userID, err := auth.ValidateToken(database, token)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
ctx := context.WithValue(r.Context(), userIDKey, userID)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|