Merge pull request 'OAuth: require security scopes for /api/oauth_tokens' (#8013) from lambadalambda/pleroma:fix/oauth-tokens-scopes into develop

Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/8013
This commit is contained in:
lain 2026-10-27 07:08:34 +00:00
commit 745926189f
4 changed files with 50 additions and 22 deletions

View file

@ -0,0 +1 @@
Require the read:security and write:security scopes to list and revoke OAuth tokens (HTTP Basic auth no longer works for these endpoints)

View file

@ -780,6 +780,7 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
### Retrieve a list of active sessions for the user
* Method: `GET`
* Authentication: required
* OAuth scope: `read:security`
* Params: none
* Response: JSON
* Example response:
@ -808,8 +809,9 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
### Revoke a user session by its ID
* Method: `DELETE`
* Authentication: required
* OAuth scope: `write:security`
* Params: none
* Response: HTTP 200 on success, 500 on error
* Response: HTTP 201 on success, 500 on error
## `/api/v1/pleroma/settings/:app`
### Gets settings for some application

View file

@ -13,7 +13,8 @@ defmodule Pleroma.Web.OAuth.TokenController do
require Logger
plug(:skip_auth when action == :confirm_email)
plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action == :oauth_tokens)
plug(OAuthScopesPlug, %{scopes: ["write:security"]} when action == :revoke_token)
action_fallback(:errors)

View file

@ -52,36 +52,60 @@ defmodule Pleroma.Web.OAuth.TokenControllerTest do
end
describe "GET /api/oauth_tokens" do
setup do
token = insert(:oauth_token) |> Repo.preload(:user)
%{token: token}
end
test "renders list", %{token: token} do
response =
build_conn()
|> assign(:user, token.user)
|> get("/api/oauth_tokens")
test "renders list" do
%{conn: conn} = oauth_access(["read:security"])
keys =
json_response(response, 200)
conn
|> get("/api/oauth_tokens")
|> json_response(200)
|> hd()
|> Map.keys()
assert Enum.sort(keys) == Enum.sort(["id", "app_name", "valid_until", "scopes"])
end
test "revoke token", %{token: token} do
response =
build_conn()
|> assign(:user, token.user)
|> delete("/api/oauth_tokens/#{token.id}")
test "is allowed with the read scope" do
%{conn: conn} = oauth_access(["read"])
tokens = Token.get_user_tokens(token.user)
assert [_] = conn |> get("/api/oauth_tokens") |> json_response(200)
end
assert tokens == []
assert response.status == 201
test "is forbidden without read:security" do
%{conn: conn} = oauth_access(["profile", "read:accounts", "write:security"])
assert %{"error" => "Insufficient permissions: read:security."} =
conn |> get("/api/oauth_tokens") |> json_response(403)
end
end
describe "DELETE /api/oauth_tokens/:id" do
test "revokes the token" do
%{conn: conn, user: user, token: token} = oauth_access(["write:security"])
other = insert(:oauth_token, user: user)
conn = delete(conn, "/api/oauth_tokens/#{other.id}")
assert conn.status == 201
assert [%Token{id: id}] = Token.get_user_tokens(user)
assert id == token.id
end
test "is allowed with the write scope" do
%{conn: conn, user: user} = oauth_access(["write"])
other = insert(:oauth_token, user: user)
assert delete(conn, "/api/oauth_tokens/#{other.id}").status == 201
end
test "is forbidden without write:security" do
%{conn: conn, user: user} = oauth_access(["profile", "read", "write:accounts"])
other = insert(:oauth_token, user: user)
assert %{"error" => "Insufficient permissions: write:security."} =
conn |> delete("/api/oauth_tokens/#{other.id}") |> json_response(403)
assert length(Token.get_user_tokens(user)) == 2
end
end
end