feat(core): add allowGuestDemoWorkspace flag to force login (#12779)

https://github.com/user-attachments/assets/41a659c9-6def-4492-be8e-5910eb148d6f

This PR enforces login‑first access (#8716) by disabling or enabling the
guest demo workspace via Admin Server Client Page and redirecting
unauthenticated users straight to `/sign‑in`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a configuration option to control whether guest users can create
demo workspaces.
* Updated server and client interfaces, GraphQL schema, and queries to
support the new guest demo workspace flag.

* **Bug Fixes**
* Improved sign-out behavior to redirect users appropriately based on
guest demo workspace permissions.
* Enhanced navigation flow to handle guest demo workspace access and
user authentication state.

* **Tests**
* Added tests to verify sign-out logic when guest demo workspaces are
enabled or disabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: liuyi <forehalo@gmail.com>
Co-authored-by: fengmk2 <fengmk2@gmail.com>
This commit is contained in:
Richard Lora
2025-06-29 10:17:18 -04:00
committed by GitHub
parent a4680d236d
commit 82b3c0d264
25 changed files with 209 additions and 34 deletions

View File

@@ -1,3 +1,4 @@
import { DefaultServerService } from '@affine/core/modules/cloud';
import { DesktopApiService } from '@affine/core/modules/desktop-api';
import { WorkspacesService } from '@affine/core/modules/workspace';
import {
@@ -46,16 +47,23 @@ export const Component = ({
const [navigating, setNavigating] = useState(true);
const [creating, setCreating] = useState(false);
const authService = useService(AuthService);
const defaultServerService = useService(DefaultServerService);
const loggedIn = useLiveData(
authService.session.status$.map(s => s === 'authenticated')
);
const allowGuestDemo =
useLiveData(
defaultServerService.server.config$.selector(
c => c.allowGuestDemoWorkspace
)
) ?? true;
const workspacesService = useService(WorkspacesService);
const list = useLiveData(workspacesService.list.workspaces$);
const listIsLoading = useLiveData(workspacesService.list.isRevalidating$);
const { openPage, jumpToPage } = useNavigateHelper();
const { openPage, jumpToPage, jumpToSignIn } = useNavigateHelper();
const [searchParams] = useSearchParams();
const createOnceRef = useRef(false);
@@ -84,6 +92,12 @@ export const Component = ({
return;
}
if (!allowGuestDemo && !loggedIn) {
localStorage.removeItem('last_workspace_id');
jumpToSignIn();
return;
}
// check is user logged in && has cloud workspace
if (searchParams.get('initCloud') === 'true') {
if (loggedIn) {
@@ -111,10 +125,12 @@ export const Component = ({
openPage(openWorkspace.id, defaultIndexRoute, RouteLogic.REPLACE);
}
}, [
allowGuestDemo,
createCloudWorkspace,
list,
openPage,
searchParams,
jumpToSignIn,
listIsLoading,
loggedIn,
navigating,
@@ -128,7 +144,9 @@ export const Component = ({
}, [desktopApi]);
useEffect(() => {
setCreating(true);
if (listIsLoading || list.length > 0) {
return;
}
createFirstAppData(workspacesService)
.then(createdWorkspace => {
if (createdWorkspace) {
@@ -148,7 +166,15 @@ export const Component = ({
.finally(() => {
setCreating(false);
});
}, [jumpToPage, openPage, workspacesService]);
}, [
jumpToPage,
jumpToSignIn,
openPage,
workspacesService,
loggedIn,
listIsLoading,
list,
]);
if (navigating || creating) {
return fallback ?? <AppContainer fallback />;

View File

@@ -83,7 +83,8 @@ const AcceptInvite = ({ inviteId: targetInviteId }: { inviteId: string }) => {
const onSignOut = useAsyncCallback(async () => {
await authService.signOut();
}, [authService]);
navigateHelper.jumpToSignIn();
}, [authService, navigateHelper]);
if ((loading && !requestToJoinLoading) || inviteId !== targetInviteId) {
return null;