🔐 Initial change on the public links using anonymous users (#69)

* Initial change on the public links using anonymous users

Co-authored-by: Anton Shepilov <anton.shepilov@gmail.com>
This commit is contained in:
Romaric Mourgues
2023-06-09 16:03:01 +02:00
committed by GitHub
parent adda7caf00
commit 723b77cb55
26 changed files with 294 additions and 80 deletions
@@ -32,7 +32,7 @@ export default (props: { driveItem: DriveItem & { user?: UserType } }) => {
const input = useRecoilValue(SearchInputState);
const currentWorkspaceId = useRouterWorkspace();
const companyApplications = useCompanyApplications();
const [_, setParentId] = useRecoilState(DriveCurrentFolderAtom('root'));
const [_, setParentId] = useRecoilState(DriveCurrentFolderAtom({ initialFolderId: 'root' }));
const tdriveDriveApplicationId =
companyApplications.applications.find(application => {
return application.identity.code === 'tdrive_drive';
@@ -2,6 +2,7 @@ import Api from '../../global/framework/api-service';
import { DriveItem, DriveItemDetails, DriveItemVersion } from '../types';
import Workspace from '@deprecated/workspaces/workspaces';
import Logger from 'features/global/framework/logger-service';
import { JWTDataType } from 'app/features/auth/jwt-storage-service';
export interface BaseSearchOptions {
company_id?: string;
workspace_id?: string;
@@ -32,10 +33,7 @@ export const setTdriveTabToken = (token: string | null) => {
tdriveTabToken = token;
};
const appendPublicAndTdriveToken = (useAnd?: boolean) => {
if (publicLinkToken) {
return `${useAnd ? '&' : '?'}public_token=${publicLinkToken}`;
}
const appendTdriveToken = (useAnd?: boolean) => {
if (tdriveTabToken) {
return `${useAnd ? '&' : '?'}tdrive_tab_token=${tdriveTabToken}`;
}
@@ -44,21 +42,39 @@ const appendPublicAndTdriveToken = (useAnd?: boolean) => {
export class DriveApiClient {
private static logger = Logger.getLogger('MessageAPIClientService');
static async getAnonymousToken(
companyId: string,
id: string,
publicToken: string,
password?: string,
) {
return await Api.post<
{ company_id: string; document_id: string; token: string; token_password?: string },
{ access_token: JWTDataType }
>(`/internal/services/documents/v1/companies/${companyId}/anonymous/token`, {
company_id: companyId,
document_id: id,
token: publicToken,
token_password: password,
});
}
static async get(companyId: string, id: string | 'trash' | '') {
return await Api.get<DriveItemDetails>(
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendPublicAndTdriveToken()}`,
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendTdriveToken()}`,
);
}
static async remove(companyId: string, id: string | 'trash' | '') {
return await Api.delete<void>(
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendPublicAndTdriveToken()}`,
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendTdriveToken()}`,
);
}
static async update(companyId: string, id: string, update: Partial<DriveItem>) {
return await Api.post<Partial<DriveItem>, DriveItem>(
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendPublicAndTdriveToken()}`,
`/internal/services/documents/v1/companies/${companyId}/item/${id}${appendTdriveToken()}`,
update,
);
}
@@ -72,14 +88,14 @@ export class DriveApiClient {
{ item: Partial<DriveItem>; version: Partial<DriveItemVersion> },
DriveItem
>(
`/internal/services/documents/v1/companies/${companyId}/item${appendPublicAndTdriveToken()}`,
`/internal/services/documents/v1/companies/${companyId}/item${appendTdriveToken()}`,
data as { item: Partial<DriveItem>; version: Partial<DriveItemVersion> },
);
}
static async createVersion(companyId: string, id: string, version: Partial<DriveItemVersion>) {
return await Api.post<Partial<DriveItemVersion>, DriveItemVersion>(
`/internal/services/documents/v1/companies/${companyId}/item/${id}/version${appendPublicAndTdriveToken()}`,
`/internal/services/documents/v1/companies/${companyId}/item/${id}/version${appendTdriveToken()}`,
version,
);
}
@@ -88,7 +104,7 @@ export class DriveApiClient {
return Api.get<{ token: string }>(
`/internal/services/documents/v1/companies/${companyId}/item/download/token` +
`?items=${ids.join(',')}&version_id=${versionId}` +
appendPublicAndTdriveToken(true),
appendTdriveToken(true),
);
}
@@ -96,12 +112,12 @@ export class DriveApiClient {
const { token } = await DriveApiClient.getDownloadToken(companyId, [id], versionId);
if (versionId)
return Api.route(
`/internal/services/documents/v1/companies/${companyId}/item/${id}/download?version_id=${versionId}&token=${token}${appendPublicAndTdriveToken(
`/internal/services/documents/v1/companies/${companyId}/item/${id}/download?version_id=${versionId}&token=${token}${appendTdriveToken(
true,
)}`,
);
return Api.route(
`/internal/services/documents/v1/companies/${companyId}/item/${id}/download?token=${token}${appendPublicAndTdriveToken(
`/internal/services/documents/v1/companies/${companyId}/item/${id}/download?token=${token}${appendTdriveToken(
true,
)}`,
);
@@ -112,7 +128,7 @@ export class DriveApiClient {
return Api.route(
`/internal/services/documents/v1/companies/${companyId}/item/download/zip` +
`?items=${ids.join(',')}&token=${token}` +
appendPublicAndTdriveToken(true),
appendTdriveToken(true),
);
}
@@ -26,17 +26,22 @@ import { PropertiesModal } from './modals/properties';
import { AccessModal } from './modals/update-access';
import { VersionsModal } from './modals/versions';
export const DriveCurrentFolderAtom = atomFamily<string, string>({
export const DriveCurrentFolderAtom = atomFamily<
string,
{ context?: string; initialFolderId: string }
>({
key: 'DriveCurrentFolderAtom',
default: startingParentId => startingParentId || 'root',
default: options => options.initialFolderId || 'root',
});
export default memo(
({
context,
initialParentId,
tdriveTabContextToken,
inPublicSharing,
}: {
context?: string;
initialParentId?: string;
tdriveTabContextToken?: string;
inPublicSharing?: boolean;
@@ -45,7 +50,7 @@ export default memo(
setTdriveTabToken(tdriveTabContextToken || null);
const [parentId, _setParentId] = useRecoilState(
DriveCurrentFolderAtom(initialParentId || 'root'),
DriveCurrentFolderAtom({ context: context, initialFolderId: initialParentId || 'root' }),
);
const [loadingParentChange, setLoadingParentChange] = useState(false);
@@ -118,7 +123,7 @@ export default memo(
return (
<UploadZone
overClassName={''}
className="h-full overflow-hidden"
className="h-full overflow-hidden w-full relative"
disableClick
parent={''}
multiple={true}
@@ -24,7 +24,9 @@ export const useOnBuildContextMenu = (children: DriveItem[], initialParentId?: s
const [checkedIds, setChecked] = useRecoilState(DriveItemSelectedList);
const checked = children.filter(c => checkedIds[c.id]);
const [_, setParentId] = useRecoilState(DriveCurrentFolderAtom(initialParentId || 'root'));
const [_, setParentId] = useRecoilState(
DriveCurrentFolderAtom({ initialFolderId: initialParentId || 'root' }),
);
const { download, downloadZip, update } = useDriveActions();
const setCreationModalState = useSetRecoilState(CreateModalAtom);
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import MenusBodyLayer from '@components/menus/menus-body-layer';
import JWTStorage from '@features/auth/jwt-storage-service';
import Api from '@features/global/framework/api-service';
import Languages from '@features/global/services/languages-service';
import { addApiUrlIfNeeded } from '@features/global/utils/URLUtils';
@@ -11,15 +12,18 @@ import { Input } from 'app/atoms/input/input-text';
import { Base, Subtitle, Title } from 'app/atoms/text';
import UploadsViewer from 'app/components/file-uploads/uploads-viewer';
import { useDriveItem } from 'app/features/drive/hooks/use-drive-item';
import { useDriveUpload } from 'app/features/drive/hooks/use-drive-upload';
import { ToasterService } from 'app/features/global/services/toaster-service';
import { useParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import shortUUID from 'short-uuid';
import Avatar from '../../../../atoms/avatar';
import { setPublicLinkToken } from '../../../../features/drive/api-client/api-client';
import {
DriveApiClient,
setPublicLinkToken,
} from '../../../../features/drive/api-client/api-client';
import useRouterCompany from '../../../../features/router/hooks/use-router-company';
import { CreateModalAtom } from './modals/create';
import { CreateModalWithUploadZones } from '../../side-bar/actions';
import { useRecoilState } from 'recoil';
import { DriveCurrentFolderAtom } from './browser';
export default () => {
const companyId = useRouterCompany();
@@ -44,7 +48,6 @@ export default () => {
const { token, documentId: _documentId } = useParams() as { token?: string; documentId?: string };
const documentId = _documentId ? shortUUID().toUUID(_documentId || '') : '';
setPublicLinkToken(token || null);
if (!companyId) {
return <></>;
@@ -77,7 +80,7 @@ export default () => {
</div>
<MenusBodyLayer />
<UploadsViewer />
<CreateModalWithUploadZones />
<CreateModalWithUploadZones initialParentId={documentId} />
</div>
);
};
@@ -92,10 +95,36 @@ const AccessChecker = ({
folderId: string;
}) => {
const { details, loading, refresh } = useDriveItem(folderId);
const companyId = useRouterCompany();
const [password, setPassword] = useState((token || '').split('+')[1] || '');
const setPublicToken = async (token: string, password?: string) => {
try {
setPublicLinkToken(token || null);
const { access_token } = await DriveApiClient.getAnonymousToken(
companyId,
folderId,
token,
password,
);
if (!access_token) {
throw new Error('Invalid password or token, or expired link.');
}
JWTStorage.updateJWT(access_token);
} catch (e) {
console.error(e);
ToasterService.error('Unable to access documents: ' + e);
}
};
useEffect(() => {
refresh(folderId);
(async () => {
await setPublicToken(token || '');
refresh(folderId);
})();
}, []);
if (!details?.item?.id && loading) {
@@ -125,8 +154,8 @@ const AccessChecker = ({
<Button
className="rounded-l-none"
theme="primary"
onClick={() => {
setPublicLinkToken(token ? encodeURIComponent(token + '+' + password) : null);
onClick={async () => {
await setPublicToken(token || '', password);
refresh(folderId);
}}
>
@@ -12,13 +12,17 @@ import { ConfirmDeleteModalAtom } from '../body/drive/modals/confirm-delete';
import { CreateModal, CreateModalAtom } from '../body/drive/modals/create';
import { Button } from '@atoms/button/button';
export const CreateModalWithUploadZones = () => {
export const CreateModalWithUploadZones = ({ initialParentId }: { initialParentId?: string }) => {
const companyId = useRouterCompany();
const uploadZoneRef = useRef<UploadZone | null>(null);
const uploadFolderZoneRef = useRef<UploadZone | null>(null);
const setCreationModalState = useSetRecoilState(CreateModalAtom);
const { uploadTree, uploadFromUrl } = useDriveUpload();
const [parentId, _] = useRecoilState(DriveCurrentFolderAtom('root'));
const [parentId, _] = useRecoilState(
DriveCurrentFolderAtom({ initialFolderId: initialParentId || 'root' }),
);
console.log(parentId);
return (
<>
@@ -73,7 +77,7 @@ export const CreateModalWithUploadZones = () => {
};
export default () => {
const [parentId, _] = useRecoilState(DriveCurrentFolderAtom('root'));
const [parentId, _] = useRecoilState(DriveCurrentFolderAtom({ initialFolderId: 'root' }));
const { access, item, inTrash } = useDriveItem(parentId);
const { children: trashChildren } = useDriveItem('trash');
const uploadZoneRef = useRef<UploadZone | null>(null);
@@ -89,7 +93,7 @@ export default () => {
<div className="-m-4 overflow-hidden">
<AnimatedHeight>
<div className="p-4">
<CreateModalWithUploadZones />
<CreateModalWithUploadZones initialParentId={parentId} />
{inTrash && access === 'manage' && (
<>
@@ -19,7 +19,9 @@ import DiskUsage from '../common/disk-usage';
import Actions from './actions';
export default () => {
const [parentId, setParentId] = useRecoilState(DriveCurrentFolderAtom('root'));
const [parentId, setParentId] = useRecoilState(
DriveCurrentFolderAtom({ initialFolderId: 'root' }),
);
const { user } = useCurrentUser();
const active = false;
const { access: rootAccess } = useDriveItem('root');