mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(modqueue): enforce role-gated access with not-allowed view
This commit is contained in:
+55
-6
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
|
||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||
import { Navigate, Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
|
||||
import { useAccount, useAccountComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts';
|
||||
import { initSnow, removeSnow } from './lib/snow';
|
||||
import { isAllView, isModView, isSubscriptionsView } from './lib/utils/view-utils';
|
||||
import { preloadThemeAssets } from './lib/utils/preload-utils';
|
||||
@@ -10,12 +11,14 @@ import useSpecialThemeStore from './stores/use-special-theme-store';
|
||||
import useIsMobile from './hooks/use-is-mobile';
|
||||
import useTheme from './hooks/use-theme';
|
||||
import { useDirectories } from './hooks/use-directories';
|
||||
import { useResolvedSubplebbitAddress } from './hooks/use-resolved-subplebbit-address';
|
||||
import { getSubplebbitAddress, isPostRoute, isPendingPostRoute, isModQueueRoute } from './lib/utils/route-utils';
|
||||
import styles from './app.module.css';
|
||||
import FAQ from './views/faq';
|
||||
import Home from './views/home';
|
||||
import Rules from './views/rules';
|
||||
import NotFound from './views/not-found';
|
||||
import NotAllowed from './views/not-allowed';
|
||||
import PendingPost from './views/pending-post';
|
||||
import Post from './views/post';
|
||||
import ModQueueView from './views/mod-queue';
|
||||
@@ -37,6 +40,8 @@ import SettingsModal from './components/settings-modal';
|
||||
// to prevent visible loading delays when switching themes
|
||||
preloadThemeAssets();
|
||||
|
||||
const hasModQueueAccessRole = (role?: string): boolean => role === 'admin' || role === 'owner' || role === 'moderator';
|
||||
|
||||
const BoardLayout = () => {
|
||||
const { accountCommentIndex, boardIdentifier } = useParams();
|
||||
const location = useLocation();
|
||||
@@ -147,6 +152,49 @@ const GlobalLayout = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ModQueueRoute = () => {
|
||||
const { boardIdentifier } = useParams();
|
||||
const account = useAccount();
|
||||
const accountAddress = account?.author?.address;
|
||||
const subplebbitAddress = useResolvedSubplebbitAddress();
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||
|
||||
const accountSubplebbitAddresses = useAccountsStore(
|
||||
(state) => {
|
||||
const activeAccountId = state.activeAccountId;
|
||||
const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined;
|
||||
const accountSubplebbits = activeAccount?.subplebbits || {};
|
||||
return Object.keys(accountSubplebbits);
|
||||
},
|
||||
(prev, next) => {
|
||||
if (prev.length !== next.length) return false;
|
||||
return prev.every((val, idx) => val === next[idx]);
|
||||
},
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!accountAddress) {
|
||||
return <Navigate to='/not-allowed' replace />;
|
||||
}
|
||||
|
||||
if (!boardIdentifier) {
|
||||
return accountSubplebbitAddresses.length > 0 ? <ModQueueView /> : <Navigate to='/not-allowed' replace />;
|
||||
}
|
||||
|
||||
// Wait for board role metadata before enforcing access to avoid false redirects during initial load.
|
||||
const boardState = subplebbit?.state;
|
||||
const isBoardLoading = !subplebbit || !boardState || (boardState !== 'succeeded' && boardState !== 'failed');
|
||||
if (isBoardLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const accountRole = subplebbit?.roles?.[accountAddress]?.role;
|
||||
return hasModQueueAccessRole(accountRole) ? <ModQueueView /> : <Navigate to='/not-allowed' replace />;
|
||||
};
|
||||
|
||||
const App = () => (
|
||||
<div className={styles.app}>
|
||||
<Routes>
|
||||
@@ -170,16 +218,16 @@ const App = () => (
|
||||
<Route path='/mod/catalog/:timeFilterName?' element={null} />
|
||||
<Route path='/mod/catalog/:timeFilterName?/settings' element={null} />
|
||||
|
||||
<Route path='/mod/modqueue' element={<ModQueueView />} />
|
||||
<Route path='/mod/modqueue/settings' element={<ModQueueView />} />
|
||||
<Route path='/mod/modqueue' element={<ModQueueRoute />} />
|
||||
<Route path='/mod/modqueue/settings' element={<ModQueueRoute />} />
|
||||
|
||||
<Route path='/:boardIdentifier' element={null} />
|
||||
<Route path='/:boardIdentifier/settings' element={null} />
|
||||
<Route path='/:boardIdentifier/catalog' element={null} />
|
||||
<Route path='/:boardIdentifier/catalog/settings' element={null} />
|
||||
|
||||
<Route path='/:boardIdentifier/modqueue' element={<ModQueueView />} />
|
||||
<Route path='/:boardIdentifier/modqueue/settings' element={<ModQueueView />} />
|
||||
<Route path='/:boardIdentifier/modqueue' element={<ModQueueRoute />} />
|
||||
<Route path='/:boardIdentifier/modqueue/settings' element={<ModQueueRoute />} />
|
||||
|
||||
<Route path='/:boardIdentifier/thread/:commentCid' element={<Post />} />
|
||||
<Route path='/:boardIdentifier/thread/:commentCid/settings' element={<Post />} />
|
||||
@@ -187,6 +235,7 @@ const App = () => (
|
||||
<Route path='/pending/:accountCommentIndex' element={<PendingPost />} />
|
||||
<Route path='/pending/:accountCommentIndex/settings' element={<PendingPost />} />
|
||||
</Route>
|
||||
<Route path='/not-allowed' element={<NotAllowed />} />
|
||||
<Route path='/not-found' element={<NotFound />} />
|
||||
<Route path='*' element={<NotFound />} />
|
||||
</Route>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './not-found-image';
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { NOT_FOUND_IMAGES } from '../../generated/asset-manifest';
|
||||
|
||||
const NotFoundImage = () => {
|
||||
const [imagePath] = useState(() => NOT_FOUND_IMAGES[Math.floor(Math.random() * NOT_FOUND_IMAGES.length)]);
|
||||
|
||||
return <img src={imagePath} alt='' />;
|
||||
};
|
||||
|
||||
export default NotFoundImage;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './not-allowed';
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { HomeLogo } from '../home';
|
||||
import NotFoundImage from '../../components/not-found-image';
|
||||
import styles from '../not-found/not-found.module.css';
|
||||
|
||||
const NotAllowed = () => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.content}>
|
||||
<HomeLogo />
|
||||
<div className={styles.boxOuter}>
|
||||
<div className={styles.boxInner}>
|
||||
<div className={styles.boxBar}>
|
||||
<h2>Not Allowed</h2>
|
||||
</div>
|
||||
<div className={styles.boxContent}>
|
||||
<NotFoundImage />
|
||||
<br />
|
||||
<div className={styles.backToBoard}>
|
||||
[<Link to='/'>Back to home</Link>]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotAllowed;
|
||||
@@ -1,17 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||
import { useDirectories } from '../../hooks/use-directories';
|
||||
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
|
||||
import { HomeLogo } from '../home';
|
||||
import NotFoundImage from '../../components/not-found-image';
|
||||
import styles from './not-found.module.css';
|
||||
import { NOT_FOUND_IMAGES } from '../../generated/asset-manifest';
|
||||
|
||||
const NotFoundImage = () => {
|
||||
const [imagePath] = useState(() => NOT_FOUND_IMAGES[Math.floor(Math.random() * NOT_FOUND_IMAGES.length)]);
|
||||
|
||||
return <img src={imagePath} alt='' />;
|
||||
};
|
||||
|
||||
const NotFound = () => {
|
||||
const location = useLocation();
|
||||
@@ -31,7 +24,7 @@ const NotFound = () => {
|
||||
<div className={styles.boxOuter}>
|
||||
<div className={styles.boxInner}>
|
||||
<div className={styles.boxBar}>
|
||||
<h2>404 Not Found</h2>
|
||||
<h2>Not Found</h2>
|
||||
</div>
|
||||
<div className={styles.boxContent}>
|
||||
<NotFoundImage />
|
||||
|
||||
Reference in New Issue
Block a user