# jwt_full/jwt.nim # Copyright © 2021 Jonathan Bernard ## =============================== ## jwt ## =============================== ## ## ------------------------------- ## JSON Web Token (JWT) - RFC 7519 ## ------------------------------- ## ## import std/options, std/strutils, std/times import ./claims, ./joseheader, ./jwa, ./jwe, ./jwk, ./jws, ./jwssig export claims type InvalidToken* = object of CatchableError JwtKind = enum jkJWE, jkJWS JWT* = object claims: JwtClaims case kind: JwtKind of jkJWE: jwe: JWE of jkJWS: jws: JWS # Public read-only accessors to JWT members # ----------------------------------------- func claims*(jwt: JWT): JwtClaims = jwt.claims func header*(jwt: JWT): JoseHeader = case jwt.kind: of jkJWS: return jwt.jws[0].protected of jkJWE: return jwt.jwe.header func `$`*(jwt: JWT): string = case jwt.kind: of jkJWS: result = jwt.header.rawB64 & "." & jwt.claims.rawB64 & "." if jwt.jws.len > 0: result &= jwt.jws[0].signatureB64 of jkJWE: result = jwt.header.rawB64 & "." & jwt.claims.rawB64 & "." proc initJWT*(encoded: string): JWT = let parts = encoded.split('.') if parts.len == 3: let jws = initJWS(encoded) result = JWT( kind: jkJWS, claims: initJwtClaims(jws.payloadB64), jws: jws) else: # TODO raise newException(Exception, "not yet implemented") proc toJWT*(encoded: string): JWT = initJWT(encoded) proc createSignedJwt*( header: JoseHeader, claims: JwtClaims, key: JWK | string ): JWT = ## Create a new JWT with the given header, claims, and signed with the given ## key. ## ## *header* can be easily created with `initJoseHeader <>`_. ## *claims* can be easily created with `initClaims <>`_. ## *key* is expected to be either a `JWK <>`_ or a string. If a string is ## supplied, it is expected to be the binary value of the key when using ## HSxxx algorithms, and a PEM representing the private key for RSxxx and ## ECxxx algorithms. result = JWT( kind: jkJWS, claims: claims, jws: sign(claims, header, key)) proc validateTimeClaims*(jwt: JWT) = let now = now() if jwt.claims.exp.isSome and now > jwt.claims.exp.get: raise newException(InvalidToken, "Token is expired.") if jwt.claims.nbf.isSome and now < jwt.claims.nbf.get: raise newException(InvalidToken, "Token cannot be used yet.") proc validate*( jwt: JWT, sigAlg: JwtAlgorithm, key: JWK | string, validateTimeClaims = true ) = case jwt.kind: of jkJWS: jwt.jws.validate(sigAlg, key) of jkJWE: raise newException(Exception, "JWE validation is not yet implemented") if validateTimeClaims: jwt.validateTimeClaims proc tryValidate*( jwt: JWT, sigAlg: JwtAlgorithm, key: JWK | string, validateTimeClaims = true ): bool = try: jwt.validate(sigAlg, key) return true except: return false ## proc verify*(jwt: JWT, secret: string, alg: SignatureAlgorithm): bool = ## tryValidate(jwt, sigAlg, secret) # proc sign*(jwt: JWT, key: JWK): JWT =