Skip to content
Snippets Groups Projects

server: Protect against cross-origin websocket requests

Merged Clément Pit-Claudel requested to merge cpc/websocket-origin into main

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
6 6 val caskVersion = "0.9.4"
7 7 val slf4jVersion = "2.0.5"
8 8 val reflectionsVersion = "0.10.2"
9 val scalaCheckVersion = "1.18.1"
  • Can you take a look at https://docs3.scala-lang.org/toolkit/web-server-cookies-and-decorators.html#using-decorators ? I'd like to get things to be a bit shorter than what we ended up with, and perhaps a RawDecorator is the way to do that? Ideally we'd keep the decorator just next to the endpoint declaration, to make the code easily readable.

  • added 1 commit

    • 7eb9c770 - Apply 1 suggestion(s) to 1 file(s)

    Compare with previous version

  • Why a Generic class and not a RawDecorator ?

    RawDecorator is a trait that extends Decorator[Response.Raw, Response.Raw, Any] which doesn't exactly match Decorator[_, cask.endpoints.WebsocketResult, _] that is a strict requirement for the Decorator (It doesn't compile).
    So the only way i found was to make a generic class. I think for most of the HTTP communications a RawDecorator will work. But for the specific websocket function it's a different return type !

    DevOps Side

    Now from a Devops perspective imagine we make it shorter and inside the class WebServerRoutes class it would make the decorator specific to the websocket function which will make it less maintainable. Personally I think centralizing all the security related functions in a single object/folder, would be a good starting point to ensure :

    • Separation of concerns
    • Maintainability
    • Reusability
    • Single source of trust for sensitive functions !

    We can also stop using cask decorators and try to find other solutions !

    Let me know your preference, and I’ll refactor accordingly !

    Edited by François Henri Théron
  • I think Cask uses implicit to lift between different response types, so I'd be inclined to write something like this:

      extension (request: cask.Request)
        def hasAcceptableOriginHeader: Boolean =
          // request.headers.get("sec-fetch-site").exists(_.contains("same-origin")) // Not supported in Safari
          request.headers.get("host").flatMap(_.headOption).exists: host =>
            request.headers.get("origin").flatMap(_.headOption).exists: origin =>
              origin == f"http://$host" || origin == f"https://$host"
    
      class withOriginValidation[T](implicit ofResponse: cask.model.Response[String] => T) extends cask.router.Decorator[T, T, Any]:
        override def wrapFunction(request: cask.Request, delegate: Delegate) =
          if request.hasAcceptableOriginHeader then
            delegate(Map())
          else
            cask.router.Result.Success:
              ofResponse(cask.Response(f"Invalid or missing 'Origin' header", 403))

    Which I think you should be able to use as @withOriginValidation[cask.WebsocketResult]() on the def websocket endpoint, because WebSocketResult defines an implicit class Response whose constructor will match the implicit ofResponse argument?

    I'd wait to create the decorators file until we have more than one of them (there's a cost to splitting things across files)

  • added 1 commit

    • cfcd863f - server: Refactor websocket decorator implementation

    Compare with previous version

  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Please register or sign in to reply
    Loading