package website import ( "Portifolio/internal/middleware" "Portifolio/internal/model" "Portifolio/internal/service" "crypto/rand" _ "embed" "encoding/base64" "fmt" "net/http" "os" "strconv" "time" "github.com/FuLygon/go-totp/v2" _ "github.com/mattn/go-sqlite3" ) //go:embed static/index.html var indexHTML []byte func Getsite() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(indexHTML) } } //go:embed static/login.html var loginxHTML []byte func GetAdminLogin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(loginxHTML) } } type contextKey string const ContextKeyRequestTime contextKey = "requestTime" func LoginHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // checked once when the handler is registered envUsername := os.Getenv("username") envPassword := os.Getenv("password") envTOTPSecret := os.Getenv("AUTH_TOTP_SECRET") if envUsername == "" || envPassword == "" || envTOTPSecret == "" { http.Error(w, "env var missing", http.StatusInternalServerError) } ts, ok := r.Context().Value(ContextKeyRequestTime).(time.Time) if !ok { ts = time.Now() } if err := r.ParseForm(); err != nil { http.Error(w, "invalid form data", http.StatusBadRequest) return } password := r.FormValue("password") username := r.FormValue("username") code := r.FormValue("auth_code") fmt.Println(username, password, code) if password == "" || username == "" || code == "" { http.Error(w, "form value is empty", http.StatusBadRequest) return } if password != envPassword || username != envUsername { http.Error(w, "username or password is not valid", http.StatusUnauthorized) return } v := totp.Validator{ Algorithm: totp.AlgorithmSHA1, Digits: 6, Period: 30, Secret: envTOTPSecret, } valid, err := v.ValidateWithTimestamp(code, ts.Unix()) if err != nil { http.Error(w, fmt.Sprintf("error validating TOTP code: %s", err), http.StatusUnauthorized) return } if !valid { http.Error(w, "invalid auth code", http.StatusUnauthorized) return } // In your login POST handler, after setting the cookie: token := generateSessionToken() middleware.RegisterSession(token) // <-- register before writing cookie http.SetCookie(w, &http.Cookie{ Name: "session", Value: token, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode, MaxAge: 86400, }) w.Header().Set("HX-Redirect", "/admin") w.WriteHeader(http.StatusOK) } } // Simple session token generator func generateSessionToken() string { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { panic(err) } return base64.URLEncoding.EncodeToString(b) } //go:embed static/admin.html var adminHTML []byte func GetAdmin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(adminHTML) } } //go:embed static/styles.css var styleCSSmain []byte func GetstylesheetMain() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css; charset=utf-8") w.Write(styleCSSmain) } } //go:embed static/login.css var styleCSSlogin []byte func GetstylesheetLogin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css; charset=utf-8") w.Write(styleCSSlogin) } } //go:embed static/admin.css var styleCSSadmin []byte func GetstylesheetAdmin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css; charset=utf-8") w.Write(styleCSSadmin) } } func HealthFragment(svc *service.Service) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { h := svc.CheckHealth() status := h["status"].(string) uptime := h["uptime"].(string) dotClass := "ok" statusText := "backend ok" if status != "ok" { dotClass = "error" statusText = "backend " + status w.WriteHeader(http.StatusServiceUnavailable) // ← triggers htmx:responseError } dbInfo := h["database"].(map[string]any) dbStatus := dbInfo["status"].(string) dbLatency := dbInfo["latency"].(string) dbText := "db " + dbStatus if dbLatency != "" { dbText += " · " + dbLatency } fmt.Fprintf(w, ` %s · up %s · %s `, dotClass, statusText, uptime, dbText) } } // GET /positions/fragment — returns