Current section

Files

Jump to
maat_feather lib maat_feather auth token.ex
Raw

lib/maat_feather/auth/token.ex

defmodule MaatFeather.Auth.Token do
@moduledoc """
## Token
```elixir
# config/config.exs
config :maat_feather, MaatFeather.Auth.Token,
alg: System.get_env("TOKEN_ALG", "RS256"),
exp:
if(System.get_env("TOKEN_EXP") != nil,
do: String.to_integer(System.get_env("TOKEN_EXP")),
else: :timer.minutes(15)
),
iss: System.get_env("AUTH_ISS", "maat_feather")
```
"""
alias MaatFeather.Users
alias MaatFeather.Users.User
alias MaatFeather.Auth.KeyServer
defp env(), do: Application.fetch_env!(:maat_feather, __MODULE__)
defp put_iss(config) do
iss = Keyword.fetch!(env(), :iss)
Joken.Config.add_claim(
config,
"iss",
fn -> iss end,
fn claim_val, _claims, _context -> claim_val == iss end
)
end
defp put_typ(config, typ) do
Joken.Config.add_claim(
config,
"typ",
fn -> typ end,
fn claim_val, _claims, _context -> claim_val == typ end
)
end
defp put_aud(config) do
Joken.Config.add_claim(config, "aud", nil, fn _claim_val, claims, _context ->
case claims["role"] do
"admin" ->
[
"maat_feather.delete",
"maat_feather.update",
"maat_feather.create",
"maat_feather.read"
]
"user" ->
[
"maat_feather.read"
]
_ ->
[]
end
end)
end
defp put_exp(config) do
exp = Keyword.fetch!(env(), :exp)
Joken.Config.add_claim(
config,
"exp",
fn -> Joken.current_time() + exp end,
fn claim_val, _claims, _context ->
claim_val > Joken.current_time()
end
)
end
defp put_kid(config) do
Joken.Config.add_claim(config, "kid", fn ->
{kid, _key_pem} = KeyServer.last_key!()
kid
end)
end
defp config() do
Map.new()
|> put_iss()
|> put_aud()
|> put_exp()
|> put_kid()
end
def signer(kid) do
alg = Keyword.fetch!(env(), :alg)
Joken.Signer.create(alg, %{"pem" => KeyServer.fetch_key!(kid)})
end
defp signer() do
alg = Keyword.fetch!(env(), :alg)
%{generate: kid} = Map.get(config(), "kid")
Joken.Signer.create(alg, %{"pem" => KeyServer.fetch_key!(kid.())})
end
@doc """
This function encodes a user into a token.
## Parameters
- `user`: the user to encode
## Examples
```elixir
iex> {:ok, token, claims} = MaatFeather.Auth.Token.encode(user)
{:ok,
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjAwNzMxNzcsImlzcyI6Im1hYXRfZmVhdGhlciIsImtpZCI6IkQ1NTVBMDE0OTdBRkI3NkYiLCJzdWIiOiIyRDJEMkQyRDJENDI0NTQ3NDk0RTIwNTA1NTQyNEM0OTQzMjA0QjQ1NTkyRDJEMkQyRDJEMEE0RDQ5NDc2NjRENDEzMDQ3NDM1MzcxNDc1MzQ5NjIzMzQ0NTE0NTQyNDE1MTU1NDE0MTM0NDc0RTQxNDQ0MzQyNjk1MTRCNDI2NzUxNDM2RjM2NUEzNTU3NDE0MTM2MzY2NjcyNkQ0MTRENTQzODdBNEY2RTRBNDg2QzU0MzA0MTBBNTA2NjU0Nzc3ODM1NTEzOTQ4NEY0RDRDNzM1NTU0NzI2NTY2NDM2MjJCNzc3MjY0MzU0ODU1NEIzMTQyNEYzMjRBNzY2MjZENzMzMjY5NzQ2QTZENjc2ODY5NzQ1ODU2NkE0MTU5NDIzMjcyNDkyQjM2Mzc2NzZENTU3NjMyNTIwQTQ5NzA2NjcyNDU1ODM5NTE2NTZBMzM2Rjc4NDY1OTU4NEU0MjU1NjY2NTQ2Nzg1OTM2NjE1MTMzNzE2NTc2NkE3NDU3NjE1QTUxNjQ1Nzc5NkM0MTJCN0E2RjVBNTMzOTMxNEM0MjZFNTY2RDcyNEM2MjY2NkEzNjM3NDM3MDUzMEEzNTZGNDI0QjUxNEU2QjZENjE2NzUzMzM1MjZCNzY3OTQ1NTE0OTQ0NDE1MTQxNDIwQTJEMkQyRDJEMkQ0NTRFNDQyMDUwNTU0MjRDNDk0MzIwNEI0NTU5MkQyRDJEMkQyRDBBMEEifQ.L5yGWXAApVFVIcm-Vijqt1LvOGYO9dBb4nqM15VJe-Al91IvXPZlwb15EK5QXujbCo2-S_gLOL9eBiId4ogfNNqavSO82wHUyTCh6akhTT4QYMEYrQgvyYuKW78DmAajhXH1R1NY9IG-JnWzNlEyrSeVZf3nOuPyriWkQ7kTbDY",
%{
"exp" => 1660073177,
"iss" => "maat_feather",
"kid" => "D555A01497AFB76F",
"sub" => "2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B426751436F365A35574141363666726D414D54387A4F6E4A486C5430410A5066547778355139484F4D4C73555472656643622B7772643548554B31424F324A76626D733269746A6D6768697458566A4159423272492B3637676D557632520A4970667245583951656A336F784659584E42556665467859366151337165766A7457615A516457796C412B7A6F5A5339314C426E566D724C62666A36374370530A356F424B514E6B6D61675333526B767945514944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D0A0A"
}}
```
"""
@spec encode(%User{}) :: {:ok, binary(), map()} | {:error, binary()}
def encode(%User{address: address}, typ \\ "access") do
Joken.generate_and_sign(put_typ(config(), typ), %{"sub" => address}, signer())
end
@doc """
This function decodes a token into a user.
## Parameters
- `token`: the token to decode
## Examples
```elixir
iex> {:ok, claims} = MaatFeather.Auth.Token.decode(token)
{:ok,
%{
"exp" => 1660962870,
"iss" => "maat_feather",
"kid" => "A6FF4B0E88C95C0C",
"sub" => "2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B42675143377138674E625069734F317332677A2F34616639626F4164630A6A314177614B71763932454F7364324C36615674415630326B6642437643504F61614B4C6B58546838314B483759322B704951532B764A2B475267565573576D0A504D6965344966766E575A52506E4F594E355A63536C5A4F4E2F39326E6F4C6F72633471303958327774526C4D685966783976337146684E58346E48335457470A377041464F3641705836646A4F5A376564774944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D0A0A"
}}
```
"""
@spec decode(binary()) :: {:ok, map()} | {:error, charlist()}
def decode(token) do
{:ok, %{"kid" => kid}} = Joken.peek_claims(token)
Joken.verify_and_validate(config(), token, signer(kid))
end
@doc """
This function refresh a token.
## Parameters
- `token`: the token to refresh
## Examples
```elixir
iex> {:ok, {old_token, old_claims}, {new_token, new_claims}} = MaatFeather.Auth.Token.refresh(access_token)
{:ok,
{"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjA5NzU4MjYsImlzcyI6Im1hYXRfZmVhdGhlciIsImtpZCI6IjE3MzJGOEI2N0Q3QjUxMjgiLCJzdWIiOiIyRDJEMkQyRDJENDI0NTQ3NDk0RTIwNTA1NTQyNEM0OTQzMjA0QjQ1NTkyRDJEMkQyRDJEMEE0RDQ5NDc2NjRENDEzMDQ3NDM1MzcxNDc1MzQ5NjIzMzQ0NTE0NTQyNDE1MTU1NDE0MTM0NDc0RTQxNDQ0MzQyNjk1MTRCNDI2NzUxNDMzMzZDMzM0RjRENkQ0MjU1NDQ1QTU3MkY1NzcyNkQzMDY3NkUzNzcwNEU2RjZGNDgzMTBBMzg2RjYzNzU2RTdBN0EzNTY2NjE3MzVBNzIzNTZFMzg2NTMzMkY0RTUyNTI2NzRCNTI1NTM4MzU0OTM5MkY0MTdBNTY0QzQxNEU0NDczNEI1NDMxNzQzMTY2NUE2ODQ2NTA2ODM3NkE2OTRENTI1MTU4NDkzNTMyMzYzNjUyNjkwQTMzNkM0ODM3NDMzNjQ2NTA3NDcwNkUyQjc1NkU0QzQyNEM3NzdBMzI1ODcyMzY0QjM0MkYzODcwNkE2ODQ5NzA3QTMyNDY3ODRDNEI0RjMxNTQ2ODM3NDE0OTZCNTU0OTQ1MzE2OTY5MzMzMjU2NkU0RjQ2NTM1ODU2MzQ2RTYxMEE3NTQ3Njk0Mjc5NkY0NzJGMzE3NDcyNzE1NDUxNjgzMzZENzc0OTQ0NDE1MTQxNDIwQTJEMkQyRDJEMkQ0NTRFNDQyMDUwNTU0MjRDNDk0MzIwNEI0NTU5MkQyRDJEMkQyRDBBMEEifQ.mRhm2oX_wiKf7K03pax16g6HhvHml5ZgN_X21bbtmr7qiyfPMTMD85kUZbIZzlhKxc1dJKuelbSKs_sKCD2G1GbVIthCwc2FU1httUXxheM4rrMtm8JifY58oRsYM-dyBvhK2B1I4gpWej7lsLV_OYVwsWAwbTeYEwxFN1ML11k",
%{
"exp" => 1660975826,
"iss" => "maat_feather",
"kid" => "1732F8B67D7B5128",
"sub" => "2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B42675143336C334F4D6D4255445A572F57726D30676E37704E6F6F48310A386F63756E7A7A356661735A72356E3865332F4E5252674B5255383549392F417A564C414E44734B54317431665A68465068376A694D525158493532363652690A336C48374336465074706E2B756E4C424C777A325872364B342F38706A6849707A3246784C4B4F3154683741496B5549453169693332566E4F46535856346E610A75476942796F472F31747271545168336D774944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D0A0A"
}},
{"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjA5NzU4NTMsImlzcyI6Im1hYXRfZmVhdGhlciIsImtpZCI6IjE3MzJGOEI2N0Q3QjUxMjgiLCJzdWIiOiIyRDJEMkQyRDJENDI0NTQ3NDk0RTIwNTA1NTQyNEM0OTQzMjA0QjQ1NTkyRDJEMkQyRDJEMEE0RDQ5NDc2NjRENDEzMDQ3NDM1MzcxNDc1MzQ5NjIzMzQ0NTE0NTQyNDE1MTU1NDE0MTM0NDc0RTQxNDQ0MzQyNjk1MTRCNDI2NzUxNDMzMzZDMzM0RjRENkQ0MjU1NDQ1QTU3MkY1NzcyNkQzMDY3NkUzNzcwNEU2RjZGNDgzMTBBMzg2RjYzNzU2RTdBN0EzNTY2NjE3MzVBNzIzNTZFMzg2NTMzMkY0RTUyNTI2NzRCNTI1NTM4MzU0OTM5MkY0MTdBNTY0QzQxNEU0NDczNEI1NDMxNzQzMTY2NUE2ODQ2NTA2ODM3NkE2OTRENTI1MTU4NDkzNTMyMzYzNjUyNjkwQTMzNkM0ODM3NDMzNjQ2NTA3NDcwNkUyQjc1NkU0QzQyNEM3NzdBMzI1ODcyMzY0QjM0MkYzODcwNkE2ODQ5NzA3QTMyNDY3ODRDNEI0RjMxNTQ2ODM3NDE0OTZCNTU0OTQ1MzE2OTY5MzMzMjU2NkU0RjQ2NTM1ODU2MzQ2RTYxMEE3NTQ3Njk0Mjc5NkY0NzJGMzE3NDcyNzE1NDUxNjgzMzZENzc0OTQ0NDE1MTQxNDIwQTJEMkQyRDJEMkQ0NTRFNDQyMDUwNTU0MjRDNDk0MzIwNEI0NTU5MkQyRDJEMkQyRDBBMEEifQ.J1hxlZ5liJ1GjgeQ3hnAHRTtlTTyZoMg9GWsgmelDEyN4TC66E0q9-L7aDFYwqoszBnhgQaoynQhlBGnBlaas2uFTiQgRCrUc18OEARsTfXs67GyMrka60duWrP8BwEeJZS8GT51VMNlZzzL2MKTLvjlQJwq11u_gmBgiy65Wt4",
%{
"exp" => 1660975853,
"iss" => "maat_feather",
"kid" => "1732F8B67D7B5128",
"sub" => "2D2D2D2D2D424547494E205055424C4943204B45592D2D2D2D2D0A4D4947664D413047435371475349623344514542415155414134474E4144434269514B42675143336C334F4D6D4255445A572F57726D30676E37704E6F6F48310A386F63756E7A7A356661735A72356E3865332F4E5252674B5255383549392F417A564C414E44734B54317431665A68465068376A694D525158493532363652690A336C48374336465074706E2B756E4C424C777A325872364B342F38706A6849707A3246784C4B4F3154683741496B5549453169693332566E4F46535856346E610A75476942796F472F31747271545168336D774944415141420A2D2D2D2D2D454E44205055424C4943204B45592D2D2D2D2D0A0A"
}}}
iex> {:error, message} = MaatFeather.Auth.Token.refresh(other_typ_token)
{:error, "invalid token typ"}
```
"""
@spec refresh(binary()) :: {:ok, {binary(), map()}, {binary(), map()}} | {:error, charlist()}
def refresh(old_token) do
{:ok, %{"typ" => typ} = old_claims} = decode(old_token)
if typ == "access" do
user = Users.get_user!(old_claims["sub"])
{:ok, new_token, new_claims} = encode(user, "refresh")
{:ok, {old_token, old_claims}, {new_token, new_claims}}
else
{:error, "invalid token typ"}
end
end
end