Closed davidperrenoud closed 2 years ago
I think this originated from parts of the reference implementation source (prior to the formal spec being written to clarify), where array<string, string>
was used e.g. https://github.com/paragonie/paseto/blob/52c094dd4f6689fa5ba5352dc3d87c0f31955f9a/src/JsonToken.php#L189-L199
You're correct about PASETO spec allowing nested structures though, the spec even gives one as an example: {"foo":"bar","baz":12345,"678":["a","b","c"]}
(with the restriction that the top layer must be a dictionary).
Likely the simplest fix is to do something like change: [String: String]
to [String: Any]
, though probably nicer to work with (and certainly more specific than Any
) would be to implement something like the JSON
enum given as an example in SE-0195, probably also utilising @dynamicMemberLookup
. To my knowledge there's no type in the standard library that was ever added to already implement this use-case for working with JSON, unless you know of anything?
I'm taking a look at fixing this.
My initial recent thoughts were that I could have Token
accept a generic T: Codable
that could be used. Unfortunately I think this isn't really workable, particularly with regard to adding "claims" to an already built token (i.e. if claims is T: Codable
, how to construct a new type S
at runtime such that S
has all the properties of T
plus a new claim key). If both types were defined it might be possible to write a mapper between the two types (which adds data to S
upon creation), but this seems like it might be difficult to use in practice.
For adding of validator support, this would also make it hard to vet the standardised claims if a custom T
is supplied (especially given that Codable
s can decode with different key names than the original JSON had). I think the only sane way to vet claims would be to hang onto the original JSON string and decode many times depending on which T
was passed (again, this seems kind of non-ideal).
I'd like to avoid [String: Any]
, so my first go will be to try implementing this using an extended version of the JSON type from SE-0195.
My initial first go at this involves introducing a new type, simplified form:
public enum JSON {
@dynamicMemberLookup
public enum Container: Equatable {
case Array([Value])
case Dictionary([String: Value])
public subscript(dynamicMember member: String) -> Value? {
if case .Dictionary(let dict) = self {
return dict[member]
}
return nil
}
}
/**
* JSON.Value is based on the types that may be returned from JSONSerialization.
*
* Apple's documentation notably omits the boolean case, and does not distinguish numbers by type.
*/
public enum Value: Equatable {
case Container(Container)
case String(String)
case Number(NSNumber)
case Null
}
}
JSON.Value
and JSON.Container
then implement the various ExpressibleBy*Literal
protocols so that values can be constructed without much hassle.
The claims
property in Token
would then be changed from [String: String]
to [String: JSON.Value]
, which ensures that the top level is still a dictionary (as required by Paseto spec), but then allows JSON values to be nested.
Still a bit of a WIP, I've extracted the JSON implementation out into its own repo: https://github.com/aidantwoods/TypedJSON
This isn't in a release version yet, but closing this now as it'll be in 1.0
I tried decoding a public token corresponding to the following nested data structure:
But this results in
Message.verify
throwing an exception, as the decoder tries to map all tokens to[String: String]
. I do not see anything in the PASETO spec discouraging these kinds of structures. Do you plan on supporting them?