package api import ( "errors" "net/http" "time" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5" "booklib/internal/auth" ) const loginWindow = time.Minute const loginMax = 5 func (a *api) login(c *gin.Context) { var req struct{ Username, Password string } if c.ShouldBindJSON(&req) != nil || req.Username == "" || req.Password == "" { err(c, http.StatusBadRequest, "bad_request", "username and password required") return } if n := a.rdb.IncrWindow(c, "loginrl:"+c.ClientIP(), loginWindow); n > loginMax { err(c, http.StatusTooManyRequests, "rate_limited", "too many login attempts") return } u, qerr := a.st.GetUserByName(c, req.Username) if qerr != nil { if !errors.Is(qerr, pgx.ErrNoRows) { dbErr(c, qerr) return } // 用户不存在也走一次 bcrypt,防用户名枚举时序差 auth.CheckPassword("$2a$12$V5TmlpkEi9G/DFAmnkt9YunNdGLnnW921/5TcSo4OeE6iaaLDPN1K", req.Password) err(c, http.StatusUnauthorized, "unauthorized", "bad credentials") return } if !auth.CheckPassword(u.PasswordHash, req.Password) { err(c, http.StatusUnauthorized, "unauthorized", "bad credentials") return } tok, serr := auth.Sign(a.cfg.JWTSecret, u.ID, u.Role) if serr != nil { err(c, http.StatusInternalServerError, "internal", "sign") return } c.JSON(http.StatusOK, gin.H{"token": tok}) } func (a *api) me(c *gin.Context) { u, qerr := a.st.GetUserByID(c, uid(c)) if qerr != nil { err(c, http.StatusUnauthorized, "unauthorized", "no such user") return } c.JSON(http.StatusOK, gin.H{"id": u.ID, "username": u.Username, "role": u.Role}) }