diff --git a/apps/astarte_appengine_api/lib/astarte_appengine_api_web/controllers/fallback_controller.ex b/apps/astarte_appengine_api/lib/astarte_appengine_api_web/controllers/fallback_controller.ex index a97483a4b..d4fd81cf2 100644 --- a/apps/astarte_appengine_api/lib/astarte_appengine_api_web/controllers/fallback_controller.ex +++ b/apps/astarte_appengine_api/lib/astarte_appengine_api_web/controllers/fallback_controller.ex @@ -204,11 +204,39 @@ defmodule Astarte.AppEngine.APIWeb.FallbackController do |> render(:"422_unexpected_object_key") end + # Invalid authorized path + def call(conn, {:error, :invalid_auth_path}) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.AppEngine.APIWeb.ErrorView) + |> render(:invalid_auth_path) + end + # This is called when no JWT token is present - def auth_error(conn, {:unauthenticated, reason}, _opts) do - _ = - Logger.info("Refusing unauthenticated request: #{inspect(reason)}.", tag: "unauthenticated") + def auth_error(conn, {:unauthenticated, :unauthenticated}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.AppEngine.APIWeb.ErrorView) + |> render(:missing_token) + end + + # Invalid JWT token + def auth_error(conn, {:invalid_token, :invalid_token}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.AppEngine.APIWeb.ErrorView) + |> render(:invalid_token) + end + + # Path not authorized + def auth_error(conn, {:unauthorized, :authorization_path_not_matched}, _opts) do + conn + |> put_status(:forbidden) + |> put_view(Astarte.AppEngine.APIWeb.ErrorView) + |> render(:authorization_path_not_matched, %{method: conn.method, path: conn.request_path}) + end + def auth_error(conn, {:unauthenticated, _reason}, _opts) do conn |> put_status(:unauthorized) |> put_view(Astarte.AppEngine.APIWeb.ErrorView) diff --git a/apps/astarte_appengine_api/lib/astarte_appengine_api_web/views/error_view.ex b/apps/astarte_appengine_api/lib/astarte_appengine_api_web/views/error_view.ex index 56b99ddb6..0ae89d701 100644 --- a/apps/astarte_appengine_api/lib/astarte_appengine_api_web/views/error_view.ex +++ b/apps/astarte_appengine_api/lib/astarte_appengine_api_web/views/error_view.ex @@ -110,6 +110,30 @@ defmodule Astarte.AppEngine.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 + + def render("invalid_auth_path.json", _assigns) do + %{ + errors: %{ + detail: "Authorization failed due to an invalid path" + } + } + 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("503_cannot_push_to_device.json", _assigns) do %{errors: %{detail: "Cannot push to device"}} end diff --git a/apps/astarte_appengine_api/priv/static/astarte_appengine_api.yaml b/apps/astarte_appengine_api/priv/static/astarte_appengine_api.yaml index b1f1d19f4..c22780c7f 100644 --- a/apps/astarte_appengine_api/priv/static/astarte_appengine_api.yaml +++ b/apps/astarte_appengine_api/priv/static/astarte_appengine_api.yaml @@ -136,11 +136,9 @@ paths: - 8ZxuSGkU7pggwoomJeXo9g - k-IFKDPoVzIXUcFkF7U80A '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '/{realm_name}/devices/{device_id}': parameters: - name: realm_name @@ -176,11 +174,9 @@ paths: data: $ref: '#/components/schemas/DeviceStatus' '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Device not found content: @@ -221,11 +217,9 @@ paths: '400': description: Bad request '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Device not found. content: @@ -272,11 +266,9 @@ paths: data: $ref: '#/components/schemas/DeviceStatus' '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Device not found content: @@ -317,11 +309,9 @@ paths: '400': description: Bad request '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Device not found. content: @@ -371,11 +361,9 @@ paths: - com.test.foo - com.test.bar '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Device not found. content: @@ -423,11 +411,9 @@ paths: '200': description: Success '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Interface not found in introspection or device not found. content: @@ -525,11 +511,9 @@ paths: '200': description: Success '401': - description: Realm doesn't exist or operation not allowed. - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: >- Path not found or interface not found in introspection or device not @@ -588,11 +572,9 @@ paths: '400': description: Bad request '401': - description: Realm doesn't exist or operation not allowed. - content: - '*/*': - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: >- Endpoint not found or interface not found in introspection or device @@ -650,11 +632,9 @@ paths: '400': description: Bad request '401': - description: Realm doesn't exist or operation not allowed. - content: - '*/*': - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: >- Endpoint not found or interface not found in introspection or device @@ -710,11 +690,9 @@ paths: '204': description: Success '401': - description: Realm doesn't exist or operation not allowed. - content: - '*/*': - schema: - $ref: '#/components/schemas/UnauthorizedError' + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: >- Path not found or interface not found in introspection or device not @@ -748,6 +726,8 @@ paths: $ref: '#/components/responses/IndexGroups' '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' post: tags: - groups @@ -767,6 +747,8 @@ paths: $ref: '#/components/responses/GroupCreated' '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '422': $ref: '#/components/responses/InvalidGroupConfig' '/{realm_name}/groups/{group_name}': @@ -788,6 +770,8 @@ paths: $ref: '#/components/responses/GetGroup' '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': $ref: '#/components/responses/GroupNotFound' '/{realm_name}/groups/{group_name}/devices': @@ -808,6 +792,8 @@ paths: $ref: '#/components/responses/IndexGroupDevices' '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': $ref: '#/components/responses/GroupNotFound' post: @@ -829,6 +815,8 @@ paths: description: Success '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': $ref: '#/components/responses/GroupNotFound' '422': @@ -852,6 +840,8 @@ paths: description: Device removed '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': $ref: '#/components/responses/GroupOrDeviceNotFound' '/{realm_name}/stats/devices': @@ -871,6 +861,8 @@ paths: $ref: '#/components/responses/GetDevicesStats' '401': $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' components: securitySchemes: JWT: @@ -1000,11 +992,24 @@ components: group_name: - is invalid Unauthorized: - description: Realm doesn't exist or operation not allowed. + description: Token/Realm doesn't exist or operation not allowed. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/MissingTokenError' + - $ref: '#/components/schemas/InvalidTokenError' + - $ref: '#/components/schemas/InvalidAuthPathError' + - $ref: '#/components/schemas/UnauthorizedError' + AuthorizationPathNotMatched: + description: Authorization path not matched. content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedError' + type: object + properties: + data: + $ref: '#/components/schemas/AuthorizationPathNotMatchedError' GroupNotFound: description: Group not found content: @@ -1252,3 +1257,54 @@ components: example: errors: detail: Unauthorized + MissingTokenError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + description: Short error description + example: + errors: + detail: Missing authorization token + + InvalidTokenError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + description: Short error description + example: + errors: + detail: Invalid JWT token + + InvalidAuthPathError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + description: Short error description + example: + errors: + detail: Authorization failed due to an invalid path + + AuthorizationPathNotMatchedError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + description: Detailed error message including the method and path + example: + errors: + detail: Unauthorized access to GET /api/v1/some_path. Please verify your permissions diff --git a/apps/astarte_appengine_api/test/astarte_appengine_api_web/auth/auth_test.exs b/apps/astarte_appengine_api/test/astarte_appengine_api_web/auth/auth_test.exs index b9191ee7f..98eb38971 100644 --- a/apps/astarte_appengine_api/test/astarte_appengine_api_web/auth/auth_test.exs +++ b/apps/astarte_appengine_api/test/astarte_appengine_api_web/auth/auth_test.exs @@ -53,7 +53,7 @@ defmodule Astarte.AppEngine.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 @@ -113,7 +113,8 @@ defmodule Astarte.AppEngine.APIWeb.AuthTest do ) |> get("#{@request_path}/with/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 device returns 403", %{conn: conn} do @@ -125,7 +126,8 @@ defmodule Astarte.AppEngine.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 devices returns the data", %{conn: conn} do @@ -149,7 +151,8 @@ defmodule Astarte.AppEngine.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 with generic matching regexp returns the data", %{conn: conn} do @@ -177,5 +180,27 @@ defmodule Astarte.AppEngine.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 end end diff --git a/apps/astarte_appengine_api/test/astarte_appengine_api_web/controllers/groups_controller_test.exs b/apps/astarte_appengine_api/test/astarte_appengine_api_web/controllers/groups_controller_test.exs index 16c2e136f..8130eb433 100644 --- a/apps/astarte_appengine_api/test/astarte_appengine_api_web/controllers/groups_controller_test.exs +++ b/apps/astarte_appengine_api/test/astarte_appengine_api_web/controllers/groups_controller_test.exs @@ -61,7 +61,7 @@ defmodule Astarte.AppEngine.APIWeb.GroupsControllerTest do test "returns 403 on unexisting realm", %{conn: conn} do conn = get(conn, groups_path(conn, :index, "unexisting")) - assert json_response(conn, 403)["errors"] == %{"detail" => "Forbidden"} + assert json_response(conn, 401)["errors"] == %{"detail" => "Invalid JWT token"} end test "returns an empty list on empty realm", %{conn: conn} do diff --git a/apps/astarte_appengine_api/test/support/jwt_test_helper.ex b/apps/astarte_appengine_api/test/support/jwt_test_helper.ex index 1c57c5c91..08421f7bc 100644 --- a/apps/astarte_appengine_api/test/support/jwt_test_helper.ex +++ b/apps/astarte_appengine_api/test/support/jwt_test_helper.ex @@ -39,6 +39,16 @@ defmodule Astarte.AppEngine.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 diff --git a/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/controllers/fallback_controller.ex b/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/controllers/fallback_controller.ex index 90a3a1f19..6e23bab16 100644 --- a/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/controllers/fallback_controller.ex +++ b/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/controllers/fallback_controller.ex @@ -83,7 +83,38 @@ defmodule Astarte.Housekeeping.APIWeb.FallbackController do |> render(:"401") end + # Invalid authorized path + def call(conn, {:error, :invalid_auth_path}) do + conn + |> put_status(:unauthorized) + |> put_view(ErrorView) + |> render(:invalid_auth_path) + end + # This is called when no JWT token is present + def auth_error(conn, {:unauthenticated, :unauthenticated}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(ErrorView) + |> render(:missing_token) + end + + # Invalid JWT token + def auth_error(conn, {:invalid_token, :invalid_token}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(ErrorView) + |> render(:invalid_token) + end + + # Path not authorized + def auth_error(conn, {:unauthorized, :authorization_path_not_matched}, _opts) do + conn + |> put_status(:forbidden) + |> put_view(ErrorView) + |> render(:authorization_path_not_matched, %{method: conn.method, path: conn.request_path}) + end + def auth_error(conn, {:unauthenticated, _reason}, _opts) do conn |> put_status(:unauthorized) diff --git a/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/views/error_view.ex b/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/views/error_view.ex index 178960692..68a7f7c05 100644 --- a/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/views/error_view.ex +++ b/apps/astarte_housekeeping_api/lib/astarte_housekeeping_api_web/views/error_view.ex @@ -43,6 +43,30 @@ defmodule Astarte.Housekeeping.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 + + def render("invalid_auth_path.json", _assigns) do + %{ + errors: %{ + detail: "Authorization failed due to an invalid path" + } + } + 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("realm_deletion_disabled.json", _assigns) do %{errors: %{detail: "Realm deletion disabled"}} end diff --git a/apps/astarte_housekeeping_api/priv/static/astarte_housekeeping_api.yaml b/apps/astarte_housekeeping_api/priv/static/astarte_housekeeping_api.yaml index 62f6e4805..929d04644 100644 --- a/apps/astarte_housekeeping_api/priv/static/astarte_housekeeping_api.yaml +++ b/apps/astarte_housekeeping_api/priv/static/astarte_housekeeping_api.yaml @@ -49,6 +49,17 @@ paths: data: - arealm - anotherrealm + '401': + description: Token doesn't exist or operation not allowed. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/MissingTokenError' + - $ref: '#/components/schemas/InvalidTokenError' + - $ref: '#/components/schemas/InvalidAuthPathError' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' post: tags: - realm @@ -81,6 +92,17 @@ paths: properties: data: $ref: '#/components/schemas/Realm' + '401': + description: Token doesn't exist or operation not allowed. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/MissingTokenError' + - $ref: '#/components/schemas/InvalidTokenError' + - $ref: '#/components/schemas/InvalidAuthPathError' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' requestBody: $ref: '#/components/requestBodies/createRealmBody' '/realms/{realm_name}': @@ -108,6 +130,10 @@ paths: properties: data: $ref: '#/components/schemas/Realm' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' put: tags: - realm @@ -126,6 +152,10 @@ paths: responses: '200': description: Success + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' requestBody: $ref: '#/components/requestBodies/createRealmBody' delete: @@ -162,6 +192,10 @@ paths: responses: '204': description: Success + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '405': description: Realm deletion disabled '422': @@ -193,14 +227,9 @@ paths: '400': description: Bad request '401': - description: Authorization information is missing or invalid. - content: - application/json: - schema: - $ref: '#/components/schemas/GenericError' - example: - errors: - detail: Unauthorized + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/AuthorizationPathNotMatched' '404': description: Realm not found. content: @@ -247,6 +276,25 @@ components: description: >- A JSON Merge Patch containing the property changes that should be applied to the realm. Explicitly set a property to null to remove it. required: true + responses: + Unauthorized: + description: Token/Realm doesn't exist or operation not allowed. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/MissingTokenError' + - $ref: '#/components/schemas/InvalidTokenError' + - $ref: '#/components/schemas/InvalidAuthPathError' + AuthorizationPathNotMatched: + description: Authorization path not matched. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/AuthorizationPathNotMatchedError' schemas: Realm: type: object @@ -369,3 +417,50 @@ components: properties: detail: type: string + MissingTokenError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Missing authorization token + + InvalidTokenError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Invalid JWT token + + InvalidAuthPathError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Authorization failed due to an invalid path + + AuthorizationPathNotMatchedError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Unauthorized access to GET /api/v1/some_path. Please verify your permissions diff --git a/apps/astarte_housekeeping_api/test/astarte_housekeeping_api_web/auth/auth_test.exs b/apps/astarte_housekeeping_api/test/astarte_housekeeping_api_web/auth/auth_test.exs index 37f5c0c7a..855058709 100644 --- a/apps/astarte_housekeeping_api/test/astarte_housekeeping_api_web/auth/auth_test.exs +++ b/apps/astarte_housekeeping_api/test/astarte_housekeeping_api_web/auth/auth_test.exs @@ -38,7 +38,7 @@ defmodule Astarte.Housekeeping.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 @@ -86,7 +86,8 @@ defmodule Astarte.Housekeeping.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 @@ -98,7 +99,8 @@ defmodule Astarte.Housekeeping.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 @@ -122,7 +124,8 @@ defmodule Astarte.Housekeeping.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 @@ -148,5 +151,27 @@ defmodule Astarte.Housekeeping.APIWeb.AuthTest do assert json_response(conn, 200) == @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 end end diff --git a/apps/astarte_housekeeping_api/test/support/jwt_test_helper.ex b/apps/astarte_housekeeping_api/test/support/jwt_test_helper.ex index 6119a80a7..4b68b0338 100644 --- a/apps/astarte_housekeeping_api/test/support/jwt_test_helper.ex +++ b/apps/astarte_housekeeping_api/test/support/jwt_test_helper.ex @@ -36,6 +36,16 @@ defmodule Astarte.Housekeeping.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 diff --git a/apps/astarte_pairing_api/lib/astarte_pairing_api_web/controllers/fallback_controller.ex b/apps/astarte_pairing_api/lib/astarte_pairing_api_web/controllers/fallback_controller.ex index a669bfa2a..d73319416 100644 --- a/apps/astarte_pairing_api/lib/astarte_pairing_api_web/controllers/fallback_controller.ex +++ b/apps/astarte_pairing_api/lib/astarte_pairing_api_web/controllers/fallback_controller.ex @@ -61,17 +61,46 @@ defmodule Astarte.Pairing.APIWeb.FallbackController do |> render(:"403") end - # This is the final call made by EnsureAuthenticated - def auth_error(conn, {:unauthenticated, reason}, _opts) do - _ = - Logger.info("Refusing unauthenticated request: #{inspect(reason)}.", tag: "unauthenticated") + # Invalid authorized path + def call(conn, {:error, :invalid_auth_path}) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.Pairing.APIWeb.ErrorView) + |> render(:invalid_auth_path) + end + + # This is called when no JWT token is present + def auth_error(conn, {:unauthenticated, :unauthenticated}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.Pairing.APIWeb.ErrorView) + |> render(:missing_token) + end + + # Invalid JWT token + def auth_error(conn, {:invalid_token, :invalid_token}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.Pairing.APIWeb.ErrorView) + |> render(:invalid_token) + end + + # Path not authorized + def auth_error(conn, {:unauthorized, :authorization_path_not_matched}, _opts) do + conn + |> put_status(:forbidden) + |> put_view(Astarte.Pairing.APIWeb.ErrorView) + |> render(:authorization_path_not_matched, %{method: conn.method, path: conn.request_path}) + end + def auth_error(conn, {:unauthenticated, _reason}, _opts) do conn |> put_status(:unauthorized) |> put_view(Astarte.Pairing.APIWeb.ErrorView) |> render(:"401") end + # In all other cases, we reply with 403 def auth_error(conn, _reason, _opts) do conn |> put_status(:forbidden) diff --git a/apps/astarte_pairing_api/lib/astarte_pairing_api_web/views/error_view.ex b/apps/astarte_pairing_api/lib/astarte_pairing_api_web/views/error_view.ex index 35067c476..a8a5ca0f4 100644 --- a/apps/astarte_pairing_api/lib/astarte_pairing_api_web/views/error_view.ex +++ b/apps/astarte_pairing_api/lib/astarte_pairing_api_web/views/error_view.ex @@ -31,6 +31,30 @@ defmodule Astarte.Pairing.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 + + def render("invalid_auth_path.json", _assigns) do + %{ + errors: %{ + detail: "Authorization failed due to an invalid path" + } + } + 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("404.json", _assigns) do %{errors: %{detail: "Page not found"}} end diff --git a/apps/astarte_pairing_api/priv/static/astarte_pairing_api.yaml b/apps/astarte_pairing_api/priv/static/astarte_pairing_api.yaml index f3fba45af..9ee29f8f5 100644 --- a/apps/astarte_pairing_api/priv/static/astarte_pairing_api.yaml +++ b/apps/astarte_pairing_api/priv/static/astarte_pairing_api.yaml @@ -63,17 +63,15 @@ paths: data: credentials_secret: TTkd5OgB13X/3qU0LXU7OCxyTXz5QHM2NY1IgidtPOs= '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedResponse' + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden + description: Forbidden or Authorization path not matched content: application/json: schema: - $ref: '#/components/schemas/ForbiddenResponse' + oneOf: + - $ref: '#/components/schemas/ForbiddenResponse' + - $ref: '#/components/schemas/AuthorizationPathNotMatchedResponse' '422': description: Unprocessable entity content: @@ -135,17 +133,15 @@ paths: '204': description: Device unregistered '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedResponse' + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden + description: Forbidden or Authorization path not matched content: application/json: schema: - $ref: '#/components/schemas/ForbiddenResponse' + oneOf: + - $ref: '#/components/schemas/ForbiddenResponse' + - $ref: '#/components/schemas/AuthorizationPathNotMatchedResponse' '404': description: Device not found content: @@ -198,17 +194,15 @@ paths: astarte_mqtt_v1: broker_url: 'ssl://broker.astarte.example.com:8883' '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedResponse' + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden + description: Forbidden or Authorization path not matched content: application/json: schema: - $ref: '#/components/schemas/ForbiddenResponse' + oneOf: + - $ref: '#/components/schemas/ForbiddenResponse' + - $ref: '#/components/schemas/AuthorizationPathNotMatchedResponse' '/{realm_name}/devices/{hw_id}/protocols/astarte_mqtt_v1/credentials': post: tags: @@ -278,17 +272,15 @@ paths: ySj0xif2Z8U7MTfhmZs1cyDA/A== -----END CERTIFICATE----- '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedResponse' + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden + description: Forbidden or Authorization path not matched content: application/json: schema: - $ref: '#/components/schemas/ForbiddenResponse' + oneOf: + - $ref: '#/components/schemas/ForbiddenResponse' + - $ref: '#/components/schemas/AuthorizationPathNotMatchedResponse' '422': description: Unprocessable entity content: @@ -375,17 +367,15 @@ paths: valid: false cause: INVALID_ISSUER '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedResponse' + $ref: '#/components/responses/Unauthorized' '403': - description: Forbidden + description: Forbidden or Authorization path not matched content: application/json: schema: - $ref: '#/components/schemas/ForbiddenResponse' + oneOf: + - $ref: '#/components/schemas/ForbiddenResponse' + - $ref: '#/components/schemas/AuthorizationPathNotMatchedResponse' '422': description: Unprocessable entity content: @@ -465,6 +455,26 @@ components: The following syntax must be used in the 'Authorization' header : Bearer xxxxxxxxxxxxxxxxxxxxx + responses: + Unauthorized: + description: Token/Realm doesn't exist or operation not allowed. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/MissingTokenResponse' + - $ref: '#/components/schemas/InvalidTokenResponse' + - $ref: '#/components/schemas/InvalidAuthPathResponse' + - $ref: '#/components/schemas/UnauthorizedResponse' + AuthorizationPathNotMatched: + description: Authorization path not matched. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/AuthorizationPathNotMatchedResponse' schemas: DeviceRegistrationRequest: type: object @@ -578,3 +588,50 @@ components: example: errors: detail: Unauthorized + MissingTokenResponse: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Missing authorization token + + InvalidTokenResponse: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Invalid JWT token + + InvalidAuthPathResponse: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Authorization failed due to an invalid path + + AuthorizationPathNotMatchedResponse: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Unauthorized access to GET /api/v1/some_path. Please verify your permissions diff --git a/apps/astarte_pairing_api/test/astarte_pairing_api_web/auth/auth_test.exs b/apps/astarte_pairing_api/test/astarte_pairing_api_web/auth/auth_test.exs index c30c9a401..a01b38aff 100644 --- a/apps/astarte_pairing_api/test/astarte_pairing_api_web/auth/auth_test.exs +++ b/apps/astarte_pairing_api/test/astarte_pairing_api_web/auth/auth_test.exs @@ -45,6 +45,11 @@ defmodule Astarte.Pairing.APIWeb.AuthTest do {:ok, conn: conn} end + test "no token returns 401", %{conn: conn} do + conn = post(conn, agent_path(conn, :create, @realm), data: @create_attrs) + assert json_response(conn, 401)["errors"]["detail"] == "Missing authorization token" + end + test "succeeds with specific authorizations", %{conn: conn} do register_authorizations = ["POST::agent/devices"] @@ -86,7 +91,8 @@ defmodule Astarte.Pairing.APIWeb.AuthTest do |> authorize_conn(register_authorizations) |> post(agent_path(conn, :create, @realm), data: @create_attrs) - 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 "fails with authorization for different method", %{conn: conn} do @@ -97,7 +103,30 @@ defmodule Astarte.Pairing.APIWeb.AuthTest do |> authorize_conn(register_authorizations) |> post(agent_path(conn, :create, @realm), data: @create_attrs) - 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 "invalid JWT token returns 401", %{conn: conn} do + conn = + put_req_header( + conn, + "authorization", + "bearer invalid_token" + ) + |> post(agent_path(conn, :create, @realm), data: @create_attrs) + + 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(["^POST$::agent/devices"]) + + conn = + put_req_header(conn, "authorization", "bearer #{token}") + |> post(agent_path(conn, :create, @realm), data: @create_attrs) + + assert json_response(conn, 401)["errors"]["detail"] == "Invalid JWT token" end end diff --git a/apps/astarte_pairing_api/test/astarte_pairing_api_web/controllers/agent_controller_test.exs b/apps/astarte_pairing_api/test/astarte_pairing_api_web/controllers/agent_controller_test.exs index cdd8cc98b..dc24f57f5 100644 --- a/apps/astarte_pairing_api/test/astarte_pairing_api_web/controllers/agent_controller_test.exs +++ b/apps/astarte_pairing_api/test/astarte_pairing_api_web/controllers/agent_controller_test.exs @@ -199,7 +199,7 @@ defmodule Astarte.Pairing.APIWeb.AgentControllerTest do |> delete_req_header("authorization") |> post(agent_path(conn, :create, @realm), data: @create_attrs) - assert json_response(conn, 401)["errors"] == %{"detail" => "Unauthorized"} + assert json_response(conn, 401)["errors"] == %{"detail" => "Missing authorization token"} end end @@ -258,7 +258,7 @@ defmodule Astarte.Pairing.APIWeb.AgentControllerTest do |> delete_req_header("authorization") |> delete(agent_path(conn, :delete, @realm, @device_id)) - assert json_response(conn, 401)["errors"] == %{"detail" => "Unauthorized"} + assert json_response(conn, 401)["errors"] == %{"detail" => "Missing authorization token"} end end diff --git a/apps/astarte_pairing_api/test/support/jwt_test_helper.ex b/apps/astarte_pairing_api/test/support/jwt_test_helper.ex index fb1e25ce3..1e46d145c 100644 --- a/apps/astarte_pairing_api/test/support/jwt_test_helper.ex +++ b/apps/astarte_pairing_api/test/support/jwt_test_helper.ex @@ -40,6 +40,16 @@ defmodule Astarte.Pairing.APIWeb.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 diff --git a/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/controllers/fallback_controller.ex b/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/controllers/fallback_controller.ex index dd01fbcba..86f0dba97 100644 --- a/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/controllers/fallback_controller.ex +++ b/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/controllers/fallback_controller.ex @@ -129,7 +129,38 @@ defmodule Astarte.RealmManagement.APIWeb.FallbackController do |> render(:device_not_found) end + # 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 + # This is called when no JWT token is present + def auth_error(conn, {:unauthenticated, :unauthenticated}, _opts) do + conn + |> put_status(:unauthorized) + |> put_view(Astarte.RealmManagement.APIWeb.ErrorView) + |> 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 + + # 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 + def auth_error(conn, {:unauthenticated, _reason}, _opts) do conn |> put_status(:unauthorized) diff --git a/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/views/error_view.ex b/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/views/error_view.ex index d2bacb5bc..774a0a836 100644 --- a/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/views/error_view.ex +++ b/apps/astarte_realm_management_api/lib/astarte_realm_management_api_web/views/error_view.ex @@ -39,6 +39,30 @@ 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 + + def render("invalid_auth_path.json", _assigns) do + %{ + errors: %{ + detail: "Authorization failed due to an invalid path" + } + } + 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 diff --git a/apps/astarte_realm_management_api/priv/static/astarte_realm_management_api.yaml b/apps/astarte_realm_management_api/priv/static/astarte_realm_management_api.yaml index b3fea2cbc..c3d05d8b5 100644 --- a/apps/astarte_realm_management_api/priv/static/astarte_realm_management_api.yaml +++ b/apps/astarte_realm_management_api/priv/static/astarte_realm_management_api.yaml @@ -541,7 +541,9 @@ components: content: application/json: schema: - $ref: '#/components/schemas/GenericError' + oneOf: + - $ref: '#/components/schemas/AuthorizationPathNotMatchedError' + - $ref: '#/components/schemas/GenericError' example: errors: detail: Forbidden @@ -814,14 +816,24 @@ components: errors: detail: Trigger policy not found Unauthorized: - description: Authorization information is missing or invalid. + description: Token/Realm doesn't exist or operation not allowed. content: application/json: schema: - $ref: '#/components/schemas/GenericError' - example: - errors: - detail: Unauthorized + oneOf: + - $ref: '#/components/schemas/MissingTokenError' + - $ref: '#/components/schemas/InvalidTokenError' + - $ref: '#/components/schemas/InvalidAuthPathError' + - $ref: '#/components/schemas/GenericError' + AuthorizationPathNotMatched: + description: Authorization path not matched. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/AuthorizationPathNotMatchedError' UpdateConflict: description: >- The updated interface is valid, but there's a conflict with the existing @@ -1422,3 +1434,50 @@ components: properties: errors: type: object + MissingTokenError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Missing authorization token + + InvalidTokenError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Invalid JWT token + + InvalidAuthPathError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Authorization failed due to an invalid path + + AuthorizationPathNotMatchedError: + type: object + properties: + errors: + type: object + properties: + detail: + type: string + example: + errors: + detail: Unauthorized access to GET /api/v1/some_path. Please verify your permissions diff --git a/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/auth/auth_test.exs b/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/auth/auth_test.exs index 46dec3e04..b794dcc86 100644 --- a/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/auth/auth_test.exs +++ b/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/auth/auth_test.exs @@ -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 @@ -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 @@ -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 @@ -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 @@ -151,5 +154,27 @@ 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 end end diff --git a/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/controllers/device_controller_test.exs b/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/controllers/device_controller_test.exs index 6a6670035..ed6775743 100644 --- a/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/controllers/device_controller_test.exs +++ b/apps/astarte_realm_management_api/test/astarte_realm_management_api_web/controllers/device_controller_test.exs @@ -28,6 +28,7 @@ defmodule Astarte.RealmManagement.APIWeb.DeviceControllerTest do setup %{conn: conn} do Mock.DB.create_device(@realm, @device_id) + Mock.DB.put_jwt_public_key_pem(@realm, JWTTestHelper.public_key_pem()) token = JWTTestHelper.gen_jwt_all_access_token() conn = diff --git a/apps/astarte_realm_management_api/test/support/jwt_test_helper.ex b/apps/astarte_realm_management_api/test/support/jwt_test_helper.ex index 577f79129..10b964c32 100644 --- a/apps/astarte_realm_management_api/test/support/jwt_test_helper.ex +++ b/apps/astarte_realm_management_api/test/support/jwt_test_helper.ex @@ -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