Update version of namespaced_logging.
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| # Package | # Package | ||||||
|  |  | ||||||
| version       = "0.2.1" | version       = "0.2.2" | ||||||
| author        = "Jonathan Bernard" | author        = "Jonathan Bernard" | ||||||
| description   = "JDB Software's opinionated extensions and auth layer for Jester." | description   = "JDB Software's opinionated extensions and auth layer for Jester." | ||||||
| license       = "MIT" | license       = "MIT" | ||||||
| @@ -12,8 +12,8 @@ srcDir        = "src" | |||||||
| requires "nim >= 1.6.2" | requires "nim >= 1.6.2" | ||||||
| requires @["bcrypt", "jester >= 0.5.0", "uuids"] | requires @["bcrypt", "jester >= 0.5.0", "uuids"] | ||||||
|  |  | ||||||
| requires "https://git.jdb-software.com/jdb/nim-jwt-full.git" | requires "https://git.jdb-software.com/jdb/nim-jwt-full.git >= 0.2.0" | ||||||
| requires "https://git.jdb-software.com/jdb/nim-namespaced-logging.git" | requires "https://git.jdb-software.com/jdb/nim-namespaced-logging.git >= 0.3.0" | ||||||
|  |  | ||||||
| task unittest, "Runs the unit test suite.": | task unittest, "Runs the unit test suite.": | ||||||
|   exec "nim c -r test/runner" |   exec "nim c -r test/runner" | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ const CONTENT_TYPE_JSON* = "application/json" | |||||||
| var logNs {.threadvar.}: LoggingNamespace | var logNs {.threadvar.}: LoggingNamespace | ||||||
|  |  | ||||||
| template log(): untyped = | template log(): untyped = | ||||||
|   if logNs.isNil: logNs = initLoggingNamespace("buffoonery/apiutils", lvlDebug) |   if logNs.isNil: logNs = getLoggerForNamespace("buffoonery/apiutils", lvlDebug) | ||||||
|   logNs |   logNs | ||||||
|  |  | ||||||
| ## Response Utilities | ## Response Utilities | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ type | |||||||
| var logNs {.threadvar.}: LoggingNamespace | var logNs {.threadvar.}: LoggingNamespace | ||||||
|  |  | ||||||
| template log(): untyped = | template log(): untyped = | ||||||
|   if logNs.isNil: logNs = initLoggingNamespace("buffoonery/auth", lvlDebug) |   if logNs.isNil: logNs = getLoggerForNamespace("buffoonery/auth", lvlDebug) | ||||||
|   logNs |   logNs | ||||||
|  |  | ||||||
| proc failAuth*(reason: string, parentException: ref Exception = nil) = | proc failAuth*(reason: string, parentException: ref Exception = nil) = | ||||||
| @@ -91,6 +91,11 @@ proc addSigningKeys*(ctx: ApiAuthContext, issuer: string, keySet: JwkSet): void | |||||||
| proc findSigningKey*(ctx: ApiAuthContext, jwt: JWT, allowFetch = true): JWK {.gcsafe.} = | proc findSigningKey*(ctx: ApiAuthContext, jwt: JWT, allowFetch = true): JWK {.gcsafe.} = | ||||||
|   ## Lookup the signing key for a given JWT. This method assumes that you trust |   ## Lookup the signing key for a given JWT. This method assumes that you trust | ||||||
|   ## the issuer named in the JWT. |   ## the issuer named in the JWT. | ||||||
|  |   ## | ||||||
|  |   ## If our ApiAuthContext does not contain any keys for the issuer named in | ||||||
|  |   ## the JWT, or if that set of keys does not contain the key id referenced in | ||||||
|  |   ## the JWT, we will attempt to fetch the signing keys based on the | ||||||
|  |   ## [OpenID Connect standard discovery mechanism](https://openid.net/specs/openid-connect-discovery-1_0.html) | ||||||
|  |  | ||||||
|   try: |   try: | ||||||
|     if jwt.claims.iss.isNone: failAuth "Missing 'iss' claim." |     if jwt.claims.iss.isNone: failAuth "Missing 'iss' claim." | ||||||
| @@ -100,23 +105,20 @@ proc findSigningKey*(ctx: ApiAuthContext, jwt: JWT, allowFetch = true): JWK {.gc | |||||||
|  |  | ||||||
|     let jwtIssuer = jwt.claims.iss.get |     let jwtIssuer = jwt.claims.iss.get | ||||||
|  |  | ||||||
|     # Do we already have keys for this issuer in our cache? |  | ||||||
|     if ctx.issuerKeys.hasKey(jwtIssuer): |     if ctx.issuerKeys.hasKey(jwtIssuer): | ||||||
|       # Do we have the key for this keyId? |  | ||||||
|       let foundKeys = ctx.issuerKeys[jwtIssuer] |       let foundKeys = ctx.issuerKeys[jwtIssuer] | ||||||
|         .filterIt(it.kid.isSome and it.kid.get == jwt.header.kid.get) |         .filterIt(it.kid.isSome and it.kid.get == jwt.header.kid.get) | ||||||
|  |  | ||||||
|       if foundKeys.len == 1: return foundKeys[0] |       if foundKeys.len == 1: return foundKeys[0] | ||||||
|  |  | ||||||
|     # If all of the above were true, we should have returned. If we reach this |     # If we didn't have keys for that issuer, or if we couldn't find the given | ||||||
|     # point, we know that one of the above was false and we need to refresh our |     # kid, we need to refresh our cache of keys. | ||||||
|     # cache of keys. |  | ||||||
|     if allowFetch: |     if allowFetch: | ||||||
|       ctx.issuerKeys[jwtIssuer] = |       ctx.issuerKeys[jwtIssuer] = | ||||||
|         fetchJWKs(jwtIssuer & "/.well-known/openid-configuration") |         fetchJWKs(jwtIssuer & "/.well-known/openid-configuration") | ||||||
|       return ctx.findSigningKey(jwt, false) |       return ctx.findSigningKey(jwt, false) | ||||||
|  |  | ||||||
|     else: failAuth "unable to find JWT signing key" |     failAuth "unable to find JWT signing key" | ||||||
|  |  | ||||||
|   except: |   except: | ||||||
|     log.error "unable to find JWT signing key: " & getCurrentExceptionMsg() |     log.error "unable to find JWT signing key: " & getCurrentExceptionMsg() | ||||||
| @@ -165,24 +167,22 @@ proc extractValidJwt*(ctx: ApiAuthContext, req: Request): JWT = | |||||||
|   ## We support two authentication flows: |   ## We support two authentication flows: | ||||||
|   ## |   ## | ||||||
|   ## - Strict API via a JWT Bearer token in the Authorization header. This is |   ## - Strict API via a JWT Bearer token in the Authorization header. This is | ||||||
|   ##   intended for API consumers (not the browser-based web-app). In this |   ##   intended for API consumers (not a browser-based web-app). In this case, | ||||||
|   ##   case, the token is validated directly. |   ##   the token is validated directly. | ||||||
|   ## |   ## | ||||||
|   ## - Split JWT via two cookies: |   ## - Split JWT via two cookies: | ||||||
|   ## |   ## | ||||||
|   ##   - `${cookiePrefix}-user`: Contains the JWT header and payload, but not the |   ##   - `${cookiePrefix}-user`: Contains the JWT header and payload, but not the | ||||||
|   ##      signature.  This cookie is set Secure. The JWT payload contains a 30 |   ##      signature.  This cookie should be set Secure. The JWT payload should | ||||||
|   ##      minute expiry (and the Max-Age is set the same) and also contains a |   ##      have a defined expiration date (matching the Max-Age of the cookie) | ||||||
|   ##      CSRF token. This cookie is accessible by the web application. |   ##      and a CSRF token. This cookie is accessible by the web application. | ||||||
|  |   ## | ||||||
|   ##   - `${cookiePrefix}-session`: Contains the JWT signature. This cookie is |   ##   - `${cookiePrefix}-session`: Contains the JWT signature. This cookie is | ||||||
|   ##      set Secure and HttpOnly. This serves as the session token (when the |   ##      set Secure and HttpOnly. This serves as the session token (when the | ||||||
|   ##      user closes the browser this gets unset). |   ##      user closes the browser this gets unset). | ||||||
|   ## |   ## | ||||||
|   ##   In this split-cookie mode, the API will also check for the presence of a |   ##   In the split-cookie mode we also check that the `csrfToken` claim in the | ||||||
|   ##   CSRF token on any mutation requests (PUT, POST, and DELETE requests). |   ##   JWT payload matches the CSRF value passed via the `X-CSRF-TOKEN` header. | ||||||
|   ##   The client must set the X-CSRF-TOKEN header with the same CSRF value |  | ||||||
|   ##   present in the `csrfToken` claim in the JWT presented in the |  | ||||||
|   ##   `${cookiePrefix}-user` cookie. |  | ||||||
|  |  | ||||||
|   try: |   try: | ||||||
|     if headers(req).hasKey("Authorization"): |     if headers(req).hasKey("Authorization"): | ||||||
| @@ -238,6 +238,7 @@ proc createSignedJWT*(ctx: ApiAuthContext, claims: JsonNode, kid: string): JWT = | |||||||
|     sigKey) |     sigKey) | ||||||
|  |  | ||||||
| proc newApiAccessToken*(ctx: ApiAuthContext, sub: string, duration = 1.hours): JWT = | proc newApiAccessToken*(ctx: ApiAuthContext, sub: string, duration = 1.hours): JWT = | ||||||
|  |   ## Create a new JWT for API access. | ||||||
|   result = ctx.createSignedJWT( |   result = ctx.createSignedJWT( | ||||||
|     %*{ |     %*{ | ||||||
|       "sub": sub, |       "sub": sub, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user