Fix share page permissions
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
import { Loading } from '@umami/react-zen';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { createContext, type ReactNode, useEffect } from 'react';
|
||||
import { useShareTokenQuery } from '@/components/hooks';
|
||||
import { ENTITY_TYPE } from '@/lib/constants';
|
||||
import type { WhiteLabel } from '@/lib/types';
|
||||
import { Loading } from '@umami/react-zen';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { createContext, type ReactNode, useEffect } from 'react';
|
||||
|
||||
export interface ShareData {
|
||||
shareId: string;
|
||||
@@ -56,14 +56,15 @@ export function ShareProvider({ slug, children }: { slug: string; children: Reac
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const path = getSharePath(pathname);
|
||||
const isBoardShare = share?.shareType === ENTITY_TYPE.board;
|
||||
const isWebsiteShare = share?.shareType === ENTITY_TYPE.website;
|
||||
|
||||
const allowedSections = isWebsiteShare && share?.parameters
|
||||
? ALL_SECTION_IDS.filter(id => share.parameters[id] === true)
|
||||
: [];
|
||||
const allowedSections =
|
||||
isWebsiteShare && share?.parameters
|
||||
? ALL_SECTION_IDS.filter(id => share.parameters[id] === true)
|
||||
: [];
|
||||
|
||||
const shouldRedirect = isWebsiteShare &&
|
||||
const shouldRedirect =
|
||||
isWebsiteShare &&
|
||||
allowedSections.length === 1 &&
|
||||
allowedSections[0] !== 'overview' &&
|
||||
(path === undefined || path === '' || path === 'overview');
|
||||
|
||||
@@ -83,6 +83,10 @@ export function SharePage() {
|
||||
}
|
||||
}, [setTheme]);
|
||||
|
||||
// Check if the requested path is allowed
|
||||
const pageKey = path || '';
|
||||
const isAllowed = pageKey === '' || parameters[pageKey] === true;
|
||||
|
||||
const entityPage =
|
||||
shareType === ENTITY_TYPE.board && boardId ? (
|
||||
<BoardViewPage boardId={boardId} showActions={false} />
|
||||
@@ -92,6 +96,12 @@ export function SharePage() {
|
||||
<LinkPage linkId={linkId} showHeaderActions={false} />
|
||||
) : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAllowed) {
|
||||
router.replace(`/share/${slug}`);
|
||||
}
|
||||
}, [isAllowed, slug, router]);
|
||||
|
||||
if (entityPage) {
|
||||
return (
|
||||
<Column>
|
||||
@@ -101,12 +111,7 @@ export function SharePage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the requested path is allowed
|
||||
const pageKey = path || '';
|
||||
const isAllowed = pageKey === '' || parameters[pageKey] === true;
|
||||
|
||||
if (!isAllowed) {
|
||||
router.replace(`/share/${slug}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { setShare, setShareToken, useApp } from '@/store/app';
|
||||
import { setShareData } from '@/store/app';
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
const selector = state => state.share;
|
||||
|
||||
export function useShareTokenQuery(slug: string) {
|
||||
const share = useApp(selector);
|
||||
const { get, useQuery } = useApi();
|
||||
const query = useQuery({
|
||||
queryKey: ['share', slug],
|
||||
queryFn: async () => {
|
||||
const data = await get(`/share/${slug}`);
|
||||
|
||||
setShare(data);
|
||||
setShareToken({ token: data?.token });
|
||||
setShareData(data, { token: data?.token });
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { share, ...query };
|
||||
return { share: query.data, ...query };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useCallback } from 'react';
|
||||
import { getClientAuthToken } from '@/lib/client';
|
||||
import { SHARE_TOKEN_HEADER } from '@/lib/constants';
|
||||
import { SHARE_CONTEXT_HEADER, SHARE_TOKEN_HEADER } from '@/lib/constants';
|
||||
import { type FetchResponse, httpDelete, httpGet, httpPost, httpPut } from '@/lib/fetch';
|
||||
import { useApp } from '@/store/app';
|
||||
|
||||
@@ -18,10 +19,17 @@ async function handleResponse(res: FetchResponse): Promise<any> {
|
||||
|
||||
export function useApi() {
|
||||
const shareToken = useApp(selector);
|
||||
const pathname = usePathname();
|
||||
const isSharePath = pathname?.startsWith('/share');
|
||||
|
||||
const shareHeaders =
|
||||
isSharePath && shareToken?.token
|
||||
? { [SHARE_TOKEN_HEADER]: shareToken.token, [SHARE_CONTEXT_HEADER]: '1' }
|
||||
: {};
|
||||
|
||||
const defaultHeaders = {
|
||||
authorization: `Bearer ${getClientAuthToken()}`,
|
||||
[SHARE_TOKEN_HEADER]: shareToken?.token,
|
||||
...shareHeaders,
|
||||
};
|
||||
const basePath = process.env.basePath;
|
||||
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
import debug from 'debug';
|
||||
import { ROLE_PERMISSIONS, ROLES, SHARE_TOKEN_HEADER } from '@/lib/constants';
|
||||
import { ROLE_PERMISSIONS, ROLES, SHARE_CONTEXT_HEADER, SHARE_TOKEN_HEADER } from '@/lib/constants';
|
||||
import { createAuthKey, secret } from '@/lib/crypto';
|
||||
import { createSecureToken, parseSecureToken, parseToken } from '@/lib/jwt';
|
||||
import redis from '@/lib/redis';
|
||||
@@ -39,6 +39,14 @@ export async function checkAuth(request: Request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!user?.id && shareToken) {
|
||||
const shareContext = request.headers.get(SHARE_CONTEXT_HEADER);
|
||||
if (!shareContext) {
|
||||
log('Share token used outside share context');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (user) {
|
||||
user.isAdmin = user.role === ROLES.admin;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export const DASHBOARD_CONFIG = 'umami.dashboard';
|
||||
export const LAST_TEAM_CONFIG = 'umami.last-team';
|
||||
export const VERSION_CHECK = 'umami.version-check';
|
||||
export const SHARE_TOKEN_HEADER = 'x-umami-share-token';
|
||||
export const SHARE_CONTEXT_HEADER = 'x-umami-share-context';
|
||||
export const HOMEPAGE_URL = 'https://umami.is';
|
||||
export const DOCS_URL = 'https://umami.is/docs';
|
||||
export const REPO_URL = 'https://github.com/umami-software/umami';
|
||||
|
||||
+5
-6
@@ -32,12 +32,11 @@ export function setLocale(locale: string) {
|
||||
store.setState({ locale });
|
||||
}
|
||||
|
||||
export function setShare(share: object) {
|
||||
store.setState({ share });
|
||||
}
|
||||
|
||||
export function setShareToken(shareToken: { token?: string } | null) {
|
||||
store.setState({ shareToken });
|
||||
export function setShareData(
|
||||
share: object | null,
|
||||
shareToken: { token?: string } | null,
|
||||
) {
|
||||
store.setState({ share, shareToken });
|
||||
}
|
||||
|
||||
export function setUser(user: object) {
|
||||
|
||||
Reference in New Issue
Block a user