111 lines
3.0 KiB
Go
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)
|
|
}
|