Skip to content

Commit 3958322

Browse files
author
Adrián Quintás
committed
A new hope
0 parents  commit 3958322

File tree

11 files changed

+308
-0
lines changed

11 files changed

+308
-0
lines changed

.formatter.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where 3rd-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
jwt_test_utils-*.tar
24+
25+
# Apple stuff
26+
**/.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Orbit technologies
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# JwtTestUtils
2+
3+
[![Coverage Status](https://coveralls.io/repos/github/heyorbit/elixir-jwt-utils/badge.svg?branch=master)](https://coveralls.io/github/heyorbit/elixir-jwt-utils?branch=master)
4+
[![Hex version](https://img.shields.io/hexpm/v/sippet.svg "Hex version")](https://hex.pm/packages/server_utils)
5+
[![Hex Docs](https://img.shields.io/badge/hex-docs-9768d1.svg)](https://hexdocs.pm/server_utils)
6+
[![Build Status](https://travis-ci.org/heyorbit/elixir-jwt-utils.svg?branch=master)](https://travis-ci.org/heyorbit/elixir-jwt-utils)
7+
[![Deps Status](https://beta.hexfaktor.org/badge/all/github/heyorbit/elixir-jwt-utils.svg)](https://beta.hexfaktor.org/github/heyorbit/elixir-jwt-utils)
8+
9+
# ServerUtils
10+
11+
This project has several module utils to handle common tasks in a server, like authorization params parsing.
12+
13+
Features:
14+
15+
* Phoenix plug to validate JWT header
16+
* Phoenix plug to parse a pagination request
17+
* Simple integer parsing
18+
* Page query params parser
19+
* JWT claims parser
20+
* Wrapped Logger with Sentry integration
21+
22+
## Installation
23+
24+
Add to dependencies
25+
26+
```elixir
27+
def deps do
28+
[{:server_utils, "~> 0.1.3"}]
29+
end
30+
```
31+
32+
```bash
33+
mix deps.get
34+
```
35+
36+
## Configuration
37+
38+
Configure default pagination params
39+
40+
```
41+
config :server_utils,
42+
page_size_key: "page_size",
43+
page_number_key: "page_number",
44+
max_page_size: 25,
45+
page_size: 10,
46+
page_number: 1
47+
```

config/config.exs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file is responsible for configuring your application
2+
# and its dependencies with the aid of the Mix.Config module.
3+
use Mix.Config
4+
5+
# This configuration is loaded before any dependency and is restricted
6+
# to this project. If another project depends on this project, this
7+
# file won't be loaded nor affect the parent project. For this reason,
8+
# if you want to provide default values for your application for
9+
# 3rd-party users, it should be done in your "mix.exs" file.
10+
11+
# You can configure your application as:
12+
#
13+
# config :jwt_test_utils, key: :value
14+
#
15+
# and access this configuration in your application as:
16+
#
17+
# Application.get_env(:jwt_test_utils, :key)
18+
#
19+
# You can also configure a 3rd-party app:
20+
#
21+
# config :logger, level: :info
22+
#
23+
24+
# It is also possible to import configuration files, relative to this
25+
# directory. For example, you can emulate configuration per environment
26+
# by uncommenting the line below and defining dev.exs, test.exs and such.
27+
# Configuration from the imported file will override the ones defined
28+
# here (which is why it is important to import them last).
29+
#
30+
# import_config "#{Mix.env}.exs"

lib/auth_conn_case.ex

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
defmodule JwtTestUtils.AuthConnCase do
2+
@moduledoc """
3+
Module to provide authed conn using JWT
4+
"""
5+
6+
use ExUnit.CaseTemplate
7+
8+
using do
9+
quote do
10+
@fake_user_id "user@fake.is"
11+
@auth_header "authorization"
12+
13+
@doc """
14+
Returns a Plug.Conn with a injected header with a JWT token injected.
15+
16+
By default returns always the same token in the "authorization" header.
17+
"""
18+
@spec get_claim(String.t(), String.t()) :: {Plug.Conn.t()}
19+
def build_authed_conn(user_id \\ @fake_user_id, auth_header \\ @auth_header) do
20+
jwt = JwtMocker.generate_json_token(user_id)
21+
conn = Plug.Adapters.Test.Conn.conn(%Conn{}, :get, "/", nil)
22+
Plug.Conn.put_req_header(conn, auth_header, jwt)
23+
end
24+
end
25+
end
26+
end

lib/jwt_mocker.ex

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
defmodule JwtTestUtils.JwtMocker do
2+
@moduledoc """
3+
Module to generate and validate a JWT token
4+
"""
5+
6+
import Joken
7+
8+
@secret_key "secret_key"
9+
def secret_key, do: @secret_key
10+
11+
@doc """
12+
Generate a JWT token given a username.
13+
14+
## Examples
15+
16+
iex> JwtTestUtils.JwtMocker.generate_json_token("a_username")
17+
"ees45nj3453jkbgbrk3jtvnvjkwn254ioj5iogf4nnj42gn24jkgnt35353ella2375zp1m4me"
18+
"""
19+
@spec generate_json_token(String.t(), Integer.t()) :: String.t()
20+
def generate_json_token(username, exp \\ 0) do
21+
%{username: username, exp: exp}
22+
|> token()
23+
|> with_signer(hs256(@secret_key))
24+
|> sign()
25+
|> get_compact()
26+
end
27+
28+
@doc """
29+
Validates a JWT token for the given username.
30+
31+
## Examples
32+
33+
iex> JwtTestUtils.JwtMocker.validate_token("ees45nj3453jkbgbrk3jtvnvjkwn254ioj5iogf4nnj42gn24jkgnt35353ella2375zp1m4me", "a_username")
34+
:ok
35+
36+
iex> JwtTestUtils.JwtMocker.validate_token("", "a_username")
37+
:error
38+
"""
39+
@spec validate_token(String.t(), String.t(), Integer.t()) :: :ok | :error
40+
def validate_token(jwt, username, exp \\ 0) do
41+
jwt
42+
|> token()
43+
|> with_validation("username", &(&1 == username))
44+
|> with_validation("exp", &(&1 == exp))
45+
|> verify!(hs256(@secret_key))
46+
|> elem(0)
47+
end
48+
end

mix.exs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
defmodule JwtTestUtils.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :jwt_test_utils,
7+
version: "0.1.0",
8+
elixir: "~> 1.6",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps(),
11+
package: package(),
12+
description: description(),
13+
test_coverage: [tool: ExCoveralls],
14+
preferred_cli_env: [
15+
coveralls: :test,
16+
"coveralls.detail": :test,
17+
"coveralls.post": :test,
18+
"coveralls.html": :test
19+
]
20+
]
21+
end
22+
23+
# Run "mix help compile.app" to learn about applications.
24+
def application do
25+
[
26+
extra_applications: [:logger]
27+
]
28+
end
29+
30+
defp package do
31+
[
32+
name: "jwt_test_utils",
33+
files: ["lib", "mix.exs", "README*", "LICENSE*"],
34+
maintainers: ["Adrián Quintás"],
35+
licenses: ["MIT"],
36+
links: %{"GitHub" => "https://github.com/heyorbit/elixir-jwt-test-utils"}
37+
]
38+
end
39+
40+
defp description do
41+
"Test utils for app based JWT authentication"
42+
end
43+
44+
# Run "mix help deps" to learn about dependencies.
45+
defp deps do
46+
[
47+
{:ex_doc, "~> 0.16", only: :dev, runtime: false},
48+
{:excoveralls, "~> 0.8", only: :test},
49+
{:plug, "~> 1.4"},
50+
{:poison, "~> 3.1"},
51+
{:joken, "~> 1.5"}
52+
]
53+
end
54+
end

mix.lock

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
%{
2+
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
3+
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
4+
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
5+
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
6+
"excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
7+
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
8+
"hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
9+
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
10+
"joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
11+
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
12+
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
13+
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
14+
"mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
15+
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
16+
"plug": {:hex, :plug, "1.4.5", "7b13869283fff6b8b21b84b8735326cc012c5eef8607095dc6ee24bd0a273d8e", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
17+
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
18+
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
19+
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
20+
}

test/jwt_mocker_test.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
defmodule JwtTestUtils.JwtMockerTest do
2+
use ExUnit.Case
3+
4+
import Joken
5+
6+
alias JwtTestUtils.JwtMocker
7+
8+
@moduletag :unit
9+
10+
test "Given a username and a jwt when generate a nested jwt then a new jwt is returned" do
11+
generated_jwt = JwtMocker.generate_json_token("fake_user")
12+
13+
is_jwt_valid =
14+
generated_jwt
15+
|> token()
16+
|> with_validation("username", &(&1 == "fake_user"))
17+
|> verify!(hs256(JwtMocker.secret_key()))
18+
|> elem(0)
19+
20+
assert :ok == is_jwt_valid
21+
end
22+
23+
test "Given a valid nested jwt when validate it then a ok is returned" do
24+
is_jwt_valid =
25+
"fake_user"
26+
|> JwtMocker.generate_json_token()
27+
|> JwtMocker.validate_token("fake_user")
28+
29+
assert :ok == is_jwt_valid
30+
end
31+
end

0 commit comments

Comments
 (0)