Skip to content

Commit

Permalink
Handle API authentication errors for the RM API
Browse files Browse the repository at this point in the history
- Expand the Fallback controller with some failed
  auth functions
- Add additional error views with useful feedback
  messages
- Add the ```gen_jwt_token_with_wrong_signature```
  function in the JWTTestHelper
- Add the necessary auth tests and adapt existing
  ones

Signed-off-by: Armin Ahmetović <[email protected]>
  • Loading branch information
arahmarchak committed Dec 11, 2024
1 parent 057214d commit 31a833d
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,37 @@ defmodule Astarte.RealmManagement.APIWeb.FallbackController do
end

# This is called when no JWT token is present
def auth_error(conn, {:unauthenticated, _reason}, _opts) do
def auth_error(conn, {:unauthenticated, :unauthenticated}, _opts) do
conn
|> put_status(:unauthorized)
|> put_view(Astarte.RealmManagement.APIWeb.ErrorView)
|> render(:"401")
|> render(:missing_token)
end

# Invalid JWT token
def auth_error(conn, {:invalid_token, :invalid_token}, _opts) do
conn
|> put_status(:unauthorized)
|> put_view(Astarte.RealmManagement.APIWeb.ErrorView)
|> render(:invalid_token)
end

# TODO: add another auth_error for invalid_token_signature

# Invalid authorized path
def call(conn, {:error, :invalid_auth_path}) do
conn
|> put_status(:unauthorized)
|> put_view(Astarte.RealmManagement.APIWeb.ErrorView)
|> render(:invalid_auth_path)
end

# Path not authorized
def auth_error(conn, {:unauthorized, :authorization_path_not_matched}, _opts) do
conn
|> put_status(:forbidden)
|> put_view(Astarte.RealmManagement.APIWeb.ErrorView)
|> render(:authorization_path_not_matched, %{method: conn.method, path: conn.request_path})
end

# In all other cases, we reply with 403
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,33 @@ defmodule Astarte.RealmManagement.APIWeb.ErrorView do
%{errors: %{detail: "Forbidden"}}
end

def render("missing_token.json", _assigns) do
%{errors: %{detail: "Missing authorization token"}}
end

def render("invalid_token.json", _assigns) do
%{errors: %{detail: "Invalid JWT token"}}
end

# TODO: add error message for invalid_token_signature

def render("invalid_auth_path.json", _assigns) do
%{
errors: %{
detail:
"Authorization failed due to an invalid path. Ensure the realm name and endpoint are correctly specified in the request"
}
}
end

def render("authorization_path_not_matched.json", %{method: method, path: path}) do
%{
errors: %{
detail: "Unauthorized access to #{method} #{path}. Please verify your permissions"
}
}
end

def render("interface_not_found.json", _assigns) do
%{errors: %{detail: "Interface not found"}}
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Astarte.RealmManagement.APIWeb.AuthTest do
describe "JWT" do
test "no token returns 401", %{conn: conn} do
conn = get(conn, @request_path)
assert json_response(conn, 401)["errors"]["detail"] == "Unauthorized"
assert json_response(conn, 401)["errors"]["detail"] == "Missing authorization token"
end

test "all access token returns the data", %{conn: conn} do
Expand Down Expand Up @@ -89,7 +89,8 @@ defmodule Astarte.RealmManagement.APIWeb.AuthTest do
)
|> get("#{@request_path}/suffix")

assert json_response(conn, 403)["errors"]["detail"] == "Forbidden"
assert json_response(conn, 403)["errors"]["detail"] ==
"Unauthorized access to #{conn.assigns.method} #{conn.assigns.path}. Please verify your permissions"
end

test "token for another path returns 403", %{conn: conn} do
Expand All @@ -101,7 +102,8 @@ defmodule Astarte.RealmManagement.APIWeb.AuthTest do
)
|> get(@request_path)

assert json_response(conn, 403)["errors"]["detail"] == "Forbidden"
assert json_response(conn, 403)["errors"]["detail"] ==
"Unauthorized access to #{conn.assigns.method} #{conn.assigns.path}. Please verify your permissions"
end

test "token for both paths returns the data", %{conn: conn} do
Expand All @@ -125,7 +127,8 @@ defmodule Astarte.RealmManagement.APIWeb.AuthTest do
)
|> get(@request_path)

assert json_response(conn, 403)["errors"]["detail"] == "Forbidden"
assert json_response(conn, 403)["errors"]["detail"] ==
"Unauthorized access to #{conn.assigns.method} #{conn.assigns.path}. Please verify your permissions"
end

test "token for both methods returns the data", %{conn: conn} do
Expand All @@ -151,5 +154,40 @@ defmodule Astarte.RealmManagement.APIWeb.AuthTest do

assert json_response(conn, 200)["data"] == @expected_data
end

test "invalid JWT token returns 401", %{conn: conn} do
conn =
put_req_header(
conn,
"authorization",
"bearer invalid_token"
)
|> get(@request_path)

assert json_response(conn, 401)["errors"]["detail"] == "Invalid JWT token"
end

test "token with mismatched signature returns 401", %{conn: conn} do
token = JWTTestHelper.gen_jwt_token_with_wrong_signature(["^GET$::#{@valid_auth_path}"])

conn =
put_req_header(conn, "authorization", "bearer #{token}")
|> get(@request_path)

assert json_response(conn, 401)["errors"]["detail"] == "Invalid JWT token"
end

test "token with invalid path claim returns 403", %{conn: conn} do
conn =
put_req_header(
conn,
"authorization",
"bearer #{JWTTestHelper.gen_jwt_token(["^POST$::#{@non_matching_auth_path}"])}"
)
|> get(@request_path)

assert json_response(conn, 403)["errors"]["detail"] ==
"Unauthorized access to #{conn.assigns.method} #{conn.assigns.path}. Please verify your permissions"
end
end
end
10 changes: 10 additions & 0 deletions apps/astarte_realm_management_api/test/support/jwt_test_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ defmodule Astarte.RealmManagement.API.JWTTestHelper do
jwt
end

def gen_jwt_token_with_wrong_signature(authorization_paths) do
valid_token = gen_jwt_token(authorization_paths)

[header, payload, _signature] = String.split(valid_token, ".")

fake_signature = "fake_signature"

"#{header}.#{payload}.#{fake_signature}"
end

def gen_jwt_all_access_token do
gen_jwt_token([".*::.*"])
end
Expand Down

0 comments on commit 31a833d

Please sign in to comment.