fix: Use request body for user creation and update endpoints

Stop using query parameters for user creation and update endpoints in
the API. Instead, use the request body to pass user data.

This change stops leaking sensitive information like passwords in the
URL.

Fixes #2010
This commit is contained in:
Michael Manganiello
2025-06-24 10:10:02 -03:00
parent b274cec8d2
commit b1ba322408
4 changed files with 30 additions and 39 deletions

View File

@@ -1,25 +1,16 @@
from fastapi import UploadFile
from fastapi.param_functions import Form
from pydantic import BaseModel
class UserForm:
def __init__(
self,
username: str | None = None,
password: str | None = None,
email: str | None = None,
role: str | None = None,
enabled: bool | None = None,
avatar: UploadFile | None = None,
ra_username: str | None = None,
):
self.username = username
self.password = password
self.email = email
self.role = role
self.enabled = enabled
self.avatar = avatar
self.ra_username = ra_username
class UserForm(BaseModel):
username: str | None = None
password: str | None = None
email: str | None = None
role: str | None = None
enabled: bool | None = None
ra_username: str | None = None
avatar: UploadFile | None = None
class OAuth2RequestForm:

View File

@@ -71,7 +71,7 @@ def test_get_user(client, access_token, editor_user):
def test_add_user_from_admin_user(client, access_token, new_user_role):
response = client.post(
"/api/users",
params={
json={
"username": "new_user",
"password": "new_user_password",
"email": "new_user@example.com",
@@ -122,7 +122,7 @@ def test_add_user_from_unauthorized_user(
):
response = client.post(
"/api/users",
params={
json={
"username": "new_user",
"password": "new_user_password",
"email": "new_user@example.com",
@@ -136,7 +136,7 @@ def test_add_user_from_unauthorized_user(
def test_add_user_with_existing_username(client, access_token, admin_user):
response = client.post(
"/api/users",
params={
json={
"username": admin_user.username,
"password": "new_user_password",
"email": "new_user@example.com",
@@ -155,7 +155,7 @@ def test_update_user(client, access_token, editor_user):
response = client.put(
f"/api/users/{editor_user.id}",
params={"username": "editor_user_new_username", "role": "viewer"},
data={"username": "editor_user_new_username", "role": "viewer"},
headers={"Authorization": f"Bearer {access_token}"},
)
assert response.status_code == 200

View File

@@ -8,7 +8,7 @@ from decorators.auth import protected_route
from endpoints.forms.identity import UserForm
from endpoints.responses import MessageResponse
from endpoints.responses.identity import InviteLinkSchema, UserSchema
from fastapi import Body, Depends, HTTPException, Request, status
from fastapi import Body, Form, HTTPException, Request, status
from handler.auth import auth_handler
from handler.auth.constants import Scope
from handler.database import db_user_handler
@@ -31,7 +31,11 @@ router = APIRouter(
status_code=status.HTTP_201_CREATED,
)
def add_user(
request: Request, username: str, password: str, email: str, role: str
request: Request,
username: str = Body(..., embed=True),
email: str = Body(..., embed=True),
password: str = Body(..., embed=True),
role: str = Body(..., embed=True),
) -> UserSchema:
"""Create user endpoint
@@ -219,7 +223,7 @@ def get_user(request: Request, id: int) -> UserSchema:
@protected_route(router.put, "/{id}", [Scope.ME_WRITE])
async def update_user(
request: Request, id: int, form_data: Annotated[UserForm, Depends()]
request: Request, id: int, form_data: Annotated[UserForm, Form()]
) -> UserSchema:
"""Update user endpoint

View File

@@ -19,11 +19,7 @@ async function createUser({
email: string;
role: string;
}): Promise<{ data: UserSchema }> {
return api.post(
"/users",
{},
{ params: { username, password, email, role } },
);
return api.post("/users", { username, password, email, role });
}
async function createInviteLink({
@@ -67,18 +63,18 @@ async function updateUser({
`/users/${id}`,
{
avatar: avatar || null,
username: attrs.username,
password: attrs.password,
email: attrs.email,
enabled: attrs.enabled,
role: attrs.role,
ra_username: attrs.ra_username,
},
{
headers: {
"Content-Type": avatar ? "multipart/form-data" : "application/json",
},
params: {
username: attrs.username,
password: attrs.password,
email: attrs.email,
enabled: attrs.enabled,
role: attrs.role,
ra_username: attrs.ra_username,
"Content-Type": avatar
? "multipart/form-data"
: "application/x-www-form-urlencoded",
},
},
);