移除 backend .env,暂时关闭 JWT,改为一次性登录

This commit is contained in:
fish
2026-05-03 20:41:43 +08:00
parent d0e5ddb678
commit fbcde3cc71
8 changed files with 21 additions and 74 deletions

View File

@@ -32,7 +32,7 @@ services:
context: ./web context: ./web
dockerfile: backend/Dockerfile dockerfile: backend/Dockerfile
container_name: trade-web container_name: trade-web
env_file: ./web/backend/.env # .env 已移除,环境变量直接写在此处
environment: environment:
- LISTEN_ADDR=:8080 - LISTEN_ADDR=:8080
- DATABASE_URL=postgres://trade:trade@postgres:5432/futures?sslmode=disable - DATABASE_URL=postgres://trade:trade@postgres:5432/futures?sslmode=disable

View File

@@ -1,8 +0,0 @@
# 拷贝为 web/backend/.env 后填入真实值。.env 已被 .gitignore 排除。
# 首次启动时,若 auth.db 中没有任何 admin 用户,会用下面这一对凭据创建管理员;
# 一旦 admin 已存在,这两个变量会被忽略,改它们不会改密码。
ADMIN_USER=admin
ADMIN_PASS=changeme
# JWT 签名密钥;生成方式:openssl rand -hex 32
JWT_SECRET=replace-with-32-bytes-hex

View File

@@ -3,14 +3,12 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
) )
type Config struct { type Config struct {
ListenAddr string ListenAddr string
DatabaseURL string DatabaseURL string
AuthDBPath string AuthDBPath string
JWTSecret []byte
TushareAPIURL string TushareAPIURL string
} }
@@ -24,11 +22,6 @@ func Load() (*Config, error) {
if cfg.DatabaseURL == "" { if cfg.DatabaseURL == "" {
return nil, fmt.Errorf("DATABASE_URL 环境变量未设置") return nil, fmt.Errorf("DATABASE_URL 环境变量未设置")
} }
secret := strings.TrimSpace(os.Getenv("JWT_SECRET"))
if len(secret) < 16 {
return nil, fmt.Errorf("JWT_SECRET 必须至少 16 个字符 (建议 openssl rand -hex 32)")
}
cfg.JWTSecret = []byte(secret)
return cfg, nil return cfg, nil
} }

View File

@@ -54,13 +54,9 @@ func (d *Deps) Login(w http.ResponseWriter, r *http.Request) {
writeErr(w, http.StatusUnauthorized, "用户名或密码错误") writeErr(w, http.StatusUnauthorized, "用户名或密码错误")
return return
} }
token, _, err := d.JWT.Issue(u.ID, u.Username, u.Role) // 暂时不用 JWT返回固定 token
if err != nil {
writeErr(w, http.StatusInternalServerError, "issue token failed")
return
}
writeJSON(w, http.StatusOK, loginResp{ writeJSON(w, http.StatusOK, loginResp{
Token: token, Token: "noop",
User: publicUserView{ User: publicUserView{
ID: u.ID, ID: u.ID,
Username: u.Username, Username: u.Username,

View File

@@ -5,7 +5,6 @@ import (
"log" "log"
"net/http" "net/http"
"trade/web/internal/auth"
"trade/web/internal/store" "trade/web/internal/store"
) )
@@ -13,7 +12,6 @@ import (
type Deps struct { type Deps struct {
Auth *store.AuthStore Auth *store.AuthStore
Futures *store.FuturesStore Futures *store.FuturesStore
JWT *auth.Manager
TushareURL string TushareURL string
} }

View File

@@ -3,9 +3,7 @@ package middleware
import ( import (
"context" "context"
"net/http" "net/http"
"strings"
"trade/web/internal/auth"
"trade/web/internal/store" "trade/web/internal/store"
) )
@@ -24,32 +22,14 @@ func FromContext(ctx context.Context) (CtxUser, bool) {
return u, ok return u, ok
} }
// RequireUser 校验 Authorization Bearer JWT,通过后把 CtxUser 写入 context // RequireUser 不再校验 JWT直接注入默认管理员用户所有请求放行
// 同时校验数据库里的 disabled 状态,被禁用的账户即使持有 token 也会被拒。 func RequireUser(next http.Handler) http.Handler {
func RequireUser(mgr *auth.Manager, s *store.AuthStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tok := bearer(r)
if tok == "" {
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "missing token"})
return
}
claims, err := mgr.Parse(tok)
if err != nil {
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid token"})
return
}
u, err := s.GetByID(claims.UserID)
if err != nil || u.Disabled {
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "account disabled or removed"})
return
}
ctx := context.WithValue(r.Context(), userKey, CtxUser{ ctx := context.WithValue(r.Context(), userKey, CtxUser{
ID: u.ID, Username: u.Username, Role: u.Role, ID: 1, Username: "admin", Role: store.RoleAdmin,
}) })
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
}
} }
func RequireAdmin(next http.Handler) http.Handler { func RequireAdmin(next http.Handler) http.Handler {
@@ -62,12 +42,3 @@ func RequireAdmin(next http.Handler) http.Handler {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
} }
func bearer(r *http.Request) string {
h := r.Header.Get("Authorization")
const p = "Bearer "
if strings.HasPrefix(h, p) {
return strings.TrimSpace(h[len(p):])
}
return ""
}

View File

@@ -7,13 +7,11 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"trade/web/internal/auth"
"trade/web/internal/handlers" "trade/web/internal/handlers"
mw "trade/web/internal/middleware" mw "trade/web/internal/middleware"
"trade/web/internal/store"
) )
func New(d *handlers.Deps, mgr *auth.Manager, authStore *store.AuthStore, dist fs.FS) http.Handler { func New(d *handlers.Deps, dist fs.FS) http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Use(mw.Recover) r.Use(mw.Recover)
r.Use(mw.Logger) r.Use(mw.Logger)
@@ -22,7 +20,7 @@ func New(d *handlers.Deps, mgr *auth.Manager, authStore *store.AuthStore, dist f
r.Post("/login", d.Login) r.Post("/login", d.Login)
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(mw.RequireUser(mgr, authStore)) r.Use(mw.RequireUser)
r.Post("/logout", d.Logout) r.Post("/logout", d.Logout)
r.Get("/me", d.Me) r.Get("/me", d.Me)

View File

@@ -40,8 +40,7 @@ func main() {
log.Fatalf("bootstrap: %v", err) log.Fatalf("bootstrap: %v", err)
} }
mgr := auth.NewManager(cfg.JWTSecret) deps := &handlers.Deps{Auth: authDB, Futures: futures, TushareURL: cfg.TushareAPIURL}
deps := &handlers.Deps{Auth: authDB, Futures: futures, JWT: mgr, TushareURL: cfg.TushareAPIURL}
dist, err := fs.Sub(distFS, "dist") dist, err := fs.Sub(distFS, "dist")
if err != nil { if err != nil {
@@ -50,7 +49,7 @@ func main() {
srv := &http.Server{ srv := &http.Server{
Addr: cfg.ListenAddr, Addr: cfg.ListenAddr,
Handler: router.New(deps, mgr, authDB, dist), Handler: router.New(deps, dist),
ReadHeaderTimeout: 10 * time.Second, ReadHeaderTimeout: 10 * time.Second,
} }