lib/kinde_sdk/sdk/utils.ex

defmodule KindeSDK.SDK.Utils do
  @length 32

  defp base64_url_encode(string) do
    string
    |> Base.url_encode64()
    |> String.trim()
    |> String.replace("=", "")
    |> String.replace("+", "-")
    |> String.replace("/", "_")
  end

  @spec random_string(any) :: binary
  def random_string(length \\ @length) do
    :crypto.strong_rand_bytes(length)
    |> Base.encode16()
    |> base64_url_encode()
  end

  @spec generate_challenge :: %{code_challenge: binary, code_verifier: binary, state: binary}
  def generate_challenge do
    state = random_string()
    code_verifier = random_string()
    code_challenge = :crypto.hash(:sha256, code_verifier) |> Base.url_encode64(padding: false)

    %{
      state: state,
      code_verifier: code_verifier,
      code_challenge: code_challenge
    }
  end

  @spec validate_url(binary) :: boolean
  def validate_url(url) do
    Regex.match?(
      ~r/https?:\/\/(?:w{1,3}\.)?[^\s.]+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))/,
      url
    )
  end

  @spec parse_jwt(nil | binary) :: any
  def parse_jwt(nil), do: nil

  def parse_jwt(token) do
    String.split(token, ".")
    |> Enum.at(1)
    |> String.replace("-", "+")
    |> String.replace("_", "/")
    |> Base.decode64!(padding: false)
    |> Jason.decode!()
  end

  @additional_param_keys [:audience, :org_code, :org_name, :is_create_org]

  @spec check_additional_params(map) :: map
  def check_additional_params(params) when params == %{}, do: %{}

  def check_additional_params(params) do
    keys = Map.keys(params)

    for key <- keys do
      if !(key in @additional_param_keys) do
        throw("Please provide correct additional, #{key}")
      end

      if !is_binary(Map.get(params, key)) do
        throw("Please supply a valid #{key}. Expected: string")
      end
    end

    params
  end

  @spec add_additional_params(map, map) :: map
  def add_additional_params(target, additional_params) do
    additional_params
    |> check_additional_params()
    |> Map.merge(target)
  end

  @spec get_current_app_version :: binary
  def get_current_app_version() do
    Mix.Project.config()[:version]
    |> String.replace("~>", "")
    |> String.trim_leading()
  end

  def calculate_expiring_timestamp(login_timestamp, expiring_in) do
    {:ok, expiring_at} =
      login_timestamp
      |> DateTime.to_unix()
      |> Kernel.+(expiring_in)
      |> DateTime.from_unix()

    expiring_at
  end
end