Use session-based flash messages instead of query params
Fixes #33 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ec94d94453
commit
8603b0bfb5
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/forgejo"
|
"github.com/mattnite/forgejo-tickets/internal/forgejo"
|
||||||
|
"github.com/mattnite/forgejo-tickets/internal/middleware"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/models"
|
"github.com/mattnite/forgejo-tickets/internal/models"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
@ -202,11 +202,8 @@ func (h *UserHandler) Approve(c *gin.Context) {
|
||||||
log.Error().Err(err).Msg("send approval email error")
|
log.Error().Err(err).Msg("send approval email error")
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := "/users/pending?" + url.Values{
|
middleware.SetFlash(c, "success", "User "+user.Email+" has been approved")
|
||||||
"flash": {"User " + user.Email + " has been approved"},
|
c.Redirect(http.StatusSeeOther, "/users/pending")
|
||||||
"flash_type": {"success"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UserHandler) Reject(c *gin.Context) {
|
func (h *UserHandler) Reject(c *gin.Context) {
|
||||||
|
|
@ -222,11 +219,8 @@ func (h *UserHandler) Reject(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := "/users/pending?" + url.Values{
|
middleware.SetFlash(c, "success", "User request has been rejected")
|
||||||
"flash": {"User request has been rejected"},
|
c.Redirect(http.StatusSeeOther, "/users/pending")
|
||||||
"flash_type": {"success"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UserHandler) UpdateRepos(c *gin.Context) {
|
func (h *UserHandler) UpdateRepos(c *gin.Context) {
|
||||||
|
|
@ -257,9 +251,6 @@ func (h *UserHandler) UpdateRepos(c *gin.Context) {
|
||||||
h.deps.DB.Create(&models.UserRepo{UserID: userID, RepoID: repoID})
|
h.deps.DB.Create(&models.UserRepo{UserID: userID, RepoID: repoID})
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := "/users/" + userID.String() + "?" + url.Values{
|
middleware.SetFlash(c, "success", "Project assignments updated")
|
||||||
"flash": {"Project assignments updated"},
|
c.Redirect(http.StatusSeeOther, "/users/"+userID.String())
|
||||||
"flash_type": {"success"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ package public
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/auth"
|
"github.com/mattnite/forgejo-tickets/internal/auth"
|
||||||
|
"github.com/mattnite/forgejo-tickets/internal/middleware"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/models"
|
"github.com/mattnite/forgejo-tickets/internal/models"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
@ -111,11 +111,8 @@ func (h *AuthHandler) Register(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := "/login?" + url.Values{
|
middleware.SetFlash(c, "success", "Account requested! Please check your email to verify your address. After verification, an admin will review your request.")
|
||||||
"flash": {"Account requested! Please check your email to verify your address. After verification, an admin will review your request."},
|
c.Redirect(http.StatusSeeOther, "/login")
|
||||||
"flash_type": {"success"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) Logout(c *gin.Context) {
|
func (h *AuthHandler) Logout(c *gin.Context) {
|
||||||
|
|
@ -138,11 +135,8 @@ func (h *AuthHandler) VerifyEmail(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := "/login?" + url.Values{
|
middleware.SetFlash(c, "success", "Email verified successfully. Your account is pending admin approval.")
|
||||||
"flash": {"Email verified successfully. Your account is pending admin approval."},
|
c.Redirect(http.StatusSeeOther, "/login")
|
||||||
"flash_type": {"success"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) ForgotPasswordForm(c *gin.Context) {
|
func (h *AuthHandler) ForgotPasswordForm(c *gin.Context) {
|
||||||
|
|
@ -206,9 +200,6 @@ func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectURL := "/login?" + url.Values{
|
middleware.SetFlash(c, "success", "Password reset successfully. You can now log in.")
|
||||||
"flash": {"Password reset successfully. You can now log in."},
|
c.Redirect(http.StatusSeeOther, "/login")
|
||||||
"flash_type": {"success"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/auth"
|
"github.com/mattnite/forgejo-tickets/internal/auth"
|
||||||
|
"github.com/mattnite/forgejo-tickets/internal/middleware"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
@ -98,11 +98,8 @@ func (h *OAuthHandler) Callback(c *gin.Context) {
|
||||||
user, err := h.deps.Auth.FindOrCreateOAuthUser(c.Request.Context(), provider.Name, info)
|
user, err := h.deps.Auth.FindOrCreateOAuthUser(c.Request.Context(), provider.Name, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "pending admin approval") {
|
if strings.Contains(err.Error(), "pending admin approval") {
|
||||||
redirectURL := "/login?" + url.Values{
|
middleware.SetFlash(c, "info", err.Error())
|
||||||
"flash": {err.Error()},
|
c.Redirect(http.StatusSeeOther, "/login")
|
||||||
"flash_type": {"info"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error().Err(err).Msg("find or create oauth user error")
|
log.Error().Err(err).Msg("find or create oauth user error")
|
||||||
|
|
@ -196,11 +193,8 @@ func (h *OAuthHandler) AppleCallback(c *gin.Context) {
|
||||||
user, err := h.deps.Auth.FindOrCreateOAuthUser(c.Request.Context(), "apple", info)
|
user, err := h.deps.Auth.FindOrCreateOAuthUser(c.Request.Context(), "apple", info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "pending admin approval") {
|
if strings.Contains(err.Error(), "pending admin approval") {
|
||||||
redirectURL := "/login?" + url.Values{
|
middleware.SetFlash(c, "info", err.Error())
|
||||||
"flash": {err.Error()},
|
c.Redirect(http.StatusSeeOther, "/login")
|
||||||
"flash_type": {"info"},
|
|
||||||
}.Encode()
|
|
||||||
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error().Err(err).Msg("find or create apple user error")
|
log.Error().Err(err).Msg("find or create apple user error")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetFlash sets a flash message cookie that will be consumed on the next render.
|
||||||
|
func SetFlash(c *gin.Context, flashType, message string) {
|
||||||
|
// Encode as "type:message" in base64 to avoid cookie value issues
|
||||||
|
value := base64.StdEncoding.EncodeToString([]byte(flashType + ":" + message))
|
||||||
|
http.SetCookie(c.Writer, &http.Cookie{
|
||||||
|
Name: "flash",
|
||||||
|
Value: value,
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: 60,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFlash reads and clears the flash message cookie.
|
||||||
|
func GetFlash(r *http.Request, w http.ResponseWriter) (flashType, message string) {
|
||||||
|
cookie, err := r.Cookie("flash")
|
||||||
|
if err != nil || cookie.Value == "" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
// Clear the cookie
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "flash",
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: -1,
|
||||||
|
})
|
||||||
|
data, err := base64.StdEncoding.DecodeString(cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(string(data), ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/auth"
|
"github.com/mattnite/forgejo-tickets/internal/auth"
|
||||||
|
"github.com/mattnite/forgejo-tickets/internal/middleware"
|
||||||
"github.com/mattnite/forgejo-tickets/internal/models"
|
"github.com/mattnite/forgejo-tickets/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -103,12 +104,8 @@ func (r *Renderer) Render(w http.ResponseWriter, req *http.Request, name string,
|
||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg := req.URL.Query().Get("flash"); msg != "" {
|
if flashType, flashMsg := middleware.GetFlash(req, w); flashMsg != "" {
|
||||||
flashType := req.URL.Query().Get("flash_type")
|
pd.Flash = &Flash{Type: flashType, Message: flashMsg}
|
||||||
if flashType == "" {
|
|
||||||
flashType = "info"
|
|
||||||
}
|
|
||||||
pd.Flash = &Flash{Type: flashType, Message: msg}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue