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:
- 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.
- 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 source — add_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
- Create a project — web UI or
POST /v1/project/{namespace}; both reproduce it.
- 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;
Summary
add_project()never grants the creator a role on the new project. No row is written toproject_memberat all, so the project ends up with no roles whatsoever.Consequences:
anyone — including the owner adding themselves. This was the presenting symptom for us.
GLOBAL_ADMIN=True, the creator cannot see their own project.Environment
lutraconsulting/merginmaps-backend:2025.7.3(self-reports2025.6.2, see #548)GLOBAL_READ/GLOBAL_WRITE/GLOBAL_ADMINall unset (defaultFalse)Verified still present in the current source —
add_project()is structurally unchanged at2025.7.3,2025.8.0,2025.8.1,2025.8.2,2026.4.2,2026.5.0,2026.6.0and2026.6.2.None of them calls
set_role.Steps to reproduce
POST /v1/project/{namespace}; both reproduce it.Expected: the creator holds
owner.Actual: no rows.
Observed on two projects created by two different users through two different paths:
Two older projects on the same server do have
admin:ownerrows — they were migrated in fromanother host with their rows already present, which is why this went unnoticed at first.
Why the creator loses visibility
GlobalWorkspaceHandler.get_user_role()returnsOWNERforis_adminusers andGUESTforeveryone else when the
GLOBAL_*settings are at their defaults. AGUESTreaches a project onlyvia
project_memberorProject.public— so a non-superuser who creates a project immediatelycannot see it.
Reproduced with a non-superuser that had been granted create rights:
flag=createdfilters onProject.creator_id == user.idand the user is the creator, butprojects_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 withcreator=current_user, adds aProjectVersion, and commits — but never callsset_role.Project.__init__(sync/models.py) setsself.creatorand does not call it either. The onlycallers of
set_roleare the explicit collaborator endpoints inpublic_api_v2_controller.py.Suggested fix
In
add_project(), beforedb.session.commit():A data migration granting
ownertocreator_idfor existing projects that have noproject_memberrows would repair servers already affected.I'm happy to open a PR for this if it would be useful.
Workaround