Skip to content

Newly created project gets no project_member row — creator holds no role, sharing is impossible #659

Description

@silentsudo-io

Summary

add_project() never grants the creator a role on the new project. No row is written to
project_member at all, so the project ends up with no roles whatsoever.

Consequences:

  1. Sharing is broken. With no roles on the project, the web UI's Collaborators page cannot add
    anyone — including the owner adding themselves. This was the presenting symptom for us.
  2. With GLOBAL_ADMIN=True, the creator cannot see their own project.

Environment

Image lutraconsulting/merginmaps-backend:2025.7.3 (self-reports 2025.6.2, see #548)
Deployment self-hosted CE, Docker Compose, single global workspace
Config GLOBAL_READ / GLOBAL_WRITE / GLOBAL_ADMIN all unset (default False)

Verified still present in the current sourceadd_project() is structurally unchanged at
2025.7.3, 2025.8.0, 2025.8.1, 2025.8.2, 2026.4.2, 2026.5.0, 2026.6.0 and 2026.6.2.
None of them calls set_role.

Steps to reproduce

  1. Create a project — web UI or POST /v1/project/{namespace}; both reproduce it.
  2. Query the database:
SELECT p.name, u.username, pm.role
FROM project p
LEFT JOIN project_member pm ON pm.project_id = p.id
LEFT JOIN "user" u ON u.id = pm.user_id
WHERE p.name = '<new project>';

Expected: the creator holds owner.
Actual: no rows.

Observed on two projects created by two different users through two different paths:

schema-change-test | (NO MEMBERS)     <- created in the web UI by a superuser
list-refresh-test  | (NO MEMBERS)     <- created via the REST API by a non-superuser

Two older projects on the same server do have admin:owner rows — they were migrated in from
another host with their rows already present, which is why this went unnoticed at first.

Why the creator loses visibility

GlobalWorkspaceHandler.get_user_role() returns OWNER for is_admin users and GUEST for
everyone else when the GLOBAL_* settings are at their defaults. A GUEST reaches a project only
via project_member or Project.public — so a non-superuser who creates a project immediately
cannot see it.

Reproduced with a non-superuser that had been granted create rights:

GET /v1/project/paginated?flag=created   ->  count=0

flag=created filters on Project.creator_id == user.id and the user is the creator, but
projects_query(ProjectPermissions.Read) discards the project before the creator test is reached.

Cause

server/mergin/sync/public_api_controller.py, add_project() builds the project with
creator=current_user, adds a ProjectVersion, and commits — but never calls set_role.
Project.__init__ (sync/models.py) sets self.creator and does not call it either. The only
callers of set_role are the explicit collaborator endpoints in public_api_v2_controller.py.

Suggested fix

In add_project(), before db.session.commit():

p.set_role(current_user.id, ProjectRole.OWNER)

A data migration granting owner to creator_id for existing projects that have no
project_member rows would repair servers already affected.

I'm happy to open a PR for this if it would be useful.

Workaround

INSERT INTO project_member (project_id, user_id, role)
SELECT p.id, u.id, 'owner'::project_role
FROM project p JOIN "user" u ON u.username = '<owner>'
WHERE p.name = '<project>'
ON CONFLICT DO NOTHING;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions