feat: deepseek is insanly good
This commit is contained in:
1 parent
be9d6b610f
commit
56da3bae61
5 files changed
+112
-51
No files matched your search
@@ -2,14 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -74,6 +77,29 @@ func hashToken(token string) string {
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
func loadHMACKey(path string, logger *zap.Logger) ([]byte, error) {
|
||||
if data, err := os.ReadFile(path); err == nil {
|
||||
key, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(data)))
|
||||
if err == nil && len(key) >= 32 {
|
||||
return key, nil
|
||||
}
|
||||
logger.Warn("invalid HMAC key file, regenerating", zap.Error(err))
|
||||
}
|
||||
|
||||
key := make([]byte, 32)
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
return nil, fmt.Errorf("generate HMAC key: %w", err)
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(key)
|
||||
if err := os.WriteFile(path, []byte(encoded+"\n"), 0600); err != nil {
|
||||
return nil, fmt.Errorf("write HMAC key: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("generated new HMAC key", zap.String("path", path))
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (s *sqliteStore) countUsers(ctx context.Context) (int, error) {
|
||||
var n int
|
||||
err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&n)
|
||||
@@ -276,44 +302,86 @@ func getUserFromContext(r *http.Request) *userRow {
|
||||
return nil
|
||||
}
|
||||
|
||||
func authMiddleware(store *sqliteStore, logger *zap.Logger, next http.Handler) http.HandlerFunc {
|
||||
func signCookie(key []byte, userID int64, username string) string {
|
||||
expiry := time.Now().Add(30 * 24 * time.Hour).Unix()
|
||||
payload := fmt.Sprintf("%d|%s|%d", userID, username, expiry)
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(payload))
|
||||
sig := hex.EncodeToString(mac.Sum(nil))
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(payload + "|" + sig))
|
||||
}
|
||||
|
||||
func verifyCookie(key []byte, encoded string) (int64, string, bool) {
|
||||
raw, err := base64.RawURLEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return 0, "", false
|
||||
}
|
||||
parts := strings.SplitN(string(raw), "|", 4)
|
||||
if len(parts) != 4 {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
userID, username, expiryStr, sig := parts[0], parts[1], parts[2], parts[3]
|
||||
payload := userID + "|" + username + "|" + expiryStr
|
||||
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(payload))
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
if !hmac.Equal([]byte(sig), []byte(expected)) {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
expiryUnix, err := strconv.ParseInt(expiryStr, 10, 64)
|
||||
if err != nil || time.Now().Unix() > expiryUnix {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(userID, 10, 64)
|
||||
if err != nil {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
return id, username, true
|
||||
}
|
||||
|
||||
func authMiddleware(store *sqliteStore, hmacKey []byte, logger *zap.Logger, next http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodOptions {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
token := ""
|
||||
var user *userRow
|
||||
|
||||
cookie, err := r.Cookie("st_pierre_session")
|
||||
if err == nil && cookie.Value != "" {
|
||||
token = cookie.Value
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
token = strings.TrimPrefix(authHeader, "Bearer ")
|
||||
if id, username, ok := verifyCookie(hmacKey, cookie.Value); ok {
|
||||
u, err := store.getUserByID(r.Context(), id)
|
||||
if err == nil && u != nil && u.Active && u.Username == username {
|
||||
user = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
writeUnauthorized(w)
|
||||
return
|
||||
if user == nil {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
session, u, err := store.getSessionByToken(r.Context(), token)
|
||||
if err != nil {
|
||||
logger.Error("token lookup failed", zap.Error(err))
|
||||
}
|
||||
if session != nil && session.Active && u != nil && u.Active {
|
||||
store.updateSessionLastUsed(r.Context(), session.ID)
|
||||
user = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session, user, err := store.getSessionByToken(r.Context(), token)
|
||||
if err != nil {
|
||||
logger.Error("auth lookup failed", zap.Error(err))
|
||||
if user == nil {
|
||||
writeUnauthorized(w)
|
||||
return
|
||||
}
|
||||
if session == nil || !session.Active || !user.Active {
|
||||
writeUnauthorized(w)
|
||||
return
|
||||
}
|
||||
|
||||
store.updateSessionLastUsed(r.Context(), session.ID)
|
||||
|
||||
ctx := context.WithValue(r.Context(), userContextKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
@@ -376,7 +444,7 @@ func getenvDefault(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
func loginHandler(store *sqliteStore, logger *zap.Logger) http.HandlerFunc {
|
||||
func loginHandler(store *sqliteStore, hmacKey []byte, logger *zap.Logger) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
|
||||
@@ -405,31 +473,18 @@ func loginHandler(store *sqliteStore, logger *zap.Logger) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := generateToken()
|
||||
if err != nil {
|
||||
logger.Error("generate token failed", zap.Error(err))
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
|
||||
return
|
||||
}
|
||||
|
||||
session, err := store.createSession(r.Context(), user.ID, token, "UI login")
|
||||
if err != nil {
|
||||
logger.Error("create session failed", zap.Error(err))
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
|
||||
return
|
||||
}
|
||||
signed := signCookie(hmacKey, user.ID, user.Username)
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "st_pierre_session",
|
||||
Value: token,
|
||||
Value: signed,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Now().Add(365 * 24 * time.Hour),
|
||||
Expires: time.Now().Add(30 * 24 * time.Hour),
|
||||
})
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"session": session,
|
||||
"user": map[string]any{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
@@ -438,21 +493,13 @@ func loginHandler(store *sqliteStore, logger *zap.Logger) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func logoutHandler(store *sqliteStore) http.HandlerFunc {
|
||||
func logoutHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := r.Cookie("st_pierre_session")
|
||||
if err == nil && cookie.Value != "" {
|
||||
session, _, _ := store.getSessionByToken(r.Context(), cookie.Value)
|
||||
if session != nil {
|
||||
store.deactivateSession(r.Context(), session.ID, session.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "st_pierre_session",
|
||||
Value: "",
|
||||
@@ -731,4 +778,3 @@ func getUserHandler(store *sqliteStore, logger *zap.Logger) http.HandlerFunc {
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user