1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.OAuth.OAuthControllerTest do
6 use Pleroma.Web.ConnCase
10 alias Pleroma.Web.OAuth.Authorization
11 alias Pleroma.Web.OAuth.OAuthController
12 alias Pleroma.Web.OAuth.Token
17 signing_salt: "cooldude"
19 clear_config_all([:instance, :account_activation_required])
21 describe "in OAuth consumer mode, " do
24 app: insert(:oauth_app),
27 |> Plug.Session.call(Plug.Session.init(@session_opts))
32 clear_config([:auth, :oauth_consumer_strategies]) do
34 [:auth, :oauth_consumer_strategies],
39 test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{
48 "response_type" => "code",
49 "client_id" => app.client_id,
50 "redirect_uri" => OAuthController.default_redirect_uri(app),
55 assert response = html_response(conn, 200)
56 assert response =~ "Sign in with Twitter"
57 assert response =~ o_auth_path(conn, :prepare_request)
60 test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{
67 "/oauth/prepare_request",
69 "provider" => "twitter",
71 "scope" => "read follow",
72 "client_id" => app.client_id,
73 "redirect_uri" => OAuthController.default_redirect_uri(app),
79 assert response = html_response(conn, 302)
81 redirect_query = URI.parse(redirected_to(conn)).query
82 assert %{"state" => state_param} = URI.decode_query(redirect_query)
83 assert {:ok, state_components} = Poison.decode(state_param)
85 expected_client_id = app.client_id
86 expected_redirect_uri = app.redirect_uris
89 "scope" => "read follow",
90 "client_id" => ^expected_client_id,
91 "redirect_uri" => ^expected_redirect_uri,
96 test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`",
97 %{app: app, conn: conn} do
98 registration = insert(:registration)
99 redirect_uri = OAuthController.default_redirect_uri(app)
102 "scope" => Enum.join(app.scopes, " "),
103 "client_id" => app.client_id,
104 "redirect_uri" => redirect_uri,
110 |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
112 "/oauth/twitter/callback",
114 "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
115 "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
116 "provider" => "twitter",
117 "state" => Poison.encode!(state_params)
121 assert response = html_response(conn, 302)
122 assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
125 test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
126 %{app: app, conn: conn} do
130 "scope" => "read write",
131 "client_id" => app.client_id,
132 "redirect_uri" => OAuthController.default_redirect_uri(app),
138 |> assign(:ueberauth_auth, %{
141 info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil}
144 "/oauth/twitter/callback",
146 "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
147 "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
148 "provider" => "twitter",
149 "state" => Poison.encode!(state_params)
153 assert response = html_response(conn, 200)
154 assert response =~ ~r/name="op" type="submit" value="register"/
155 assert response =~ ~r/name="op" type="submit" value="connect"/
156 assert response =~ user.email
157 assert response =~ user.nickname
160 test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{
165 "scope" => Enum.join(app.scopes, " "),
166 "client_id" => app.client_id,
167 "redirect_uri" => OAuthController.default_redirect_uri(app),
173 |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]})
175 "/oauth/twitter/callback",
177 "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
178 "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
179 "provider" => "twitter",
180 "state" => Poison.encode!(state_params)
184 assert response = html_response(conn, 302)
185 assert redirected_to(conn) == app.redirect_uris
186 assert get_flash(conn, :error) == "Failed to authenticate: (error description)."
189 test "GET /oauth/registration_details renders registration details form", %{
196 "/oauth/registration_details",
198 "authorization" => %{
199 "scopes" => app.scopes,
200 "client_id" => app.client_id,
201 "redirect_uri" => OAuthController.default_redirect_uri(app),
202 "state" => "a_state",
204 "email" => "john@doe.com"
209 assert response = html_response(conn, 200)
210 assert response =~ ~r/name="op" type="submit" value="register"/
211 assert response =~ ~r/name="op" type="submit" value="connect"/
214 test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`",
219 registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
220 redirect_uri = OAuthController.default_redirect_uri(app)
224 |> put_session(:registration_id, registration.id)
229 "authorization" => %{
230 "scopes" => app.scopes,
231 "client_id" => app.client_id,
232 "redirect_uri" => redirect_uri,
233 "state" => "a_state",
234 "nickname" => "availablenick",
235 "email" => "available@email.com"
240 assert response = html_response(conn, 302)
241 assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
244 test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in HTTP 401",
249 registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
250 unlisted_redirect_uri = "http://cross-site-request.com"
254 |> put_session(:registration_id, registration.id)
259 "authorization" => %{
260 "scopes" => app.scopes,
261 "client_id" => app.client_id,
262 "redirect_uri" => unlisted_redirect_uri,
263 "state" => "a_state",
264 "nickname" => "availablenick",
265 "email" => "available@email.com"
270 assert response = html_response(conn, 401)
273 test "with invalid params, POST /oauth/register?op=register renders registration_details page",
278 another_user = insert(:user)
279 registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil})
283 "authorization" => %{
284 "scopes" => app.scopes,
285 "client_id" => app.client_id,
286 "redirect_uri" => OAuthController.default_redirect_uri(app),
287 "state" => "a_state",
288 "nickname" => "availablenickname",
289 "email" => "available@email.com"
293 for {bad_param, bad_param_value} <-
294 [{"nickname", another_user.nickname}, {"email", another_user.email}] do
295 bad_registration_attrs = %{
296 "authorization" => Map.put(params["authorization"], bad_param, bad_param_value)
299 bad_params = Map.merge(params, bad_registration_attrs)
303 |> put_session(:registration_id, registration.id)
304 |> post("/oauth/register", bad_params)
306 assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/
307 assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken."
311 test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`",
316 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
317 registration = insert(:registration, user: nil)
318 redirect_uri = OAuthController.default_redirect_uri(app)
322 |> put_session(:registration_id, registration.id)
327 "authorization" => %{
328 "scopes" => app.scopes,
329 "client_id" => app.client_id,
330 "redirect_uri" => redirect_uri,
331 "state" => "a_state",
332 "name" => user.nickname,
333 "password" => "testpassword"
338 assert response = html_response(conn, 302)
339 assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
342 test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in HTTP 401`",
347 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword"))
348 registration = insert(:registration, user: nil)
349 unlisted_redirect_uri = "http://cross-site-request.com"
353 |> put_session(:registration_id, registration.id)
358 "authorization" => %{
359 "scopes" => app.scopes,
360 "client_id" => app.client_id,
361 "redirect_uri" => unlisted_redirect_uri,
362 "state" => "a_state",
363 "name" => user.nickname,
364 "password" => "testpassword"
369 assert response = html_response(conn, 401)
372 test "with invalid params, POST /oauth/register?op=connect renders registration_details page",
378 registration = insert(:registration, user: nil)
382 "authorization" => %{
383 "scopes" => app.scopes,
384 "client_id" => app.client_id,
385 "redirect_uri" => OAuthController.default_redirect_uri(app),
386 "state" => "a_state",
387 "name" => user.nickname,
388 "password" => "wrong password"
394 |> put_session(:registration_id, registration.id)
395 |> post("/oauth/register", params)
397 assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/
398 assert get_flash(conn, :error) == "Invalid Username/Password"
402 describe "GET /oauth/authorize" do
405 app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
408 |> Plug.Session.call(Plug.Session.init(@session_opts))
413 test "renders authentication page", %{app: app, conn: conn} do
419 "response_type" => "code",
420 "client_id" => app.client_id,
421 "redirect_uri" => OAuthController.default_redirect_uri(app),
426 assert html_response(conn, 200) =~ ~s(type="submit")
429 test "properly handles internal calls with `authorization`-wrapped params", %{
438 "authorization" => %{
439 "response_type" => "code",
440 "client_id" => app.client_id,
441 "redirect_uri" => OAuthController.default_redirect_uri(app),
447 assert html_response(conn, 200) =~ ~s(type="submit")
450 test "renders authentication page if user is already authenticated but `force_login` is tru-ish",
451 %{app: app, conn: conn} do
452 token = insert(:oauth_token, app_id: app.id)
456 |> put_session(:oauth_token, token.token)
460 "response_type" => "code",
461 "client_id" => app.client_id,
462 "redirect_uri" => OAuthController.default_redirect_uri(app),
464 "force_login" => "true"
468 assert html_response(conn, 200) =~ ~s(type="submit")
471 test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
476 token = insert(:oauth_token, app_id: app.id)
480 |> put_session(:oauth_token, token.token)
484 "response_type" => "code",
485 "client_id" => app.client_id,
486 "redirect_uri" => OAuthController.default_redirect_uri(app),
487 "state" => "specific_client_state",
492 assert URI.decode(redirected_to(conn)) ==
493 "https://redirect.url?access_token=#{token.token}&state=specific_client_state"
496 test "with existing authentication and unlisted non-OOB `redirect_uri`, redirects without credentials",
501 unlisted_redirect_uri = "http://cross-site-request.com"
502 token = insert(:oauth_token, app_id: app.id)
506 |> put_session(:oauth_token, token.token)
510 "response_type" => "code",
511 "client_id" => app.client_id,
512 "redirect_uri" => unlisted_redirect_uri,
513 "state" => "specific_client_state",
518 assert redirected_to(conn) == unlisted_redirect_uri
521 test "with existing authentication and OOB `redirect_uri`, redirects to app with `token` and `state` params",
526 token = insert(:oauth_token, app_id: app.id)
530 |> put_session(:oauth_token, token.token)
534 "response_type" => "code",
535 "client_id" => app.client_id,
536 "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
541 assert html_response(conn, 200) =~ "Authorization exists"
545 describe "POST /oauth/authorize" do
546 test "redirects with oauth authorization" do
548 app = insert(:oauth_app, scopes: ["read", "write", "follow"])
549 redirect_uri = OAuthController.default_redirect_uri(app)
553 |> post("/oauth/authorize", %{
554 "authorization" => %{
555 "name" => user.nickname,
556 "password" => "test",
557 "client_id" => app.client_id,
558 "redirect_uri" => redirect_uri,
559 "scope" => "read write",
560 "state" => "statepassed"
564 target = redirected_to(conn)
565 assert target =~ redirect_uri
567 query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
569 assert %{"state" => "statepassed", "code" => code} = query
570 auth = Repo.get_by(Authorization, token: code)
572 assert auth.scopes == ["read", "write"]
575 test "returns 401 for wrong credentials", %{conn: conn} do
577 app = insert(:oauth_app)
578 redirect_uri = OAuthController.default_redirect_uri(app)
582 |> post("/oauth/authorize", %{
583 "authorization" => %{
584 "name" => user.nickname,
585 "password" => "wrong",
586 "client_id" => app.client_id,
587 "redirect_uri" => redirect_uri,
588 "state" => "statepassed",
589 "scope" => Enum.join(app.scopes, " ")
592 |> html_response(:unauthorized)
595 assert result =~ app.client_id
596 assert result =~ redirect_uri
599 assert result =~ "Invalid Username/Password"
602 test "returns 401 for missing scopes", %{conn: conn} do
604 app = insert(:oauth_app)
605 redirect_uri = OAuthController.default_redirect_uri(app)
609 |> post("/oauth/authorize", %{
610 "authorization" => %{
611 "name" => user.nickname,
612 "password" => "test",
613 "client_id" => app.client_id,
614 "redirect_uri" => redirect_uri,
615 "state" => "statepassed",
619 |> html_response(:unauthorized)
622 assert result =~ app.client_id
623 assert result =~ redirect_uri
626 assert result =~ "This action is outside the authorized scopes"
629 test "returns 401 for scopes beyond app scopes", %{conn: conn} do
631 app = insert(:oauth_app, scopes: ["read", "write"])
632 redirect_uri = OAuthController.default_redirect_uri(app)
636 |> post("/oauth/authorize", %{
637 "authorization" => %{
638 "name" => user.nickname,
639 "password" => "test",
640 "client_id" => app.client_id,
641 "redirect_uri" => redirect_uri,
642 "state" => "statepassed",
643 "scope" => "read write follow"
646 |> html_response(:unauthorized)
649 assert result =~ app.client_id
650 assert result =~ redirect_uri
653 assert result =~ "This action is outside the authorized scopes"
657 describe "POST /oauth/token" do
658 test "issues a token for an all-body request" do
660 app = insert(:oauth_app, scopes: ["read", "write"])
662 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
666 |> post("/oauth/token", %{
667 "grant_type" => "authorization_code",
668 "code" => auth.token,
669 "redirect_uri" => OAuthController.default_redirect_uri(app),
670 "client_id" => app.client_id,
671 "client_secret" => app.client_secret
674 assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200)
676 token = Repo.get_by(Token, token: token)
678 assert token.scopes == auth.scopes
679 assert user.ap_id == ap_id
682 test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
683 password = "testpassword"
684 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
686 app = insert(:oauth_app, scopes: ["read", "write"])
688 # Note: "scope" param is intentionally omitted
691 |> post("/oauth/token", %{
692 "grant_type" => "password",
693 "username" => user.nickname,
694 "password" => password,
695 "client_id" => app.client_id,
696 "client_secret" => app.client_secret
699 assert %{"access_token" => token} = json_response(conn, 200)
701 token = Repo.get_by(Token, token: token)
703 assert token.scopes == app.scopes
706 test "issues a token for request with HTTP basic auth client credentials" do
708 app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])
710 {:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"])
711 assert auth.scopes == ["scope1", "scope2"]
714 (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
719 |> put_req_header("authorization", "Basic " <> app_encoded)
720 |> post("/oauth/token", %{
721 "grant_type" => "authorization_code",
722 "code" => auth.token,
723 "redirect_uri" => OAuthController.default_redirect_uri(app)
726 assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
728 assert scope == "scope1 scope2"
730 token = Repo.get_by(Token, token: token)
732 assert token.scopes == ["scope1", "scope2"]
735 test "issue a token for client_credentials grant type" do
736 app = insert(:oauth_app, scopes: ["read", "write"])
740 |> post("/oauth/token", %{
741 "grant_type" => "client_credentials",
742 "client_id" => app.client_id,
743 "client_secret" => app.client_secret
746 assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
747 json_response(conn, 200)
750 token_from_db = Repo.get_by(Token, token: token)
753 assert scope == "read write"
756 test "rejects token exchange with invalid client credentials" do
758 app = insert(:oauth_app)
760 {:ok, auth} = Authorization.create_authorization(app, user)
764 |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=")
765 |> post("/oauth/token", %{
766 "grant_type" => "authorization_code",
767 "code" => auth.token,
768 "redirect_uri" => OAuthController.default_redirect_uri(app)
771 assert resp = json_response(conn, 400)
772 assert %{"error" => _} = resp
773 refute Map.has_key?(resp, "access_token")
776 test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
777 Pleroma.Config.put([:instance, :account_activation_required], true)
779 password = "testpassword"
780 user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
781 info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true)
785 |> Ecto.Changeset.change()
786 |> Ecto.Changeset.put_embed(:info, info_change)
789 refute Pleroma.User.auth_active?(user)
791 app = insert(:oauth_app)
795 |> post("/oauth/token", %{
796 "grant_type" => "password",
797 "username" => user.nickname,
798 "password" => password,
799 "client_id" => app.client_id,
800 "client_secret" => app.client_secret
803 assert resp = json_response(conn, 403)
804 assert %{"error" => _} = resp
805 refute Map.has_key?(resp, "access_token")
808 test "rejects token exchange for valid credentials belonging to deactivated user" do
809 password = "testpassword"
813 password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
814 info: %{deactivated: true}
817 app = insert(:oauth_app)
821 |> post("/oauth/token", %{
822 "grant_type" => "password",
823 "username" => user.nickname,
824 "password" => password,
825 "client_id" => app.client_id,
826 "client_secret" => app.client_secret
829 assert resp = json_response(conn, 403)
830 assert %{"error" => _} = resp
831 refute Map.has_key?(resp, "access_token")
834 test "rejects token exchange for user with password_reset_pending set to true" do
835 password = "testpassword"
839 password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
840 info: %{password_reset_pending: true}
843 app = insert(:oauth_app, scopes: ["read", "write"])
847 |> post("/oauth/token", %{
848 "grant_type" => "password",
849 "username" => user.nickname,
850 "password" => password,
851 "client_id" => app.client_id,
852 "client_secret" => app.client_secret
855 assert resp = json_response(conn, 403)
857 assert resp["error"] == "Password reset is required"
858 refute Map.has_key?(resp, "access_token")
861 test "rejects an invalid authorization code" do
862 app = insert(:oauth_app)
866 |> post("/oauth/token", %{
867 "grant_type" => "authorization_code",
868 "code" => "Imobviouslyinvalid",
869 "redirect_uri" => OAuthController.default_redirect_uri(app),
870 "client_id" => app.client_id,
871 "client_secret" => app.client_secret
874 assert resp = json_response(conn, 400)
875 assert %{"error" => _} = json_response(conn, 400)
876 refute Map.has_key?(resp, "access_token")
880 describe "POST /oauth/token - refresh token" do
881 clear_config([:oauth2, :issue_new_refresh_token])
883 test "issues a new access token with keep fresh token" do
884 Pleroma.Config.put([:oauth2, :issue_new_refresh_token], true)
886 app = insert(:oauth_app, scopes: ["read", "write"])
888 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
889 {:ok, token} = Token.exchange_token(app, auth)
893 |> post("/oauth/token", %{
894 "grant_type" => "refresh_token",
895 "refresh_token" => token.refresh_token,
896 "client_id" => app.client_id,
897 "client_secret" => app.client_secret
899 |> json_response(200)
906 "token_type" => "Bearer",
909 "refresh_token" => _,
915 refute Repo.get_by(Token, token: token.token)
916 new_token = Repo.get_by(Token, token: response["access_token"])
917 assert new_token.refresh_token == token.refresh_token
918 assert new_token.scopes == auth.scopes
919 assert new_token.user_id == user.id
920 assert new_token.app_id == app.id
923 test "issues a new access token with new fresh token" do
924 Pleroma.Config.put([:oauth2, :issue_new_refresh_token], false)
926 app = insert(:oauth_app, scopes: ["read", "write"])
928 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
929 {:ok, token} = Token.exchange_token(app, auth)
933 |> post("/oauth/token", %{
934 "grant_type" => "refresh_token",
935 "refresh_token" => token.refresh_token,
936 "client_id" => app.client_id,
937 "client_secret" => app.client_secret
939 |> json_response(200)
946 "token_type" => "Bearer",
949 "refresh_token" => _,
955 refute Repo.get_by(Token, token: token.token)
956 new_token = Repo.get_by(Token, token: response["access_token"])
957 refute new_token.refresh_token == token.refresh_token
958 assert new_token.scopes == auth.scopes
959 assert new_token.user_id == user.id
960 assert new_token.app_id == app.id
963 test "returns 400 if we try use access token" do
965 app = insert(:oauth_app, scopes: ["read", "write"])
967 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
968 {:ok, token} = Token.exchange_token(app, auth)
972 |> post("/oauth/token", %{
973 "grant_type" => "refresh_token",
974 "refresh_token" => token.token,
975 "client_id" => app.client_id,
976 "client_secret" => app.client_secret
978 |> json_response(400)
980 assert %{"error" => "Invalid credentials"} == response
983 test "returns 400 if refresh_token invalid" do
984 app = insert(:oauth_app, scopes: ["read", "write"])
988 |> post("/oauth/token", %{
989 "grant_type" => "refresh_token",
990 "refresh_token" => "token.refresh_token",
991 "client_id" => app.client_id,
992 "client_secret" => app.client_secret
994 |> json_response(400)
996 assert %{"error" => "Invalid credentials"} == response
999 test "issues a new token if token expired" do
1000 user = insert(:user)
1001 app = insert(:oauth_app, scopes: ["read", "write"])
1003 {:ok, auth} = Authorization.create_authorization(app, user, ["write"])
1004 {:ok, token} = Token.exchange_token(app, auth)
1007 Ecto.Changeset.change(
1009 %{valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -86_400 * 30)}
1012 {:ok, access_token} = Repo.update(change)
1016 |> post("/oauth/token", %{
1017 "grant_type" => "refresh_token",
1018 "refresh_token" => access_token.refresh_token,
1019 "client_id" => app.client_id,
1020 "client_secret" => app.client_secret
1022 |> json_response(200)
1029 "token_type" => "Bearer",
1030 "expires_in" => 600,
1031 "access_token" => _,
1032 "refresh_token" => _,
1038 refute Repo.get_by(Token, token: token.token)
1039 token = Repo.get_by(Token, token: response["access_token"])
1041 assert token.scopes == auth.scopes
1042 assert token.user_id == user.id
1043 assert token.app_id == app.id
1047 describe "POST /oauth/token - bad request" do
1048 test "returns 500" do
1051 |> post("/oauth/token", %{})
1052 |> json_response(500)
1054 assert %{"error" => "Bad request"} == response
1058 describe "POST /oauth/revoke - bad request" do
1059 test "returns 500" do
1062 |> post("/oauth/revoke", %{})
1063 |> json_response(500)
1065 assert %{"error" => "Bad request"} == response