Files
bot_ticket_r6_elite/internal/panel/handlers/login.go
T
2026-05-10 18:07:51 +02:00

111 lines
3.0 KiB
Go

package handlers
import (
"log/slog"
"net/http"
"time"
"golang.org/x/oauth2"
"github.com/leolionad58/ticketbot/internal/db"
panelauth "github.com/leolionad58/ticketbot/internal/panel/auth"
)
// LoginHandler handles the Discord OAuth2 login flow.
type LoginHandler struct {
Admins *db.PanelAdminRepo
Auth *panelauth.Service
OAuthCfg *oauth2.Config
Renderer *Renderer
}
// HandleLogin serves GET /login.
func (h *LoginHandler) HandleLogin(w http.ResponseWriter, r *http.Request) {
state, err := panelauth.GenerateState()
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
secure := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
http.SetCookie(w, &http.Cookie{
Name: "oauth_state",
Value: state,
Path: "/oauth",
HttpOnly: true,
Secure: secure,
SameSite: http.SameSiteLaxMode, // Lax to survive the Discord redirect
MaxAge: 60,
})
type loginData struct {
OAuthURL string
Error string
}
h.Renderer.Auth(w, "login", loginData{
OAuthURL: panelauth.AuthURL(h.OAuthCfg, state),
})
}
// HandleCallback serves GET /oauth/callback.
func (h *LoginHandler) HandleCallback(w http.ResponseWriter, r *http.Request) {
// Validate state
stateCookie, err := r.Cookie("oauth_state")
if err != nil || stateCookie.Value != r.URL.Query().Get("state") {
type loginData struct{ OAuthURL, Error string }
h.Renderer.Auth(w, "login", loginData{Error: "Session expirée, réessaie."})
return
}
// Clear state cookie
http.SetCookie(w, &http.Cookie{Name: "oauth_state", Path: "/oauth", MaxAge: -1, Expires: time.Unix(0, 0)})
code := r.URL.Query().Get("code")
if code == "" {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
ctx := r.Context()
discordUser, err := panelauth.FetchDiscordUser(ctx, h.OAuthCfg, code)
if err != nil {
slog.Warn("oauth callback: fetch discord user", "err", err)
type loginData struct{ OAuthURL, Error string }
h.Renderer.Auth(w, "login", loginData{
OAuthURL: panelauth.AuthURL(h.OAuthCfg, ""),
Error: "Erreur lors de la connexion Discord. Réessaie.",
})
return
}
// Check admin whitelist
admin, err := h.Admins.GetByDiscordID(ctx, discordUser.ID)
if err != nil {
slog.Error("oauth callback: get admin", "err", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if admin == nil {
// Not in whitelist — show denied page
h.Renderer.Auth(w, "denied", nil)
return
}
// Update Discord profile info
_ = h.Admins.UpdateDiscordProfile(ctx, admin.ID, discordUser.Username, discordUser.Avatar)
admin.DiscordUsername = discordUser.Username
admin.DiscordAvatar = discordUser.Avatar
// Store pending auth cookie
panelauth.SetPendingAuthCookie(w, r, admin.DiscordID)
if admin.PasswordHash == "" {
http.Redirect(w, r, "/auth/password-setup", http.StatusSeeOther)
return
}
if !admin.TOTPEnabled {
http.Redirect(w, r, "/auth/totp-setup", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/auth/verify", http.StatusSeeOther)
}