Improve installer extraction, login failures, and upload handling
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -32,7 +33,7 @@ type router struct {
|
||||
publicSnapshots *publicSnapshotService
|
||||
}
|
||||
|
||||
const loginRequestTimeout = 5 * time.Second
|
||||
const loginRequestTimeout = 8 * time.Second
|
||||
|
||||
func NewRouter(cfg *config.Config, store *db.Store, authService *auth.Service, feedbackService *feedback.Service, releaseService *releases.Service, sourceService *sources.Service, legacyService *legacy.Service, optional ...any) http.Handler {
|
||||
r := &router{
|
||||
@@ -189,6 +190,11 @@ func (r *router) handleAuthBootstrap(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (r *router) handleCaptcha(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", errors.New("GET required"))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
captcha, err := r.auth.NewCaptcha()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "CAPTCHA_FAILED", err)
|
||||
@@ -219,7 +225,8 @@ func (r *router) handleLogin(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(req.Context(), loginRequestTimeout)
|
||||
defer cancel()
|
||||
sessionID, csrf, ok, err := r.auth.Login(ctx, body.Username, body.Password, body.CaptchaID, body.Captcha, req.RemoteAddr)
|
||||
clientAddress := remoteHost(req.RemoteAddr)
|
||||
sessionID, csrf, failure, err := r.auth.LoginDetailed(ctx, body.Username, body.Password, body.CaptchaID, body.Captcha, clientAddress)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
|
||||
writeError(w, http.StatusGatewayTimeout, "LOGIN_TIMEOUT", errors.New("login verification timed out"))
|
||||
@@ -228,13 +235,33 @@ func (r *router) handleLogin(w http.ResponseWriter, req *http.Request) {
|
||||
writeError(w, http.StatusInternalServerError, "LOGIN_FAILED", err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
writeError(w, http.StatusOK, "LOGIN_FAILED", errors.New("invalid password or captcha"))
|
||||
if failure != auth.LoginFailureNone {
|
||||
code := "LOGIN_FAILED"
|
||||
message := "invalid username or password"
|
||||
switch failure {
|
||||
case auth.LoginFailureLocked:
|
||||
code = "LOGIN_LOCKED"
|
||||
message = "too many login attempts; try again later"
|
||||
case auth.LoginFailureCaptcha:
|
||||
code = "CAPTCHA_INVALID"
|
||||
message = "captcha is invalid or expired"
|
||||
case auth.LoginFailureCredentials:
|
||||
code = "CREDENTIALS_INVALID"
|
||||
}
|
||||
writeError(w, http.StatusOK, code, errors.New(message))
|
||||
return
|
||||
}
|
||||
auth.SetSessionCookieForRequest(w, req, sessionID)
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "csrfToken": csrf, "user": map[string]any{"username": body.Username}})
|
||||
go r.recordLoginAudit(body.Username, req.RemoteAddr, req.UserAgent())
|
||||
go r.recordLoginAudit(body.Username, clientAddress, req.UserAgent())
|
||||
}
|
||||
|
||||
func remoteHost(remoteAddress string) string {
|
||||
host, _, err := net.SplitHostPort(strings.TrimSpace(remoteAddress))
|
||||
if err == nil && host != "" {
|
||||
return host
|
||||
}
|
||||
return strings.TrimSpace(remoteAddress)
|
||||
}
|
||||
|
||||
func (r *router) recordLoginAudit(username, remoteAddr, userAgent string) {
|
||||
|
||||
Reference in New Issue
Block a user