7c3c28b23c
- 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
64 lines
1.7 KiB
Go
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
|
|
}
|