2022-09-13 19:26:46 +00:00
package httpmw
import (
"net/http"
"regexp"
2024-01-08 22:33:57 +00:00
"strings"
2022-09-13 19:26:46 +00:00
"github.com/justinas/nosurf"
"golang.org/x/xerrors"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/codersdk"
2022-09-13 19:26:46 +00:00
)
// CSRF is a middleware that verifies that a CSRF token is present in the request
// for non-GET requests.
2024-01-08 22:33:57 +00:00
// If enforce is false, then CSRF enforcement is disabled. We still want
// to include the CSRF middleware because it will set the CSRF cookie.
2022-09-13 19:26:46 +00:00
func CSRF ( secureCookie bool ) func ( next http . Handler ) http . Handler {
return func ( next http . Handler ) http . Handler {
mw := nosurf . New ( next )
mw . SetBaseCookie ( http . Cookie { Path : "/" , HttpOnly : true , SameSite : http . SameSiteLaxMode , Secure : secureCookie } )
mw . SetFailureHandler ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . Error ( w , "Something is wrong with your CSRF token. Please refresh the page. If this error persists, try clearing your cookies." , http . StatusBadRequest )
} ) )
2024-01-08 22:33:57 +00:00
mw . ExemptRegexp ( regexp . MustCompile ( "/api/v2/users/first" ) )
2022-09-13 19:26:46 +00:00
// Exempt all requests that do not require CSRF protection.
// All GET requests are exempt by default.
mw . ExemptPath ( "/api/v2/csp/reports" )
2024-01-08 22:33:57 +00:00
// This should not be required?
mw . ExemptRegexp ( regexp . MustCompile ( "/api/v2/users/first" ) )
2022-09-13 19:26:46 +00:00
// Agent authenticated routes
mw . ExemptRegexp ( regexp . MustCompile ( "api/v2/workspaceagents/me/*" ) )
2023-12-19 21:42:05 +00:00
mw . ExemptRegexp ( regexp . MustCompile ( "api/v2/workspaceagents/*" ) )
// Workspace Proxy routes
mw . ExemptRegexp ( regexp . MustCompile ( "api/v2/workspaceproxies/me/*" ) )
2022-09-13 19:26:46 +00:00
// Derp routes
mw . ExemptRegexp ( regexp . MustCompile ( "derp/*" ) )
2023-12-19 21:42:05 +00:00
// Scim
mw . ExemptRegexp ( regexp . MustCompile ( "api/v2/scim/*" ) )
// Provisioner daemon routes
mw . ExemptRegexp ( regexp . MustCompile ( "/organizations/[^/]+/provisionerdaemons/*" ) )
2022-09-13 19:26:46 +00:00
mw . ExemptFunc ( func ( r * http . Request ) bool {
2024-01-08 22:33:57 +00:00
// Only enforce CSRF on API routes.
if ! strings . HasPrefix ( r . URL . Path , "/api" ) {
return true
}
2022-09-13 19:26:46 +00:00
// CSRF only affects requests that automatically attach credentials via a cookie.
// If no cookie is present, then there is no risk of CSRF.
//nolint:govet
2023-01-29 21:47:24 +00:00
sessCookie , err := r . Cookie ( codersdk . SessionTokenCookie )
2022-09-13 19:26:46 +00:00
if xerrors . Is ( err , http . ErrNoCookie ) {
return true
}
2023-01-29 21:47:24 +00:00
if token := r . Header . Get ( codersdk . SessionTokenHeader ) ; token == sessCookie . Value {
2022-09-13 19:26:46 +00:00
// If the cookie and header match, we can assume this is the same as just using the
// custom header auth. Custom header auth can bypass CSRF, as CSRF attacks
// cannot add custom headers.
return true
}
2023-01-29 21:47:24 +00:00
if token := r . URL . Query ( ) . Get ( codersdk . SessionTokenCookie ) ; token == sessCookie . Value {
2022-09-13 19:26:46 +00:00
// If the auth is set in a url param and matches the cookie, it
// is the same as just using the url param.
return true
}
2023-12-19 21:42:05 +00:00
if r . Header . Get ( codersdk . ProvisionerDaemonPSK ) != "" {
// If present, the provisioner daemon also is providing an api key
// that will make them exempt from CSRF. But this is still useful
// for enumerating the external auths.
return true
}
2022-09-13 19:26:46 +00:00
// If the X-CSRF-TOKEN header is set, we can exempt the func if it's valid.
// This is the CSRF check.
sent := r . Header . Get ( "X-CSRF-TOKEN" )
if sent != "" {
return nosurf . VerifyToken ( nosurf . Token ( r ) , sent )
}
return false
} )
return mw
}
}