chore: build
This commit is contained in:
@@ -61,13 +61,16 @@ func (h *Handler) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.startSession(c, user)
|
||||
if err != nil {
|
||||
// Le JWT n'est plus jamais renvoyé dans le corps JSON (pentest F-003) :
|
||||
// uniquement posé en cookie HttpOnly par startSession. Le renvoyer ici
|
||||
// permettait au frontend de le dupliquer en localStorage, annulant la
|
||||
// protection HttpOnly contre un vol de session via XSS.
|
||||
if _, err := h.startSession(c, user); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"token": token, "token_type": "Bearer", "role": user.Role})
|
||||
c.JSON(http.StatusOK, gin.H{"role": user.Role})
|
||||
}
|
||||
|
||||
// Me renvoie l'identité de la session courante (dont le rôle, pour le front).
|
||||
@@ -111,7 +114,18 @@ func (h *Handler) Register(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, exists := h.users.ByUsername(req.Username); exists {
|
||||
if existing, exists := h.users.ByUsername(req.Username); exists {
|
||||
// Un statut 409 distinct de la création (201) permet d'énumérer les
|
||||
// comptes existants (pentest F-002) — inévitable pour un flux
|
||||
// d'inscription instantané sans vérification email (l'utilisateur a
|
||||
// besoin de savoir qu'il doit choisir un autre nom). On protège au
|
||||
// moins la cible à plus fort enjeu : un compte admin ne confirme
|
||||
// jamais son existence, la réponse est indiscernable d'un nom
|
||||
// d'utilisateur simplement invalide.
|
||||
if existing.Role == RoleAdmin {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "requête invalide"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "nom d'utilisateur déjà pris"})
|
||||
return
|
||||
}
|
||||
@@ -127,12 +141,11 @@ func (h *Handler) Register(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.startSession(c, user)
|
||||
if err != nil {
|
||||
if _, err := h.startSession(c, user); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur serveur"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"token": token, "token_type": "Bearer", "role": user.Role})
|
||||
c.JSON(http.StatusCreated, gin.H{"role": user.Role})
|
||||
}
|
||||
|
||||
// startSession ouvre une session Redis, signe le JWT et pose le cookie.
|
||||
|
||||
@@ -16,7 +16,6 @@ type Config struct {
|
||||
RedisURL string // OMNEX_REDIS_URL (sessions), ex. redis://:pass@host:6379/0
|
||||
DemoDomain string // domaine des démos
|
||||
DemoHTTPSPort string // port HTTPS public à afficher dans l'URL des démos (443 = omis, sinon ex. NodePort)
|
||||
RegistrationCode string // OMNEX_REGISTRATION_CODE (vide = inscription ouverte)
|
||||
Kubeconfig string
|
||||
FrontendImage string
|
||||
BackendImage string
|
||||
@@ -47,7 +46,6 @@ func Load() (Config, error) {
|
||||
RedisURL: os.Getenv("OMNEX_REDIS_URL"),
|
||||
DemoDomain: getenv("OMNEX_DEMO_DOMAIN", "demo.omnex.app"),
|
||||
DemoHTTPSPort: getenv("OMNEX_DEMO_HTTPS_PORT", "443"),
|
||||
RegistrationCode: os.Getenv("OMNEX_REGISTRATION_CODE"),
|
||||
Kubeconfig: os.Getenv("KUBECONFIG"),
|
||||
FrontendImage: os.Getenv("FRONTEND_IMAGE_APP"),
|
||||
BackendImage: os.Getenv("BACKEND_IMAGE_APP"),
|
||||
|
||||
@@ -34,6 +34,29 @@ func New(d Deps) *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.New()
|
||||
// Sans ça, Gin fait confiance par défaut au X-Forwarded-For fourni par
|
||||
// N'IMPORTE QUEL client pour déterminer c.ClientIP() (utilisé par
|
||||
// RateLimit) — un attaquant peut alors faire croire que chaque requête
|
||||
// vient d'une IP différente en changeant juste cet en-tête, contournant
|
||||
// intégralement la limitation de débit sur /auth/login et
|
||||
// /auth/register (trouvé en pentest, voir F-005).
|
||||
//
|
||||
// L'API n'est jamais exposée directement (pas de "ports:" dans
|
||||
// docker-compose.yml, voir docker/docker-compose.yml) — seul nginx/waf,
|
||||
// sur le réseau Docker interne, peut l'atteindre (proxy_pass vers
|
||||
// http://api:8080, voir docker/waf/nginx.conf qui construit le
|
||||
// X-Forwarded-For via $proxy_add_x_forwarded_for : ajoute toujours la
|
||||
// vraie IP vue par nginx en dernière position, sans jamais écraser une
|
||||
// valeur fournie par le client). En ne faisant confiance qu'aux plages
|
||||
// privées RFC1918 (réseau Docker interne), Gin ignore la partie du
|
||||
// X-Forwarded-For contrôlée par le client et ne retient que la partie
|
||||
// ajoutée par nginx.
|
||||
if err := r.SetTrustedProxies([]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}); err != nil {
|
||||
// CIDR statiques et valides : ne peut arriver qu'en cas d'erreur de
|
||||
// programmation (typo) — fatal au démarrage plutôt que de tourner
|
||||
// avec la protection anti-spoofing désactivée sans s'en rendre compte.
|
||||
panic(err)
|
||||
}
|
||||
r.Use(gin.Recovery())
|
||||
r.Use(httpx.SecurityHeaders())
|
||||
r.Use(httpx.CORS(d.Cfg.AllowedOrigins))
|
||||
|
||||
Reference in New Issue
Block a user