The codebase that powers boop.cat
boop.cat
1// Copyright 2025 boop.cat
2// Licensed under the Apache License, Version 2.0
3// See LICENSE file for details.
4
5package middleware
6
7import (
8 "context"
9 "database/sql"
10 "net/http"
11 "strings"
12
13 "boop-cat/db"
14)
15
16type ContextKey string
17
18const (
19 UserContextKey ContextKey = "user"
20
21 APIKeyIDContextKey ContextKey = "apiKeyId"
22)
23
24func RequireAPIKey(database *sql.DB) func(http.Handler) http.Handler {
25 return func(next http.Handler) http.Handler {
26 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 authHeader := r.Header.Get("Authorization")
28 if !strings.HasPrefix(authHeader, "Bearer ") {
29 http.Error(w, `{"error":"missing-api-key","message":"Authorization header with Bearer token required"}`, http.StatusUnauthorized)
30 return
31 }
32
33 key := strings.TrimPrefix(authHeader, "Bearer ")
34 if !strings.HasPrefix(key, "sk_") {
35 http.Error(w, `{"error":"invalid-api-key","message":"Invalid or expired API key"}`, http.StatusUnauthorized)
36 return
37 }
38
39 user, keyID, err := db.ValidateAPIKey(database, key)
40 if err != nil {
41 http.Error(w, `{"error":"invalid-api-key","message":"Invalid or expired API key"}`, http.StatusUnauthorized)
42 return
43 }
44
45 ctx := context.WithValue(r.Context(), UserContextKey, user)
46 ctx = context.WithValue(ctx, APIKeyIDContextKey, keyID)
47 next.ServeHTTP(w, r.WithContext(ctx))
48 })
49 }
50}
51
52func GetUser(ctx context.Context) *db.User {
53 if user, ok := ctx.Value(UserContextKey).(*db.User); ok {
54 return user
55 }
56 return nil
57}
58
59func GetAPIKeyID(ctx context.Context) string {
60 if keyID, ok := ctx.Value(APIKeyIDContextKey).(string); ok {
61 return keyID
62 }
63 return ""
64}
65
66func GetUserID(ctx context.Context) string {
67 user := GetUser(ctx)
68 if user != nil {
69 return user.ID
70 }
71 return ""
72}