softwaremill / tapir

Rapid development of self-documenting APIs
https://tapir.softwaremill.com
Apache License 2.0
1.36k stars 414 forks source link

Let serverSecurityLogic access path values #3613

Open geirolz opened 7 months ago

geirolz commented 7 months ago

Given this code I have some doubts

// user/{id}
val getUser = endpoint
   .securityIn(sttp.tapir.auth.bearer[AuthEncodedToken]())
   .in("user")
   .in(path[UserId]("id"))

val ep = getUser
   .serverSecurityLogic(token => decodeAndValidateToken(token))
   .serverLogic(token => userId => canAccessOrForbidden(token, userId) { ... })

The problem is that with this design within serverSecurityLogic i can just decode the token, if I want to check that the token has the rights to access that user I have to move this logic in serverLogic because I don't have access to the UserId. This force to have the security logic in two places.

It would be great to have access to the endpoint path values within serverSecurityLogic in order to have the security checks all in one place.

Am I not seeing it right ? Is there a solution ?

adamw commented 7 months ago

I think the solution is to simply make all path components a .securityIn. The inputs differ in the order of decoding: first all security inputs are decoded, the security logic is run, and then the rest of the inputs. See the note here: https://tapir.softwaremill.com/en/latest/endpoint/security.html#authentication-inputs

So you'd have:

val getUser = endpoint
   .securityIn(sttp.tapir.auth.bearer[AuthEncodedToken]())
   .securityIn("user")
   .securityIn(path[UserId]("id"))
geirolz commented 7 months ago

Thanks @adamw for the quick reply. Do you this as a workaround or a definitive solution ? Because in this way then in serverLogic I won't have access to UserId

adamw commented 7 months ago

Yes, then whatever security logic returns is available to the server logic - this might include any "raw" values from the inputs that are needed by the security logic to be also consumed by the server logic.