mirror of
https://github.com/gofiber/fiber.git
synced 2025-05-11 18:26:34 +00:00
* Update pull_request_template.md * Update v3-changes.md * Update CONTRIBUTING.md (#2752) Grammar correction. * chore(encryptcookie)!: update default config (#2753) * chore(encryptcookie)!: update default config docs(encryptcookie): enhance documentation and examples BREAKING CHANGE: removed the hardcoded "csrf_" from the Except. * docs(encryptcookie): reads or modifies cookies * chore(encryptcookie): csrf config example * docs(encryptcookie): md table spacing * build(deps): bump actions/setup-go from 4 to 5 (#2754) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * 🩹 middleware/logger/: log client IP address by default (#2755) * middleware/logger: Log client IP address by default. * Update doc. * fix: don't constrain middlewares' context-keys to strings 🐛 (#2751) * Revert "Revert "🐛 requestid.Config.ContextKey is interface{} (#2369)" (#2742)" This reverts commit 28be17f929cfa7d3c27dd292fc3956f2f9882e22. * fix: request ContextKey default value condition Should check for `nil` since it is `any`. * fix: don't constrain middlewares' context-keys to strings `context` recommends using "unexported type" as context keys to avoid collisions https://pkg.go.dev/github.com/gofiber/fiber/v2#Ctx.Locals. The official go blog also recommends this https://go.dev/blog/context. `fiber.Ctx.Locals(key any, value any)` correctly allows consumers to use unexported types or e.g. strings. But some fiber middlewares constrain their context-keys to `string` in their "default config structs", making it impossible to use unexported types. This PR removes the `string` _constraint_ from all middlewares, allowing to now use unexported types as per the official guidelines. However the default value is still a string, so it's not a breaking change, and anyone still using strings as context keys is not affected. * 📚 Update app.md for indentation (#2761) Update app.md for indentation * build(deps): bump github.com/google/uuid from 1.4.0 to 1.5.0 (#2762) Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/google/uuid/releases) - [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: github.com/google/uuid dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 2 to 3 (#2763) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Changing default log output (#2730) changing default log output Closes #2729 * Update hooks.md fix wrong hooks signature * 🩹 Fix: CORS middleware should use the defined AllowedOriginsFunc config when AllowedOrigins is empty (#2771) * 🐛 [Bug]: Adaptator + otelfiber issue #2641 (#2772) * 🩹🚨 - fix for redirect with query params (#2748) * redirect with query params did not work, fix it and add test for it * redirect middleware - fix test typo * ♻️ logger/middleware colorize logger error message #2593 (#2773) * ✨ feat: add liveness and readiness checks (#2509) * ✨ feat: add liveness and readiness checkers * 📝 docs: add docs for liveness and readiness * ✨ feat: add options method for probe checkers * ✅ tests: add tests for liveness and readiness * ♻️ refactor: change default endpoint values * ♻️ refactor: change default value for liveness endpoint * 📝 docs: add return status for liveness and readiness probes * ♻️ refactor: change probechecker to middleware * 📝 docs: move docs to middleware session * ♻️ refactor: apply gofumpt formatting * ♻️ refactor: remove unused parameter * split config and apply a review * apply reviews and add testcases * add benchmark * cleanup * rename middleware * fix linter * Update docs and config values * Revert change to IsReady * Updates based on code review * Update docs to match other middlewares --------- Co-authored-by: Muhammed Efe Cetin <efectn@protonmail.com> Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Co-authored-by: Juan Calderon-Perez <jgcalderonperez@protonmail.com> * prepare release v2.52.0 - add more Parser tests * fix healthcheck.md * configure workflows for V2 branch * configure workflows for V2 branch * Fix default value to false in docs of QueryBool (#2811) fix default value to false in docs of QueryBool * update queryParser config * Update ctx.md * Update routing.md * 📚 Doc: Fix code snippet indentation in /docs/api/middleware/keyauth.md Removes an an extra level of indentation in line 51 of `keyauth.md` [here](https://github.com/gofiber/fiber/blob/v2/docs/api/middleware/keyauth.md?plain=1#L51) * fix: healthcheck middleware not working with route group (#2863) * fix: healthcheck middleware not working with route group * perf: change verification method to improve perf * Update healthcheck_test.go * test: add not matching route test for strict routing * add more test cases * correct tests * correct test helpers * correct tests * correct tests --------- Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Co-authored-by: René Werner <rene@gofiber.io> * Merge pull request from GHSA-fmg4-x8pw-hjhg * Enforce Wildcard Origins with AllowCredentials check * Expand unit-tests, fix issues with subdomains logic, update docs * Update cors.md * Added test using localhost, ipv4, and ipv6 address * improve documentation markdown --------- Co-authored-by: René Werner <rene@gofiber.io> * Update app.go prepare release v2.52.1 * fix cors domain normalize * fix sync-docs workflow * fix sync-docs workflow * fix(middleware/cors): Validation of multiple Origins (#2883) * fix: allow origins check Refactor CORS origin validation and normalization to trim leading or trailing whitespace in the cfg.AllowOrigins string [list]. URLs with whitespace inside the URL are invalid, so the normalizeOrigin will return false because url.Parse will fail, and the middleware will panic. fixes #2882 * test: AllowOrigins with whitespace * test(middleware/cors): add benchmarks * chore: fix linter errors * test(middleware/cors): use h() instead of app.Test() * test(middleware/cors): add miltiple origins in Test_CORS_AllowOriginScheme * chore: refactor validate and normalize * test(cors/middleware): add more benchmarks * prepare release v2.52.2 * refactor(docs): deactivate docs sync for v2 * refactor(docs): deactivate docs sync for v2 * fix(middleware/cors): Handling and wildcard subdomain matching (#2915) * fix: allow origins check Refactor CORS origin validation and normalization to trim leading or trailing whitespace in the cfg.AllowOrigins string [list]. URLs with whitespace inside the URL are invalid, so the normalizeOrigin will return false because url.Parse will fail, and the middleware will panic. fixes #2882 * test: AllowOrigins with whitespace * test(middleware/cors): add benchmarks * chore: fix linter errors * test(middleware/cors): use h() instead of app.Test() * test(middleware/cors): add miltiple origins in Test_CORS_AllowOriginScheme * chore: refactor validate and normalize * test(cors/middleware): add more benchmarks * fix(middleware/cors): handling and wildcard subdomain matching docs(middleware/cors): add How it works and Security Considerations * chore: grammar * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: fix misspelling * test(middleware/cors): combine Invalid_Origins tests * refactor(middleware/cors): headers handling * docs(middleware/cors): Update AllowOrigins description * chore: merge * perf(middleware/cors): optimize handler * perf(middleware/cors): optimize handler * chore(middleware/cors): ipdate origin handling logic * chore(middleware/cors): fix header capitalization * docs(middleware/cors): improve sercuity notes * docs(middleware/cors): Improve security notes * docs(middleware/cors): improve CORS overview * docs(middleware/cors): fix ordering of how it works * docs(middleware/cors): add additional info to How to works * docs(middleware/cors): rm space * docs(middleware/cors): add validation for AllowOrigins origins to overview * docs(middleware/cors): update ExposeHeaders and MaxAge descriptions * docs(middleware/cors): Add dynamic origin validation example * docs(middleware/cors): Improve security notes and fix header capitalization * docs(middleware/cors): configuration examples * docs(middleware/cors): `"*"` --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix(middleware/cors): Categorize requests correctly (#2921) * fix(middleware/cors): categorise requests correctly * test(middleware/cors): improve test coverage for request types * test(middleware/cors): Add subdomain matching tests * test(middleware/cors): parallel tests for CORS headers based on request type * test(middleware/cors): Add benchmark for CORS subdomain matching * test(middleware/cors): cover additiona test cases * refactor(middleware/cors): origin validation and normalization * test(middleware/csrf): Fix Benchmark Tests (#2932) * test(middleware/csrf): fix Benchmark_Middleware_CSRF_* * fix(middleware/csrf): update refererMatchesHost() * Prepare release v2.52.3 * fix(middleware/cors): CORS handling (#2937) * fix(middleware/cors): CORS handling * fix(middleware/cors): Vary header handling * test(middleware/cors): Ensure Vary Headers checked * fix(middleware/cors): Vary header handling non-cors OPTIONS requests (#2939) * fix(middleware/cors): Vary header handling non-cors OPTIONS requests * chore(middleware/cors): Add Vary header for non-CORS OPTIONS requests comment * prepare release v2.52.4 * merge v2 in main(v3) * merge v2 in main(v3) * merge v2 in main(v3) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: tokelo-12 <113810058+tokelo-12@users.noreply.github.com> Co-authored-by: Jason McNeil <sixcolors@mac.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: iRedMail <2048991+iredmail@users.noreply.github.com> Co-authored-by: Benjamin Grosse <ste3ls@gmail.com> Co-authored-by: Mehmet Firat KOMURCU <mehmetfiratkomurcu@hotmail.com> Co-authored-by: Bruno <bdm2943@icloud.com> Co-authored-by: Muhammad Kholid B <muhammadkholidb@gmail.com> Co-authored-by: gilwo <gilwo@users.noreply.github.com> Co-authored-by: Lucas Lemos <lucashenriqueblemos@gmail.com> Co-authored-by: Muhammed Efe Cetin <efectn@protonmail.com> Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Co-authored-by: Juan Calderon-Perez <jgcalderonperez@protonmail.com> Co-authored-by: Jongmin Kim <kjongmin26@gmail.com> Co-authored-by: Giovanni Rivera <rivera.giovanni271@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
350 lines
10 KiB
Go
350 lines
10 KiB
Go
package csrf
|
|
|
|
import (
|
|
"errors"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
)
|
|
|
|
var (
|
|
ErrTokenNotFound = errors.New("csrf token not found")
|
|
ErrTokenInvalid = errors.New("csrf token invalid")
|
|
ErrRefererNotFound = errors.New("referer not supplied")
|
|
ErrRefererInvalid = errors.New("referer invalid")
|
|
ErrRefererNoMatch = errors.New("referer does not match host and is not a trusted origin")
|
|
ErrOriginInvalid = errors.New("origin invalid")
|
|
ErrOriginNoMatch = errors.New("origin does not match host and is not a trusted origin")
|
|
errOriginNotFound = errors.New("origin not supplied or is null") // internal error, will not be returned to the user
|
|
dummyValue = []byte{'+'}
|
|
)
|
|
|
|
// Handler for CSRF middleware
|
|
type Handler struct {
|
|
config Config
|
|
sessionManager *sessionManager
|
|
storageManager *storageManager
|
|
}
|
|
|
|
// The contextKey type is unexported to prevent collisions with context keys defined in
|
|
// other packages.
|
|
type contextKey int
|
|
|
|
// The keys for the values in context
|
|
const (
|
|
tokenKey contextKey = iota
|
|
handlerKey
|
|
)
|
|
|
|
// New creates a new middleware handler
|
|
func New(config ...Config) fiber.Handler {
|
|
// Set default config
|
|
cfg := configDefault(config...)
|
|
|
|
// Create manager to simplify storage operations ( see *_manager.go )
|
|
var sessionManager *sessionManager
|
|
var storageManager *storageManager
|
|
if cfg.Session != nil {
|
|
// Register the Token struct in the session store
|
|
cfg.Session.RegisterType(Token{})
|
|
|
|
sessionManager = newSessionManager(cfg.Session, cfg.SessionKey)
|
|
} else {
|
|
storageManager = newStorageManager(cfg.Storage)
|
|
}
|
|
|
|
// Pre-parse trusted origins
|
|
trustedOrigins := []string{}
|
|
trustedSubOrigins := []subdomain{}
|
|
|
|
for _, origin := range cfg.TrustedOrigins {
|
|
if i := strings.Index(origin, "://*."); i != -1 {
|
|
trimmedOrigin := strings.TrimSpace(origin[:i+3] + origin[i+4:])
|
|
isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin)
|
|
if !isValid {
|
|
panic("[CSRF] Invalid origin format in configuration:" + origin)
|
|
}
|
|
sd := subdomain{prefix: normalizedOrigin[:i+3], suffix: normalizedOrigin[i+3:]}
|
|
trustedSubOrigins = append(trustedSubOrigins, sd)
|
|
} else {
|
|
trimmedOrigin := strings.TrimSpace(origin)
|
|
isValid, normalizedOrigin := normalizeOrigin(trimmedOrigin)
|
|
if !isValid {
|
|
panic("[CSRF] Invalid origin format in configuration:" + origin)
|
|
}
|
|
trustedOrigins = append(trustedOrigins, normalizedOrigin)
|
|
}
|
|
}
|
|
|
|
// Create the handler outside of the returned function
|
|
handler := &Handler{
|
|
config: cfg,
|
|
sessionManager: sessionManager,
|
|
storageManager: storageManager,
|
|
}
|
|
|
|
// Return new handler
|
|
return func(c fiber.Ctx) error {
|
|
// Don't execute middleware if Next returns true
|
|
if cfg.Next != nil && cfg.Next(c) {
|
|
return c.Next()
|
|
}
|
|
|
|
// Store the CSRF handler in the context
|
|
c.Locals(handlerKey, handler)
|
|
|
|
var token string
|
|
|
|
// Action depends on the HTTP method
|
|
switch c.Method() {
|
|
case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace:
|
|
cookieToken := c.Cookies(cfg.CookieName)
|
|
|
|
if cookieToken != "" {
|
|
raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
|
|
|
|
if raw != nil {
|
|
token = cookieToken // Token is valid, safe to set it
|
|
}
|
|
}
|
|
default:
|
|
// Assume that anything not defined as 'safe' by RFC7231 needs protection
|
|
|
|
// Enforce an origin check for unsafe requests.
|
|
err := originMatchesHost(c, trustedOrigins, trustedSubOrigins)
|
|
|
|
// If there's no origin, enforce a referer check for HTTPS connections.
|
|
if errors.Is(err, errOriginNotFound) {
|
|
if c.Scheme() == "https" {
|
|
err = refererMatchesHost(c, trustedOrigins, trustedSubOrigins)
|
|
} else {
|
|
// If it's not HTTPS, clear the error to allow the request to proceed.
|
|
err = nil
|
|
}
|
|
}
|
|
|
|
// If there's an error (either from origin check or referer check), handle it.
|
|
if err != nil {
|
|
return cfg.ErrorHandler(c, err)
|
|
}
|
|
|
|
// Extract token from client request i.e. header, query, param, form or cookie
|
|
extractedToken, err := cfg.Extractor(c)
|
|
if err != nil {
|
|
return cfg.ErrorHandler(c, err)
|
|
}
|
|
|
|
if extractedToken == "" {
|
|
return cfg.ErrorHandler(c, ErrTokenNotFound)
|
|
}
|
|
|
|
// If not using FromCookie extractor, check that the token matches the cookie
|
|
// This is to prevent CSRF attacks by using a Double Submit Cookie method
|
|
// Useful when we do not have access to the users Session
|
|
if !isFromCookie(cfg.Extractor) && !compareStrings(extractedToken, c.Cookies(cfg.CookieName)) {
|
|
return cfg.ErrorHandler(c, ErrTokenInvalid)
|
|
}
|
|
|
|
raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
|
|
|
|
if raw == nil {
|
|
// If token is not in storage, expire the cookie
|
|
expireCSRFCookie(c, cfg)
|
|
// and return an error
|
|
return cfg.ErrorHandler(c, ErrTokenNotFound)
|
|
}
|
|
if cfg.SingleUseToken {
|
|
// If token is single use, delete it from storage
|
|
deleteTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
|
|
} else {
|
|
token = extractedToken // Token is valid, safe to set it
|
|
}
|
|
}
|
|
|
|
// Generate CSRF token if not exist
|
|
if token == "" {
|
|
// And generate a new token
|
|
token = cfg.KeyGenerator()
|
|
}
|
|
|
|
// Create or extend the token in the storage
|
|
createOrExtendTokenInStorage(c, token, cfg, sessionManager, storageManager)
|
|
|
|
// Update the CSRF cookie
|
|
updateCSRFCookie(c, cfg, token)
|
|
|
|
// Tell the browser that a new header value is generated
|
|
c.Vary(fiber.HeaderCookie)
|
|
|
|
// Store the token in the context
|
|
c.Locals(tokenKey, token)
|
|
|
|
// Continue stack
|
|
return c.Next()
|
|
}
|
|
}
|
|
|
|
// TokenFromContext returns the token found in the context
|
|
// returns an empty string if the token does not exist
|
|
func TokenFromContext(c fiber.Ctx) string {
|
|
token, ok := c.Locals(tokenKey).(string)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return token
|
|
}
|
|
|
|
// HandlerFromContext returns the Handler found in the context
|
|
// returns nil if the handler does not exist
|
|
func HandlerFromContext(c fiber.Ctx) *Handler {
|
|
handler, ok := c.Locals(handlerKey).(*Handler)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return handler
|
|
}
|
|
|
|
// getRawFromStorage returns the raw value from the storage for the given token
|
|
// returns nil if the token does not exist, is expired or is invalid
|
|
func getRawFromStorage(c fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
|
|
if cfg.Session != nil {
|
|
return sessionManager.getRaw(c, token, dummyValue)
|
|
}
|
|
return storageManager.getRaw(token)
|
|
}
|
|
|
|
// createOrExtendTokenInStorage creates or extends the token in the storage
|
|
func createOrExtendTokenInStorage(c fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) {
|
|
if cfg.Session != nil {
|
|
sessionManager.setRaw(c, token, dummyValue, cfg.Expiration)
|
|
} else {
|
|
storageManager.setRaw(token, dummyValue, cfg.Expiration)
|
|
}
|
|
}
|
|
|
|
func deleteTokenFromStorage(c fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) {
|
|
if cfg.Session != nil {
|
|
sessionManager.delRaw(c)
|
|
} else {
|
|
storageManager.delRaw(token)
|
|
}
|
|
}
|
|
|
|
// Update CSRF cookie
|
|
// if expireCookie is true, the cookie will expire immediately
|
|
func updateCSRFCookie(c fiber.Ctx, cfg Config, token string) {
|
|
setCSRFCookie(c, cfg, token, cfg.Expiration)
|
|
}
|
|
|
|
func expireCSRFCookie(c fiber.Ctx, cfg Config) {
|
|
setCSRFCookie(c, cfg, "", -time.Hour)
|
|
}
|
|
|
|
func setCSRFCookie(c fiber.Ctx, cfg Config, token string, expiry time.Duration) {
|
|
cookie := &fiber.Cookie{
|
|
Name: cfg.CookieName,
|
|
Value: token,
|
|
Domain: cfg.CookieDomain,
|
|
Path: cfg.CookiePath,
|
|
Secure: cfg.CookieSecure,
|
|
HTTPOnly: cfg.CookieHTTPOnly,
|
|
SameSite: cfg.CookieSameSite,
|
|
SessionOnly: cfg.CookieSessionOnly,
|
|
Expires: time.Now().Add(expiry),
|
|
}
|
|
|
|
// Set the CSRF cookie to the response
|
|
c.Cookie(cookie)
|
|
}
|
|
|
|
// DeleteToken removes the token found in the context from the storage
|
|
// and expires the CSRF cookie
|
|
func (handler *Handler) DeleteToken(c fiber.Ctx) error {
|
|
// Extract token from the client request cookie
|
|
cookieToken := c.Cookies(handler.config.CookieName)
|
|
if cookieToken == "" {
|
|
return handler.config.ErrorHandler(c, ErrTokenNotFound)
|
|
}
|
|
// Remove the token from storage
|
|
deleteTokenFromStorage(c, cookieToken, handler.config, handler.sessionManager, handler.storageManager)
|
|
// Expire the cookie
|
|
expireCSRFCookie(c, handler.config)
|
|
return nil
|
|
}
|
|
|
|
// isFromCookie checks if the extractor is set to ExtractFromCookie
|
|
func isFromCookie(extractor any) bool {
|
|
return reflect.ValueOf(extractor).Pointer() == reflect.ValueOf(FromCookie).Pointer()
|
|
}
|
|
|
|
// originMatchesHost checks that the origin header matches the host header
|
|
// returns an error if the origin header is not present or is invalid
|
|
// returns nil if the origin header is valid
|
|
func originMatchesHost(c fiber.Ctx, trustedOrigins []string, trustedSubOrigins []subdomain) error {
|
|
origin := strings.ToLower(c.Get(fiber.HeaderOrigin))
|
|
if origin == "" || origin == "null" { // "null" is set by some browsers when the origin is a secure context https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description
|
|
return errOriginNotFound
|
|
}
|
|
|
|
originURL, err := url.Parse(origin)
|
|
if err != nil {
|
|
return ErrOriginInvalid
|
|
}
|
|
|
|
if originURL.Scheme == c.Scheme() && originURL.Host == c.Host() {
|
|
return nil
|
|
}
|
|
|
|
for _, trustedOrigin := range trustedOrigins {
|
|
if origin == trustedOrigin {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for _, trustedSubOrigin := range trustedSubOrigins {
|
|
if trustedSubOrigin.match(origin) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return ErrOriginNoMatch
|
|
}
|
|
|
|
// refererMatchesHost checks that the referer header matches the host header
|
|
// returns an error if the referer header is not present or is invalid
|
|
// returns nil if the referer header is valid
|
|
func refererMatchesHost(c fiber.Ctx, trustedOrigins []string, trustedSubOrigins []subdomain) error {
|
|
referer := strings.ToLower(c.Get(fiber.HeaderReferer))
|
|
if referer == "" {
|
|
return ErrRefererNotFound
|
|
}
|
|
|
|
refererURL, err := url.Parse(referer)
|
|
if err != nil {
|
|
return ErrRefererInvalid
|
|
}
|
|
|
|
if refererURL.Scheme == c.Scheme() && refererURL.Host == c.Host() {
|
|
return nil
|
|
}
|
|
|
|
referer = refererURL.String()
|
|
|
|
for _, trustedOrigin := range trustedOrigins {
|
|
if referer == trustedOrigin {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for _, trustedSubOrigin := range trustedSubOrigins {
|
|
if trustedSubOrigin.match(referer) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return ErrRefererNoMatch
|
|
}
|