Facilitate http code response on websocket JWT authentication failure (#1043)

* Facilitate http code response on websocket JWT authentication failure

* Update JSON-RPC link
This commit is contained in:
Jordan Hrycaj 2022-04-07 10:37:35 +01:00 committed by GitHub
parent 737236fd6e
commit 8af5c33ef9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 27 additions and 75 deletions

View File

@ -190,7 +190,7 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
msg = $(rc.unsafeError) # avoid side effects msg = $(rc.unsafeError) # avoid side effects
quit(QuitFailure) quit(QuitFailure)
# Authentcation handler constructor # Authentcation handler constructor
@[rc.value.jwtAuthAsyHook] some(rc.value.jwtAuthHandler)
# Creating Websocket RPC Server # Creating Websocket RPC Server
if conf.wsEnabled: if conf.wsEnabled:

View File

@ -18,6 +18,7 @@ import
chronicles, chronicles,
chronos, chronos,
chronos/apps/http/httptable, chronos/apps/http/httptable,
json_rpc/servers/websocketserver,
httputils, httputils,
websock/types as ws, websock/types as ws,
nimcrypto/[hmac, utils], nimcrypto/[hmac, utils],
@ -39,19 +40,9 @@ const
32 32
type type
# -- currently unused -- JwtAuthHandler* = ##\
# ## Generic authentication handler, also provided by the web-socket server.
#JwtAuthHandler* = ##\ RpcWebSocketServerAuth
# ## JSW authenticator prototype
# proc(req: HttpTable): Result[void,(HttpCode,string)]
# {.gcsafe, raises: [Defect].}
#
JwtAuthAsyHandler* = ##\
## Asynchroneous JSW authenticator prototype. This is the definition
## appicable for the `verify` entry of a `ws.Hook`.
proc(req: HttpTable): Future[Result[void,string]]
{.closure, gcsafe, raises: [Defect].}
JwtSharedKey* = ##\ JwtSharedKey* = ##\
## Convenience type, needed quite often ## Convenience type, needed quite often
@ -269,68 +260,29 @@ proc jwtSharedSecret*(rng: ref BrHmacDrbgContext; config: NimbusConf):
result = rng.jwtGenSecret.jwtSharedSecret(config) result = rng.jwtGenSecret.jwtSharedSecret(config)
# -- currently unused -- proc jwtAuthHandler*(key: JwtSharedKey): JwtAuthHandler =
# ## Returns a JWT authentication handler that can be used with an HTTP header
#proc jwtAuthHandler*(key: JwtSharedKey): JwtAuthHandler = ## based call back system as the web socket server.
# ## Returns a JWT authentication handler that can be used with an HTTP header
# ## based call back system.
# ##
# ## The argument `key` is captured by the session handler for JWT
# ## authentication. The function `jwtSharedSecret()` provides such a key.
# result = proc(req: HttpTable): Result[void,(HttpCode,string)] =
# let auth = req.getString("Authorization","?")
# if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0:
# return err((Http403, "Missing Token"))
#
# let rc = auth[7..^1].strip.verifyTokenHS256(key)
# if rc.isOk:
# return ok()
#
# debug "Could not authenticate",
# error = rc.error
#
# case rc.error:
# of jwtTokenValidationError, jwtMethodUnsupported:
# return err((Http401, "Unauthorized"))
# else:
# return err((Http403, "Malformed Token"))
#
proc jwtAuthAsyHandler*(key: JwtSharedKey): JwtAuthAsyHandler =
## Returns an asynchroneous JWT authentication handler that can be used with
## an HTTP header based call back system.
## ##
## The argument `key` is captured by the session handler for JWT ## The argument `key` is captured by the session handler for JWT
## authentication. The function `jwtSharedSecret()` provides such a key. ## authentication. The function `jwtSharedSecret()` provides such a key.
result = proc(req: HttpTable): Future[Result[void,string]] {.async.} = result = proc(req: HttpTable): Result[void,(HttpCode,string)] {.gcsafe.} =
let auth = req.getString("Authorization","?") let auth = req.getString("Authorization","?")
if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0: if auth.len < 9 or auth[0..6].cmpIgnoreCase("Bearer ") != 0:
return err("Missing Token") return err((Http403, "Missing Token"))
let rc = auth[7..^1].strip.verifyTokenHS256(key) let rc = auth[7..^1].strip.verifyTokenHS256(key)
if rc.isOk: if rc.isOk:
return ok() return ok()
debug "Could not authenticate", debug "Could not authenticate",
error = rc.error error = rc.error
case rc.error: case rc.error:
of jwtTokenValidationError, jwtMethodUnsupported: of jwtTokenValidationError, jwtMethodUnsupported:
return err("Unauthorized") return err((Http401, "Unauthorized"))
else: else:
return err("Malformed Token") return err((Http403, "Malformed Token"))
proc jwtAuthAsyHook*(key: JwtSharedKey): ws.Hook =
## Variant of `jwtAuthHandler()` (e.g. directly suitable for Json WebSockets.)
##
## Note that currently there is no meaningful way to send a http 401/403 in
## case of an authentication problem.
let handler = key.jwtAuthAsyHandler
ws.Hook(
append: proc(ctx: ws.Hook, req: var HttpTable): Result[void,string] =
ok(),
verify: proc(ctx: ws.Hook, req: HttpTable): Future[Result[void,string]] =
req.handler)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End

View File

@ -224,7 +224,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
secret = fakeKey.fakeGenSecret.jwtSharedSecret(config) secret = fakeKey.fakeGenSecret.jwtSharedSecret(config)
# The wrapper contains the handler function with the captured shared key # The wrapper contains the handler function with the captured shared key
asyHandler = secret.value.jwtAuthAsyHandler handler = secret.value.jwtAuthHandler
suite "EngineAuth: Http/rpc authentication mechanics": suite "EngineAuth: Http/rpc authentication mechanics":
@ -249,7 +249,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
setTraceLevel() setTraceLevel()
# Run http authorisation request # Run http authorisation request
let htCode = waitFor req.asyHandler let htCode = req.handler
noisy.say "***", "result", noisy.say "***", "result",
" htCode=", htCode " htCode=", htCode
@ -268,7 +268,7 @@ proc runJwtAuth(noisy = true; keyFile = jwtKeyFile) =
setTraceLevel() setTraceLevel()
# Run http authorisation request # Run http authorisation request
let htCode = waitFor req.asyHandler let htCode = req.handler
noisy.say "***", "result", noisy.say "***", "result",
" htCode=", htCode " htCode=", htCode
@ -285,7 +285,7 @@ proc jwtAuthMain*(noisy = defined(debug)) =
when isMainModule: when isMainModule:
const const
noisy = defined(debug) or true noisy = defined(debug)
setErrorLevel() setErrorLevel()

2
vendor/nim-json-rpc vendored

@ -1 +1 @@
Subproject commit d4ae2328d4247c59cefd8d5e0fbc3f178a0eb4ef Subproject commit b80313bfed0594198d77c6a22616f1f96c0d91fa