package routes import ( "crypto/subtle" "embed" "encoding/base64" "fwh-toolbox-server/repositories/api/fwh_pro" "strings" "time" "github.com/gofiber/fiber/v2" ) //go:embed totp_view.html var totpViewFiles embed.FS // TotpViewSetup registers the protected page on a dedicated Fiber app. // An unset password or TOTP secret disables both endpoints. func TotpViewSetup(app *fiber.App, secret, password string) { if secret == "" || password == "" { return } view := app.Group("/fwh-totp", func(c *fiber.Ctx) error { c.Set("Cache-Control", "no-store, max-age=0") c.Set("Pragma", "no-cache") c.Set("X-Content-Type-Options", "nosniff") c.Set("X-Frame-Options", "DENY") c.Set("Referrer-Policy", "no-referrer") if !validTotpViewAuth(c.Get("Authorization"), password) { c.Set("WWW-Authenticate", `Basic realm="FWH TOTP", charset="UTF-8"`) return c.SendStatus(fiber.StatusUnauthorized) } return c.Next() }) view.Get("/", func(c *fiber.Ctx) error { page, err := totpViewFiles.ReadFile("totp_view.html") if err != nil { return fiber.ErrInternalServerError } c.Type("html", "utf-8") return c.Send(page) }) view.Get("/code", func(c *fiber.Ctx) error { now := time.Now() code, err := fwh_pro.GenerateTOTP(secret, now) if err != nil { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "TOTP 配置无效"}) } return c.JSON(fiber.Map{ "code": code, "expiresAt": (now.Unix()/30 + 1) * 30000, "serverTime": now.UnixMilli(), }) }) } func validTotpViewAuth(header, password string) bool { const prefix = "Basic " if !strings.HasPrefix(header, prefix) { return false } decoded, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(header, prefix)) if err != nil { return false } username, candidate, ok := strings.Cut(string(decoded), ":") return ok && subtle.ConstantTimeCompare([]byte(username), []byte("fwh")) == 1 && subtle.ConstantTimeCompare([]byte(candidate), []byte(password)) == 1 }