Files
terdut-server/internal/api/middleware.go
T
Niklas Ye 7c3c28b23c Stage 2: users, API key auth, bootstrap endpoint
- Migration 002: users and api_keys tables (Unix timestamps, FK cascade)
- POST /api/bootstrap — creates first user + key when DB is empty
- POST/GET/DELETE /api/users — user CRUD
- POST/DELETE /api/users/{id}/api-keys — key issuance and revocation
- AuthMiddleware: SHA-256 bearer token lookup, last_used_at tracking
- Raw key returned once on creation; only SHA-256 hash stored
2026-05-20 21:51:09 +02:00

64 lines
1.7 KiB
Go

package api
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/hex"
"net/http"
"strings"
"time"
"github.com/yeniklas/terdut-server/internal/models"
)
type contextKey string
const ctxUser contextKey = "user"
func AuthMiddleware(db *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, ok := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
if !ok || token == "" {
respond(w, http.StatusUnauthorized, errResp("unauthorized"))
return
}
h := sha256.Sum256([]byte(token))
hash := hex.EncodeToString(h[:])
var keyID, userID int64
err := db.QueryRowContext(r.Context(),
"SELECT id, user_id FROM api_keys WHERE key_hash = ?", hash,
).Scan(&keyID, &userID)
if err != nil {
respond(w, http.StatusUnauthorized, errResp("unauthorized"))
return
}
// best-effort; don't fail the request if this update fails
db.ExecContext(r.Context(),
"UPDATE api_keys SET last_used_at = ? WHERE id = ?",
time.Now().Unix(), keyID)
var u models.User
var createdUnix int64
if err := db.QueryRowContext(r.Context(),
"SELECT id, username, email, created_at FROM users WHERE id = ?", userID,
).Scan(&u.ID, &u.Username, &u.Email, &createdUnix); err != nil {
respond(w, http.StatusUnauthorized, errResp("unauthorized"))
return
}
u.CreatedAt = time.Unix(createdUnix, 0).UTC()
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxUser, u)))
})
}
}
func userFromContext(ctx context.Context) (models.User, bool) {
u, ok := ctx.Value(ctxUser).(models.User)
return u, ok
}