Skip to content

Commit

Permalink
remove micromatch as a dependency for csrf protection
Browse files Browse the repository at this point in the history
  • Loading branch information
akawalsky committed Dec 11, 2023
1 parent 3b2bc3c commit ee662fa
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 5 deletions.
13 changes: 10 additions & 3 deletions packages/next/src/server/app-render/csrf-protection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ describe('isCsrfOriginAllowed', () => {
expect(isCsrfOriginAllowed('www.vercel.com', ['www.vercel.com'])).toBe(true)
})

it('should return true when allowedOrigins contains originDomain with matching regex', () => {
it('should return true when allowedOrigins contains originDomain with matching pattern', () => {
expect(isCsrfOriginAllowed('asdf.vercel.com', ['*.vercel.com'])).toBe(true)
expect(isCsrfOriginAllowed('asdf.jkl.vercel.com', ['*.vercel.com'])).toBe(
expect(isCsrfOriginAllowed('asdf.vercel.com', ['**.vercel.com'])).toBe(true)
expect(isCsrfOriginAllowed('asdf.jkl.vercel.com', ['**.vercel.com'])).toBe(
true
)
})

it('should return false when allowedOrigins contains originDomain with non-matching regex', () => {
it('should return false when allowedOrigins contains originDomain with non-matching pattern', () => {
expect(isCsrfOriginAllowed('asdf.vercel.com', ['*.vercel.app'])).toBe(false)
expect(isCsrfOriginAllowed('asdf.jkl.vercel.com', ['*.vercel.com'])).toBe(
false
)
expect(isCsrfOriginAllowed('asdf.jkl.vercel.app', ['**.vercel.com'])).toBe(
false
)
})

it('should return false when allowedOrigins does not contain originDomain', () => {
Expand Down
37 changes: 35 additions & 2 deletions packages/next/src/server/app-render/csrf-protection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
import { isMatch } from 'next/dist/compiled/micromatch'
// micromatch is only available at node runtime, so it cannot be used here since the code path that calls this function
// can be run from edge. This is a simple implementation that safely achieves the required functionality.
// the goal is to match the functionality for remotePatterns as defined here -
// https://nextjs.org/docs/app/api-reference/components/image#remotepatterns
// TODO - retrofit micromatch to work in edge and use that instead
function matchWildcardDomain(domain: string, pattern: string) {
const domainParts = domain.split('.')
const patternParts = pattern.split('.')

// Iterate through each part and compare them
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i] === '**') {
// If '**' is encountered, ensure remaining domain ends with remaining pattern
// e.g. **.y.z and a.b.c.y.z should match (b.c.y.z ends with y.z)
const remainingPattern = patternParts.slice(i + 1).join('.')
const remainingDomain = domainParts.slice(i + 1).join('.')
return remainingDomain.endsWith(remainingPattern)
} else if (patternParts[i] === '*') {
// If '*' is encountered, ensure remaining domain is equal to remaining pattern
// e.g. *.y.z and c.y.z should match (y.z is equal to y.z)
const remainingPattern = patternParts.slice(i + 1).join('.')
const remainingDomain = domainParts.slice(i + 1).join('.')
return remainingDomain === remainingPattern
}

// If '*' is not encountered, compare the parts
if (patternParts[i] !== domainParts[i]) {
return false
}
}

return true
}

export const isCsrfOriginAllowed = (
originDomain: string,
Expand All @@ -7,6 +39,7 @@ export const isCsrfOriginAllowed = (
return allowedOrigins.some(
(allowedOrigin) =>
allowedOrigin &&
(allowedOrigin === originDomain || isMatch(originDomain, allowedOrigin))
(allowedOrigin === originDomain ||
matchWildcardDomain(originDomain, allowedOrigin))
)
}

0 comments on commit ee662fa

Please sign in to comment.