validate the Redirect_URI

This commit is contained in:
John van der Wulp
2026-03-05 22:26:39 +01:00
parent b42087dbfc
commit 45887686a7
3 changed files with 40 additions and 8 deletions

View File

@@ -2,8 +2,11 @@
package frontend
import "github.com/gin-gonic/gin"
import (
"github.com/gin-gonic/gin"
"github.com/pocket-id/pocket-id/backend/internal/service"
)
func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) error {
func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc, oidcService *service.OidcService) error {
return ErrFrontendNotIncluded
}

View File

@@ -4,6 +4,7 @@ package frontend
import (
"bytes"
"context"
"embed"
"fmt"
"io"
@@ -17,6 +18,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/pocket-id/pocket-id/backend/internal/middleware"
"github.com/pocket-id/pocket-id/backend/internal/service"
"github.com/pocket-id/pocket-id/backend/internal/utils"
)
//go:embed all:dist/*
@@ -54,7 +57,28 @@ func init() {
}
}
func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) error {
// validateRedirectURI validates that the redirect_uri is in the client's allowed callback URLs
func validateRedirectURI(ctx any, oidcService *service.OidcService, clientID, redirectURI string) (bool, error) {
client, err := oidcService.GetClient(ctx.(context.Context), clientID)
if err != nil {
return false, err
}
// If the client has no callback URLs configured, reject the redirect URI
if len(client.CallbackURLs) == 0 {
return false, fmt.Errorf("client has no callback URLs configured")
}
// Validate the redirect URI against the client's callback URLs
_, err = utils.GetCallbackURLFromList(client.CallbackURLs, redirectURI)
if err != nil {
return false, err
}
return true, nil
}
func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc, oidcService *service.OidcService) error {
distFS, err := fs.Sub(frontendFS, "dist")
if err != nil {
return fmt.Errorf("failed to create sub FS: %w", err)
@@ -92,12 +116,17 @@ func RegisterFrontend(router *gin.Engine, rateLimitMiddleware gin.HandlerFunc) e
if path == "index.html" {
// Check if this is an OAuth2 authorization request with response_mode=form_post
// In that case, we need to allow form submissions to the redirect_uri
// In that case, we need to validate and allow form submissions to the redirect_uri
responseMode := c.Query("response_mode")
redirectURI := c.Query("redirect_uri")
if responseMode == "form_post" && redirectURI != "" {
// Set the allowed form-action in CSP to include the redirect URI
middleware.SetAllowedFormAction(c, redirectURI)
clientID := c.Query("client_id")
if responseMode == "form_post" && redirectURI != "" && clientID != "" {
// Validate the redirect_uri against the client's allowlist
isValid, err := validateRedirectURI(c.Request.Context(), oidcService, clientID, redirectURI)
if err == nil && isValid {
// Set the allowed form-action in CSP to include the redirect URI
middleware.SetAllowedFormAction(c, redirectURI)
}
}
nonce := middleware.GetCSPNonce(c)

View File

@@ -61,7 +61,7 @@ func initRouter(db *gorm.DB, svc *services) (utils.Service, error) {
r.Use(middleware.NewErrorHandlerMiddleware().Add())
frontendRateLimitMiddleware := middleware.NewRateLimitMiddleware().Add(rate.Every(100*time.Millisecond), 300)
err := frontend.RegisterFrontend(r, frontendRateLimitMiddleware)
err := frontend.RegisterFrontend(r, frontendRateLimitMiddleware, svc.oidcService)
if errors.Is(err, frontend.ErrFrontendNotIncluded) {
slog.Warn("Frontend is not included in the build. Skipping frontend registration.")
} else if err != nil {