diff --git a/apps/e2e/src/tests/demo-role.test.ts b/apps/e2e/src/tests/demo-role.test.ts index e7cf1ff92..871fbe345 100644 --- a/apps/e2e/src/tests/demo-role.test.ts +++ b/apps/e2e/src/tests/demo-role.test.ts @@ -132,7 +132,7 @@ describe.skipIf(!hasSqlite3())('a published demo', () => { ]; for (const r of refused) { expect(r.status, JSON.stringify(r.json)).toBe(403); - expect(r.json.error).toMatch(/read-only demo/); + expect(r.json.error).toMatch(/read-only/); } // A write SQL statement is refused by the permissions the Demo role lacks. const conn = (await api('GET', '/connections', undefined, demoCookie)).json.connections.find( diff --git a/apps/web/src/frontend/app/shell/DemoBanner.tsx b/apps/web/src/frontend/app/shell/DemoBanner.tsx index e0a892c8f..c58d72750 100644 --- a/apps/web/src/frontend/app/shell/DemoBanner.tsx +++ b/apps/web/src/frontend/app/shell/DemoBanner.tsx @@ -8,12 +8,12 @@ */ import React from 'react'; import { Eye } from 'lucide-react'; -import { isDemoRole } from '@foxschema/shared'; import { useAuthStore } from '@/platform/authStore'; export const DemoBanner: React.FC = () => { const demo = useAuthStore((s) => s.demo); - const role = useAuthStore((s) => s.user?.role); + /** Read-only: the account cannot save (the Demo role, or any role without app.write). */ + const readOnly = useAuthStore((s) => s.user != null && !s.can('app.write')); if (!demo?.on) return null; return (
{ Demo - {isDemoRole(role) + {readOnly ? 'Explore the sample databases: browse, compare, and run queries. Nothing here can be saved or changed.' : 'Demo mode is on: visitors see this banner, and the sign-in page shows the demo login.'} diff --git a/apps/web/src/frontend/app/shell/ProfileMenu.tsx b/apps/web/src/frontend/app/shell/ProfileMenu.tsx index 6cc55d790..4ab4f8625 100644 --- a/apps/web/src/frontend/app/shell/ProfileMenu.tsx +++ b/apps/web/src/frontend/app/shell/ProfileMenu.tsx @@ -1,5 +1,4 @@ import React, { lazy, useEffect, useRef, useState } from 'react'; -import { isDemoRole } from '@foxschema/shared'; import { createPortal } from 'react-dom'; import { LogOut, Palette, ArrowUpCircle, Globe, Shield, MonitorX, MessageSquarePlus, UserPlus } from 'lucide-react'; import { apiSignOutOthers } from '@/shared/api/authApi'; @@ -23,6 +22,8 @@ export function ProfileMenu(): React.ReactElement | null { const { user, logout, launch } = useAuthStore(); const setActiveView = useUiStore((s) => s.setActiveView); const canAdminAccess = useAuthStore((s) => s.can('admin.users') || s.can('admin.roles')); + /** A shared login (the Demo role) does not run its own account; the server refuses it too. */ + const managesOwnAccount = useAuthStore((s) => s.can('account.self')); const setRegisterDialogOpen = useAuthStore((s) => s.setRegisterDialogOpen); const [open, setOpen] = useState(false); const [showAdmin, setShowAdmin] = useState(false); @@ -159,8 +160,8 @@ export function ProfileMenu(): React.ReactElement | null { foxschema.com - {/* A demo login is every visitor at once; the server refuses this for it. */} - {!isDemoRole(user?.role) && ( + {/* A shared login is every visitor at once; the server refuses this for it. */} + {managesOwnAccount && (