From c168c49d0c4a5102ec9bf6488e3a386469399e48 Mon Sep 17 00:00:00 2001 From: Martin Varga Date: Thu, 9 Jan 2025 12:52:40 +0100 Subject: [PATCH 1/3] Fix CSRF issue for public endpoint --- server/mergin/app.py | 2 +- server/mergin/auth/controller.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/mergin/app.py b/server/mergin/app.py index d025b6e2..86c72d74 100644 --- a/server/mergin/app.py +++ b/server/mergin/app.py @@ -252,7 +252,7 @@ def custom_protect(): _get_csrf_token = csrf._get_csrf_token def get_csrf_token(): - if request.path.startswith("/v1/"): + if request.path.startswith("/v1/") or request.path.startswith("/v2/"): for header_name in app.app.config["WTF_CSRF_HEADERS"]: csrf_token = request.headers.get(header_name) if csrf_token: diff --git a/server/mergin/auth/controller.py b/server/mergin/auth/controller.py index 8b693ed3..24e1e631 100644 --- a/server/mergin/auth/controller.py +++ b/server/mergin/auth/controller.py @@ -510,7 +510,9 @@ def create_user(): username = request.json.get( "username", User.generate_username(request.json["email"]) ) - form = UserRegistrationForm() + + # in public endpoint we want to disable form csrf - for browser clients endpoint is protected anyway + form = UserRegistrationForm(meta={'csrf': False}) form.confirm.data = form.password.data form.username.data = username if not form.validate(): From 73d45c5712a7596992a2b4c0c9894ddd23288b20 Mon Sep 17 00:00:00 2001 From: Martin Varga Date: Thu, 9 Jan 2025 13:37:24 +0100 Subject: [PATCH 2/3] Fix collaborators API interface according to specification --- server/mergin/sync/public_api_v2.yaml | 4 ++-- server/mergin/sync/public_api_v2_controller.py | 2 +- server/mergin/tests/test_public_api_v2.py | 2 +- web-app/packages/lib/src/modules/project/store.ts | 4 ++-- web-app/packages/lib/src/modules/project/types.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/mergin/sync/public_api_v2.yaml b/server/mergin/sync/public_api_v2.yaml index bd4b1c0c..3d47ca4e 100644 --- a/server/mergin/sync/public_api_v2.yaml +++ b/server/mergin/sync/public_api_v2.yaml @@ -136,10 +136,10 @@ paths: schema: type: object required: - - username + - user - role properties: - username: + user: type: string example: john.doe description: username or email diff --git a/server/mergin/sync/public_api_v2_controller.py b/server/mergin/sync/public_api_v2_controller.py index 30b2bcb8..9f290fb2 100644 --- a/server/mergin/sync/public_api_v2_controller.py +++ b/server/mergin/sync/public_api_v2_controller.py @@ -93,7 +93,7 @@ def get_project_collaborators(id): def add_project_collaborator(id): """Add project collaborator""" project = require_project_by_uuid(id, ProjectPermissions.Update) - user = User.get_by_login(request.json["username"]) + user = User.get_by_login(request.json["user"]) if not user: abort(404) diff --git a/server/mergin/tests/test_public_api_v2.py b/server/mergin/tests/test_public_api_v2.py index 6dc96610..a17d1696 100644 --- a/server/mergin/tests/test_public_api_v2.py +++ b/server/mergin/tests/test_public_api_v2.py @@ -90,7 +90,7 @@ def test_project_members(client): assert response.status_code == 404 # add direct access - response = client.post(url, json={"role": role, "username": user.email}) + response = client.post(url, json={"role": role, "user": user.email}) assert response.status_code == 201 assert response.json["id"] == user.id assert response.json["project_role"] == role diff --git a/web-app/packages/lib/src/modules/project/store.ts b/web-app/packages/lib/src/modules/project/store.ts index e9b1f4e4..ed8034c9 100644 --- a/web-app/packages/lib/src/modules/project/store.ts +++ b/web-app/packages/lib/src/modules/project/store.ts @@ -811,7 +811,7 @@ export const useProjectStore = defineStore('projectModule', { if (!payload.access.project_role) { await ProjectApi.addProjectCollaborator(payload.projectId, { ...payload.data, - username: payload.access.username + user: payload.access.username }) } else { await ProjectApi.updateProjectCollaborator( @@ -844,7 +844,7 @@ export const useProjectStore = defineStore('projectModule', { if (!payload.collaborator.project_role) { await ProjectApi.addProjectCollaborator(payload.projectId, { ...payload.data, - username: payload.collaborator.username + user: payload.collaborator.username }) } else { await ProjectApi.updateProjectCollaborator( diff --git a/web-app/packages/lib/src/modules/project/types.ts b/web-app/packages/lib/src/modules/project/types.ts index be6dc0dc..2e3ca0f2 100644 --- a/web-app/packages/lib/src/modules/project/types.ts +++ b/web-app/packages/lib/src/modules/project/types.ts @@ -295,7 +295,7 @@ export type EnhancedProjectDetail = ProjectDetail & { export interface AddProjectCollaboratorPayload { role: ProjectRoleName - username: string + user: string } export interface UpdateProjectCollaboratorPayload { From 61313dcd73ae77c654d71b4d5eeddc8c2c77079f Mon Sep 17 00:00:00 2001 From: Martin Varga Date: Thu, 9 Jan 2025 14:28:19 +0100 Subject: [PATCH 3/3] black --- server/mergin/auth/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/mergin/auth/controller.py b/server/mergin/auth/controller.py index 24e1e631..106d30bd 100644 --- a/server/mergin/auth/controller.py +++ b/server/mergin/auth/controller.py @@ -512,7 +512,7 @@ def create_user(): ) # in public endpoint we want to disable form csrf - for browser clients endpoint is protected anyway - form = UserRegistrationForm(meta={'csrf': False}) + form = UserRegistrationForm(meta={"csrf": False}) form.confirm.data = form.password.data form.username.data = username if not form.validate():