112 lines
3.6 KiB
Elixir
112 lines
3.6 KiB
Elixir
defmodule ToweropsWeb.Api.MobileAuthControllerTest do
|
|
use ToweropsWeb.ConnCase, async: true
|
|
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Towerops.MobileSessions
|
|
|
|
describe "verify_qr_token" do
|
|
test "returns error for missing token", %{conn: conn} do
|
|
conn = post(conn, ~p"/api/v1/mobile/auth/qr/verify", %{})
|
|
|
|
assert json_response(conn, 400)["error"] == "Missing token parameter"
|
|
end
|
|
|
|
test "returns error for invalid token", %{conn: conn} do
|
|
conn = post(conn, ~p"/api/v1/mobile/auth/qr/verify", %{"token" => "invalid"})
|
|
|
|
assert json_response(conn, 401)["valid"] == false
|
|
end
|
|
|
|
test "returns valid for an active QR token without leaking the user email", %{conn: conn} do
|
|
user = user_fixture()
|
|
{:ok, qr_token} = MobileSessions.create_qr_login_token(user.id)
|
|
|
|
conn = post(conn, ~p"/api/v1/mobile/auth/qr/verify", %{"token" => qr_token.token})
|
|
|
|
response = json_response(conn, 200)
|
|
assert response["valid"] == true
|
|
# Email must not be returned by /verify (only by /complete, once the
|
|
# token is consumed). Otherwise anyone with a QR token can read the
|
|
# linked user's email.
|
|
refute Map.has_key?(response, "user_email")
|
|
end
|
|
end
|
|
|
|
describe "get_session and revoke_session (authenticated)" do
|
|
setup do
|
|
user = user_fixture()
|
|
|
|
{:ok, session} =
|
|
MobileSessions.create_mobile_session(%{
|
|
user_id: user.id,
|
|
device_name: "iTest"
|
|
})
|
|
|
|
%{session: session, raw_token: session.raw_token, user: user}
|
|
end
|
|
|
|
test "returns session metadata for an authenticated request",
|
|
%{conn: conn, raw_token: raw_token, session: session} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer #{raw_token}")
|
|
|> get(~p"/api/v1/mobile/auth/session")
|
|
|
|
response = json_response(conn, 200)
|
|
assert response["id"] == session.id
|
|
assert response["device_name"] == "iTest"
|
|
end
|
|
|
|
test "revokes the current session", %{conn: conn, raw_token: raw_token} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer #{raw_token}")
|
|
|> delete(~p"/api/v1/mobile/auth/session")
|
|
|
|
assert json_response(conn, 200)["success"] == true
|
|
end
|
|
end
|
|
|
|
describe "complete_qr_login" do
|
|
test "returns error for missing token", %{conn: conn} do
|
|
conn = post(conn, ~p"/api/v1/mobile/auth/qr/complete", %{})
|
|
|
|
assert json_response(conn, 400)["error"] == "Missing token parameter"
|
|
end
|
|
|
|
test "returns error for invalid token", %{conn: conn} do
|
|
conn =
|
|
post(conn, ~p"/api/v1/mobile/auth/qr/complete", %{
|
|
"token" => "invalid",
|
|
"device_name" => "Test Phone"
|
|
})
|
|
|
|
assert json_response(conn, 401)["error"] == "Invalid or expired token"
|
|
end
|
|
|
|
test "returns a usable session token on success", %{conn: conn} do
|
|
user = user_fixture()
|
|
{:ok, qr_token} = MobileSessions.create_qr_login_token(user.id)
|
|
|
|
conn =
|
|
post(conn, ~p"/api/v1/mobile/auth/qr/complete", %{
|
|
"token" => qr_token.token,
|
|
"device_name" => "Test iPhone",
|
|
"device_os" => "iOS 17.2",
|
|
"app_version" => "1.0.0"
|
|
})
|
|
|
|
assert %{"session_token" => session_token} = json_response(conn, 200)
|
|
assert is_binary(session_token) and byte_size(session_token) > 0
|
|
|
|
# The returned token must be usable for subsequent authenticated requests
|
|
authed_conn =
|
|
build_conn()
|
|
|> put_req_header("authorization", "Bearer #{session_token}")
|
|
|> get(~p"/api/v1/mobile/organizations")
|
|
|
|
assert json_response(authed_conn, 200)
|
|
end
|
|
end
|
|
end
|