oups
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user