towerops/test/towerops_web/controllers/api/mobile_auth_controller_test.exs
Graham McIntire 33daaf14f0 fix: return raw session token instead of hash in mobile QR login (#42)
the complete_qr_login endpoint was returning session.token (the SHA256
hash stored in the database) instead of session.raw_token (the plaintext).
this caused all subsequent API calls and WebSocket connections to fail
with 401/REFUSED since the server would hash the already-hashed token.

adds a regression test that verifies the returned token is usable for
authenticated API requests.

Reviewed-on: graham/towerops-web#42
2026-03-16 13:59:52 -05:00

63 lines
2 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
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)
# 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