Skip to content

Allow users to view admin-owned websites#4330

Open
fuyalan126 wants to merge 1 commit into
umami-software:masterfrom
fuyalan126:codex/admin-websites-visible-to-users
Open

Allow users to view admin-owned websites#4330
fuyalan126 wants to merge 1 commit into
umami-software:masterfrom
fuyalan126:codex/admin-websites-visible-to-users

Conversation

@fuyalan126

@fuyalan126 fuyalan126 commented Jun 9, 2026

Copy link
Copy Markdown

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Umami Software Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR allows regular users to view websites owned by admin accounts by adding a new getUserWebsitesIncludingAdminOwned query, an isAdminOwnedWebsite helper, and corresponding permission changes to canViewWebsite. The implementation broadens access across all three website listing routes.

  • The new query and permission check expose every admin-owned website to every authenticated user — ownership is determined solely by the owner's role, with no per-website sharing flag or explicit grant, meaning a multi-admin deployment leaks all admins' private analytics to all users.
  • getAllUserWebsitesIncludingTeamOwner (used when includeTeams=true) was also widened to include all admin accounts' websites, affecting both admin and non-admin callers of that path.
  • canViewWebsite now short-circuits the owner/team check with isAdminOwnedWebsite, granting any logged-in user direct read access to any admin website by ID without any relationship check.

Confidence Score: 2/5

Not safe to merge as-is — the authorization model change is broader than the PR title implies and may expose private admin website data to all users.

The core change grants every authenticated user unconditional read access to every website owned by any admin account. In a single-admin setup this may be acceptable, but in any multi-admin deployment it silently exposes one admin's private analytics to every other user. The implementation has no opt-in mechanism (no isPublic flag, no explicit share, no per-user grant), so there is no way to create a private admin website after this change lands.

src/permissions/website.ts and src/queries/prisma/website.ts contain the core authorization logic that needs the most scrutiny before merging.

Important Files Changed

Filename Overview
src/queries/prisma/website.ts Adds isAdminOwnedWebsite helper and getUserWebsitesIncludingAdminOwned query; both expose every admin-owned website to every authenticated user with no per-website scope control; also widens getAllUserWebsitesIncludingTeamOwner to include all admin accounts' websites.
src/permissions/website.ts canViewWebsite now grants any authenticated user view access to any admin-owned website, bypassing the existing owner/team-member check with no sharing intent verification.
src/app/api/me/websites/route.ts Swaps getUserWebsites for getUserWebsitesIncludingAdminOwned in the non-team fallback; change is clean but inherits the authorization concerns from the query layer.
src/app/api/users/[userId]/websites/route.ts Same query swap as me/websites; the caller-authorization guard (admin or self) is correct but the returned dataset is now broadened to all admin websites.
src/app/api/websites/route.ts Identical query swap; route logic unchanged, broadened dataset inherited from query layer.

Sequence Diagram

sequenceDiagram
    participant User as Regular User
    participant Route as /api/me/websites (GET)
    participant Query as getUserWebsitesIncludingAdminOwned
    participant DB as Database

    User->>Route: GET /api/me/websites
    Route->>Query: getUserWebsitesIncludingAdminOwned(userId)
    Query->>DB: "WHERE userId=X OR user.role='admin'"
    DB-->>Query: User's own websites + ALL admin-owned websites
    Query-->>Route: Combined result set
    Route-->>User: Returns admin websites from any/all admins

    Note over User,DB: canViewWebsite path (direct access by ID)
    User->>Route: GET /api/websites/[adminWebsiteId]
    Route->>+Route: canViewWebsite(auth, adminWebsiteId)
    Route->>DB: isAdminOwnedWebsite(adminWebsiteId)
    DB-->>Route: true (owner has admin role)
    Route-->>User: 200 OK — grants access to any admin website
Loading

Comments Outside Diff (1)

  1. src/queries/prisma/website.ts, line 63-94 (link)

    P1 getAllUserWebsitesIncludingTeamOwner now exposes websites from all admins

    The new OR arm { user: { role: ROLES.admin } } was added here without narrowing to { userId } — so this function now returns websites owned by every admin in the system, not just the requesting user's own websites. This function is called when includeTeams=true is passed, which is available to any authenticated user (not gated on auth.user.isAdmin in the calling routes). An admin user viewing their own includeTeams list will also see websites that belong to other admin accounts, which is a silent data exposure across admin boundaries.

Reviews (1): Last reviewed commit: "Allow users to view admin-owned websites" | Re-trigger Greptile

Comment on lines 29 to +34
if (entity.userId) {
return user.id === entity.userId;
if (user.id === entity.userId) {
return true;
}

return isAdminOwnedWebsite(websiteId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Blanket view grant for all admin-owned websites

isAdminOwnedWebsite only checks whether the website's owner has the admin role — it does not verify any relationship between the requesting user and that admin. Any authenticated non-admin user can now directly view the data of every website owned by any admin account simply by knowing (or guessing) its ID. There is no per-website opt-in or per-admin sharing intent captured here; if an admin has websites intended to remain private, this check will still return true and grant access to all authenticated users.

Comment on lines +118 to +146
export async function getUserWebsitesIncludingAdminOwned(userId: string, filters?: QueryFilters) {
return getWebsites(
{
where: {
OR: [
{ userId },
{
user: {
role: ROLES.admin,
deletedAt: null,
},
},
],
},
include: {
user: {
select: {
username: true,
id: true,
},
},
},
},
{
orderBy: 'name',
...filters,
},
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 All admin websites leaked to every user's listing

getUserWebsitesIncludingAdminOwned returns websites owned by userId OR owned by any user with the admin role — without any relationship to the caller. In a multi-admin deployment every non-admin user now receives the full website list of every admin account in the system (not just the "system" admin that may have set up shared websites). Combined with the permission change in canViewWebsite, this means one admin's private analytics data is unconditionally visible to all users. If the intent is to share only certain admin-managed websites, a marker on the website (e.g. isPublic / sharedWithAll) or an explicit team membership would be safer than a blanket role-based filter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant