Current section
Files
Jump to
Current section
Files
templates/saas/lib/toolkit/easy_token.ex
defmodule DemoLT.Toolkit.EasyToken do
@moduledoc """
Generates, hashes, verifies token in one place.
Currently, it supports two formats of tokens:
* `:binary`
* `:digit`
"""
@hash_algorithm :sha256
@type format :: {:binary | :digit, keyword()}
@type token :: String.t()
@type token_hash :: binary()
@doc """
Generates a token with a given format.
It returns a 2-element tuple:
* the first element is the token.
* the second element is the hashed token.
## Examples
iex> generate(:binary)
{"BrZOU2e-ysMA9vs2v4juaBUlm05jAwPZfSGPbjiuRA8", <<177, 240, ...>>}
iex> generate(:digit)
{"700823", <<130, 103, ...>>}
"""
@spec generate(format()) :: {token(), token_hash()}
def generate({:binary, opts}) do
rand_size = Keyword.get(opts, :size, 32)
token_bytes = :crypto.strong_rand_bytes(rand_size)
token = Base.url_encode64(token_bytes, padding: false)
token_hash = :crypto.hash(@hash_algorithm, token_bytes)
{token, token_hash}
end
def generate({:digit, opts}) do
size = Keyword.get(opts, :size, 6)
token =
String.duplicate("9", size)
|> String.to_integer()
|> :rand.uniform()
|> Integer.to_string()
|> String.pad_leading(size, "0")
token_hash = :crypto.hash(@hash_algorithm, token)
{token, token_hash}
end
@doc """
Hashes a token without verifying it.
"""
@spec hash(format(), token()) :: {:ok, token_hash()} | :error
def hash({:binary, _opts}, token) do
case Base.url_decode64(token, padding: false) do
{:ok, token_bytes} ->
token_hash = :crypto.hash(@hash_algorithm, token_bytes)
{:ok, token_hash}
_ ->
:error
end
end
def hash({:digit, _opts}, token) do
token_hash = :crypto.hash(@hash_algorithm, token)
{:ok, token_hash}
end
@doc """
Hashes and verifies a token.
"""
@spec verify(format(), token(), token_hash()) :: :ok | :error
def verify({type, _opts} = format, token, token_hash) when type in [:binary, :digit] do
with {:ok, current_token_hash} <- hash(format, token) do
if current_token_hash == token_hash, do: :ok, else: :error
end
end
end