defmodule Magic.Token do @moduledoc """ Provides methods to interact with the DID Token. """ @nbf_grace_period 300 alias Magic.Utils alias Magic.DIDTokenError @doc """ Description: Method validate did_token Arguments: did_token: A DID Token generated by a Magic user on the client-side. Returns: true or raise an error """ def validate!(did_token) do time = DateTime.utc_now() |> DateTime.to_unix() %{proof: proof, claim: claim, message: message} = decode!(did_token) rec_address = recover_address(proof, message) validate_public_address!(rec_address, did_token) validate_claim_fields!(claim) validate_claim_ext!(time, claim["ext"]) validate_claim_nbf!(time, claim["nbf"]) true end @doc """ Description: Method Decodes a DID Token from a Base64 string into a tuple of its individual components: proof and claim. This method allows you decode the DID Token and inspect the token Arguments: did_token: A DID Token generated by a Magic user on the client-side. Returns: A map containing proof, claim and message or raise an error """ def decode!(did_token) do try do [proof, message] = Base.decode64!(did_token) |> Jason.decode!() claim = Jason.decode!(message) validate_claim_fields!(claim) %{proof: proof, claim: claim, message: message} rescue ArgumentError -> raise DIDTokenError, message: "DID Token is malformed" Jason.DecodeError -> raise DIDTokenError, message: "DID Token is malformed" end end @doc """ Description: Method parse public_address and extract issuer Arguments: public_address: Cryptographic public address of the Magic User. Returns: issuer info """ def construct_issuer_with_public_address(public_address) do "did:ethr:#{public_address}" end @doc """ Description: Method parse did_token and extract issuer Arguments: did_token: A DID Token generated by a Magic user on the client-side. Returns: issuer info """ def get_issuer(did_token) do %{claim: claim} = decode!(did_token) claim["iss"] end @doc """ Description: Method parse did_token and extract cryptographic public_address Arguments: did_token: A DID Token generated by a Magic user on the client-side. Returns: cryptographic public address of the Magic User who generated the supplied DID Token. """ def get_public_address(did_token) do get_issuer(did_token) |> String.split(":") |> List.last() end defp recover_address(proof, message) do Utils.recover_pubkey(message, proof) |> Utils.pubkey_to_address() end defp claim_fields() do ~w(iat ext iss sub aud nbf tid) end defp validate_claim_fields!(claim) do missing_fields = claim_fields() -- Map.keys(claim) if length(missing_fields) > 0 do raise DIDTokenError, message: "DID Token missing required fields: #{Enum.join(missing_fields, ", ")}" end end defp validate_public_address!(rec_address, did_token) do if Utils.hex_to_bin(rec_address) != Utils.hex_to_bin(get_public_address(did_token)) do message = "Signature mismatch between 'proof' and 'claim'." raise DIDTokenError, message: message end end defp validate_claim_ext!(time, claim_ext) do if time > claim_ext do message = "Given DID token has expired. Please generate a new one." raise DIDTokenError, message: message end end defp apply_nbf_grace_period(claim_nbf) do claim_nbf - @nbf_grace_period end defp validate_claim_nbf!(time, claim_nbf) do if time < apply_nbf_grace_period(claim_nbf) do message = "Given DID token cannot be used at this time." raise DIDTokenError, message: message end end end