🔐 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
@@ -28,6 +28,7 @@ export default interface AuthServiceAPI extends TdriveServiceProvider {
track: boolean;
provider_id: string;
application_id?: string;
public_token_document_id?: string;
} & any,
): AccessToken;
}
@@ -31,7 +31,12 @@ export class AuthService implements AuthServiceAPI {
generateJWT(
userId: uuid,
email: string,
options: { track: boolean; provider_id: string; application_id?: string },
options: {
track: boolean;
provider_id: string;
application_id?: string;
public_token_document_id?: string;
},
): AccessToken {
const now = Math.round(new Date().getTime() / 1000); // Current time in UTC
assert(this.configuration.expiration, "jwt.expiration is missing");
@@ -53,6 +58,7 @@ export class AuthService implements AuthServiceAPI {
track: !!options.track,
provider_id: options.provider_id || "",
application_id: options.application_id,
public_token_document_id: options.public_token_document_id,
} as JwtType),
refresh: this.sign({
exp: jwtRefreshExpiration,
@@ -64,6 +70,7 @@ export class AuthService implements AuthServiceAPI {
track: !!options.track,
provider_id: options.provider_id || "",
application_id: options.application_id,
public_token_document_id: options.public_token_document_id,
} as JwtType),
type: "Bearer",
};
@@ -22,6 +22,7 @@ const jwtPlugin: FastifyPluginCallback = (fastify, _opts, next) => {
...{ application_id: jwt.application_id || null },
...{ server_request: jwt.server_request || false },
...{ allow_tracking: jwt.track || false },
...{ public_token_document_id: jwt.public_token_document_id || null },
};
request.log.debug(`Authenticated as user ${request.currentUser.id}`);
};
@@ -125,7 +125,7 @@ export default class EmailPusherClass
this.logger.info("email sent");
}
} catch (error) {
this.logger.error("Failed to send email", error);
this.logger.error({ error: `${error}` }, "Failed to send email");
}
}
}
@@ -3,7 +3,7 @@ import { TransportRequestOptions } from "@elastic/elasticsearch/lib/Transport";
import { logger } from "../../../../../../core/platform/framework/logger";
import { EntityTarget, FindFilter, FindOptions, getEntityDefinition } from "../../api";
import { asciiFold } from "../utils";
import { comparisonType } from "src/core/platform/services/database/services/orm/repository/repository";
import { comparisonType } from "../../../../../../core/platform/services/database/services/orm/repository/repository";
export function buildSearchQuery<Entity>(
entityType: EntityTarget<Entity>,
@@ -9,4 +9,5 @@ export type JwtType = {
refresh_nbf?: number;
iat: number;
track: boolean;
public_token_document_id?: string;
};
@@ -126,6 +126,7 @@ export class ConsoleController {
{
track: request.currentUser?.allow_tracking || false,
provider_id: request.currentUser.identity_provider_id,
public_token_document_id: request.currentUser.public_token_document_id,
},
),
};
@@ -43,6 +43,7 @@ export const hasAccessLevel = (
/**
* checks the current user is a guest
* Company guests can be users accessing with a public link
*
* @param {CompanyExecutionContext} context
* @returns {Promise<boolean>}
@@ -149,7 +150,7 @@ export const getAccessLevel = async (
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
): Promise<DriveFileAccessLevel | "none"> => {
if (!id || id === "root")
return !context?.user?.id ? "none" : (await isCompanyGuest(context)) ? "read" : "manage";
return !context?.user?.id || (await isCompanyGuest(context)) ? "none" : "manage";
if (id === "trash")
return (await isCompanyGuest(context)) || !context?.user?.id
? "none"
@@ -164,6 +165,7 @@ export const getAccessLevel = async (
}
let publicToken = context.public_token;
const prevalidatedPublicTokenDocumentId = context?.user?.public_token_document_id;
try {
item =
@@ -187,10 +189,13 @@ export const getAccessLevel = async (
*/
//Public access
if (publicToken) {
if (publicToken || prevalidatedPublicTokenDocumentId === item.id) {
if (!item.access_info.public.token) return "none";
const { token: itemToken, level: itemLevel, password, expiration } = item.access_info.public;
if (expiration && expiration < Date.now()) return "none";
if (prevalidatedPublicTokenDocumentId) {
return itemLevel;
}
if (password) {
const data = publicToken.split("+");
if (data.length !== 2) return "none";
@@ -227,7 +232,7 @@ export const getAccessLevel = async (
const matchingCompany = accessEntities.find(
a => a.type === "company" && a.id === context.company.id,
);
if (matchingCompany) otherLevels.push(matchingCompany.level);
if (matchingCompany && !isCompanyGuest(context)) otherLevels.push(matchingCompany.level);
}
//Parent folder
@@ -70,7 +70,7 @@ export class DocumentsProcessor
content_keywords = extractKeywords(content_strings);
} catch (error) {
console.debug(error);
logger.error("Failed to generate content keywords", error);
logger.error({ error: `${error}` }, "Failed to generate content keywords");
}
return { content_keywords, item: message.item };
@@ -81,7 +81,7 @@ export class DocumentsService {
DriveTdriveTabEntity,
);
} catch (error) {
logger.error("Error while initializing Documents Service", error);
logger.error({ error: `${error}` }, "Error while initializing Documents Service");
}
return this;
@@ -94,7 +94,10 @@ export class DocumentsService {
* @param {DriveExecutionContext} context
* @returns {Promise<DriveItemDetails>}
*/
get = async (id: string, context: DriveExecutionContext): Promise<DriveItemDetails> => {
get = async (
id: string,
context: DriveExecutionContext & { public_token?: string },
): Promise<DriveItemDetails> => {
if (!context) {
this.logger.error("invalid context");
return null;
@@ -123,11 +126,12 @@ export class DocumentsService {
try {
const hasAccess = await checkAccess(id, entity, "read", this.repository, context);
if (!hasAccess) {
this.logger.error("user does not have access drive item ", id);
this.logger.error("user does not have access drive item " + id);
throw Error("user does not have access to this item");
}
} catch (error) {
this.logger.error("Failed to grant access to the drive item", error);
console.log(error);
this.logger.error({ error: `${error}` }, "Failed to grant access to the drive item");
throw new CrudException("User does not have access to this item or its children", 401);
}
@@ -309,7 +313,7 @@ export class DocumentsService {
return driveItem;
} catch (error) {
this.logger.error("Failed to create drive item", error);
this.logger.error({ error: `${error}` }, "Failed to create drive item");
CrudException.throwMe(error, new CrudException("Failed to create item", 500));
}
};
@@ -423,7 +427,7 @@ export class DocumentsService {
return item;
} catch (error) {
console.error(error);
this.logger.error("Failed to update drive item", error);
this.logger.error({ error: `${error}` }, "Failed to update drive item");
throw new CrudException("Failed to update item", 500);
}
};
@@ -468,7 +472,7 @@ export class DocumentsService {
}),
);
} catch (error) {
this.logger.error("Failed to empty trash", error);
this.logger.error({ error: `${error}` }, "Failed to empty trash");
throw new CrudException("Failed to empty trash", 500);
}
@@ -492,7 +496,7 @@ export class DocumentsService {
throw Error("user does not have access to this item");
}
} catch (error) {
this.logger.error("Failed to grant access to the drive item", error);
this.logger.error({ error: `${error}` }, "Failed to grant access to the drive item");
throw new CrudException("User does not have access to this item or its children", 401);
}
@@ -626,7 +630,7 @@ export class DocumentsService {
return driveItemVersion;
} catch (error) {
this.logger.error("Failed to create Drive item version", error);
this.logger.error({ error: `${error}` }, "Failed to create Drive item version");
throw new CrudException("Failed to create Drive item version", 500);
}
};
@@ -1,15 +1,13 @@
import { ExecutionContext } from "../../core/platform/framework/api/crud-service";
import { DriveFile } from "./entities/drive-file";
import { FileVersion } from "./entities/file-version";
import { SortType } from "src/core/platform/services/search/api";
import { SortType } from "../../core/platform/services/search/api";
export interface CompanyExecutionContext extends ExecutionContext {
company: { id: string };
}
export type DriveExecutionContext = CompanyExecutionContext & {
public_token?: string;
};
export type DriveExecutionContext = CompanyExecutionContext;
export type RequestParams = {
company_id: string;
@@ -228,7 +228,7 @@ export const getPath = async (
): Promise<DriveFile[]> => {
id = id || "root";
if (isVirtualFolder(id))
return !context.public_token || ignoreAccess
return !context?.user?.public_token_document_id || ignoreAccess
? [
{
id,
@@ -1,4 +1,5 @@
import { FastifyReply, FastifyRequest } from "fastify";
import { getInstance } from "../../../../services/user/entities/user";
import { logger } from "../../../../core/platform/framework";
import { CrudException, ListResult } from "../../../../core/platform/framework/api/crud-service";
import { File } from "../../../../services/files/entities/file";
@@ -68,7 +69,7 @@ export class DocumentsController {
context,
);
} catch (error) {
logger.error("Failed to create Drive item", error);
logger.error({ error: `${error}` }, "Failed to create Drive item");
CrudException.throwMe(error, new CrudException("Failed to create Drive item", 500));
}
};
@@ -91,7 +92,7 @@ export class DocumentsController {
reply.status(200).send();
} catch (error) {
logger.error("Failed to delete drive item", error);
logger.error({ error: `${error}` }, "Failed to delete drive item");
throw new CrudException("Failed to delete drive item", 500);
}
};
@@ -281,7 +282,7 @@ export class DocumentsController {
response.send(data.file);
}
} catch (error) {
logger.error("failed to download file", error);
logger.error({ error: `${error}` }, "failed to download file");
throw new CrudException("Failed to download file", 500);
}
};
@@ -328,7 +329,7 @@ export class DocumentsController {
archive.pipe(reply.raw);
} catch (error) {
logger.error("failed to send zip file", error);
logger.error({ error: `${error}` }, "failed to send zip file");
throw new CrudException("Failed to create zip file", 500);
}
};
@@ -361,7 +362,7 @@ export class DocumentsController {
return this.driveFileDTOBuilder.build(fileList, context, options.fields, options.view);
} catch (error) {
logger.error("error while searching for document", error);
logger.error({ error: `${error}` }, "error while searching for document");
this.throw500Search();
}
};
@@ -401,6 +402,65 @@ export class DocumentsController {
context,
);
};
async getAnonymousToken(
req: FastifyRequest<{
Body: {
company_id: string;
document_id: string;
token: string;
token_password?: string;
};
}>,
): Promise<{
access_token: {
time: number;
expiration: number;
refresh_expiration: number;
value: string;
refresh: string;
type: string;
};
}> {
const document = await globalResolver.services.documents.documents.get(req.body.document_id, {
public_token: req.body.token + (req.body.token_password ? "+" + req.body.token_password : ""),
user: null,
company: { id: req.body.company_id },
});
if (!document || !document.access || document.access === "none")
throw new CrudException("You don't have access to this document", 401);
const email = document.item.company_id + "-anonymous@tdrive.com";
let user = await globalResolver.services.users.getByEmail(email);
if (!user) {
user = (
await globalResolver.services.users.create(
getInstance({
first_name: "Anonymous",
last_name: "",
email_canonical: email,
username_canonical: (email.replace("@", ".") || "").toLocaleLowerCase(),
phone: "",
identity_provider: "anonymous",
identity_provider_id: email,
mail_verified: true,
}),
)
).entity;
}
await globalResolver.services.companies.setUserRole(document.item.company_id, user.id, "guest");
const token = globalResolver.platformServices.auth.generateJWT(user.id, user.email_canonical, {
track: false,
provider_id: "tdrive",
public_token_document_id: req.body.document_id,
});
return {
access_token: token,
};
}
}
/**
@@ -410,9 +470,8 @@ export class DocumentsController {
* @returns {CompanyExecutionContext}
*/
const getDriveExecutionContext = (
req: FastifyRequest<{ Params: { company_id: string }; Querystring?: { public_token?: string } }>,
req: FastifyRequest<{ Params: { company_id: string } }>,
): DriveExecutionContext => ({
public_token: req.query?.public_token,
user: req.currentUser,
company: { id: req.params.company_id },
url: req.url,
@@ -101,6 +101,12 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
handler: documentsController.setTab.bind(documentsController),
});
fastify.route({
method: "POST",
url: `${baseUrl}/anonymous/token`,
handler: documentsController.getAnonymousToken.bind(documentsController),
});
return next();
};
@@ -1,5 +1,5 @@
import { TdriveServiceProvider, Initializable } from "src/core/platform/framework/api";
import Repository from "src/core/platform/services/database/services/orm/repository/repository";
import { TdriveServiceProvider, Initializable } from "../../../core/platform/framework/api";
import Repository from "../../../core/platform/services/database/services/orm/repository/repository";
import { Tag, TagsType, TagsPrimaryKey } from "../entities";
import gr from "../../global-resolver";
import {
@@ -8,7 +8,7 @@ import {
} from "../../../../utils/types";
import { Tag } from "../../entities";
import gr from "../../../global-resolver";
import { ExecutionContext } from "src/core/platform/framework/api/crud-service";
import { ExecutionContext } from "../../../../core/platform/framework/api/crud-service";
import { handleError } from "../../../../utils/handleError";
export class TagsController
@@ -3,7 +3,7 @@ import { checkUserBelongsToCompany } from "../../../utils/company";
import { Tag } from "../types";
import { TagsController } from "./controllers";
import gr from "../../global-resolver";
import CompanyUser from "src/services/user/entities/company_user";
import CompanyUser from "../../../services/user/entities/company_user";
const tagsUrl = "/companies/:company_id/tags";
+2
View File
@@ -28,6 +28,8 @@ export interface User {
application_id?: string;
// allow_tracking
allow_tracking?: boolean;
// Prevalidated public token access
public_token_document_id?: string;
}
export const webSocketSchema = {
@@ -22,6 +22,17 @@ describe("the public links feature", () => {
access_info: AccessInformation;
}
class AccessTokenMockClass {
access_token: {
time: 0;
expiration: number;
refresh_expiration: number;
value: string;
refresh: string;
type: "Bearer";
};
}
class FullDriveInfoMockClass {
path: DriveFile[];
item?: DriveFile;
@@ -112,10 +123,28 @@ describe("the public links feature", () => {
const file = deserialize<DriveFileMockClass>(DriveFileMockClass, res.body);
expect(file.access_info.public?.level).toBe("read");
const accessRes = await platform.app.inject({
method: "POST",
url: `${url}/companies/${publicFile.company_id}/anonymous/token`,
headers: {},
payload: {
company_id: publicFile.company_id,
document_id: publicFile.id,
token: publicFile.access_info.public?.token,
},
});
const { access_token } = deserialize<AccessTokenMockClass>(
AccessTokenMockClass,
accessRes.body,
);
expect(access_token).toBeDefined();
const resPublicRaw = await platform.app.inject({
method: "GET",
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}?public_token=${publicFile.access_info.public?.token}`,
headers: {},
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}`,
headers: {
Authorization: `Bearer ${access_token.value}`,
},
});
const resPublic = deserialize<DriveItemDetails>(FullDriveInfoMockClass, resPublicRaw.body);
expect(resPublicRaw.statusCode).toBe(200);
@@ -135,10 +164,27 @@ describe("the public links feature", () => {
},
});
const accessRes = await platform.app.inject({
method: "POST",
url: `${url}/companies/${publicFile.company_id}/anonymous/token`,
headers: {},
payload: {
company_id: publicFile.company_id,
document_id: publicFile.id,
token: publicFile.access_info.public?.token,
},
});
const { access_token } = deserialize<AccessTokenMockClass>(
AccessTokenMockClass,
accessRes.body,
);
let resPublicRaw = await platform.app.inject({
method: "GET",
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}?public_token=${publicFile.access_info.public?.token}`,
headers: {},
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}`,
headers: {
Authorization: `Bearer ${access_token.value}`,
},
});
const resPublic = deserialize<DriveItemDetails>(FullDriveInfoMockClass, resPublicRaw.body);
expect(resPublicRaw.statusCode).toBe(200);
@@ -158,8 +204,10 @@ describe("the public links feature", () => {
resPublicRaw = await platform.app.inject({
method: "GET",
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}?public_token=${publicFile.access_info.public?.token}`,
headers: {},
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}`,
headers: {
authorization: `Bearer ${access_token.value}`,
},
});
expect(resPublicRaw.statusCode).toBe(401);
@@ -189,22 +237,45 @@ describe("the public links feature", () => {
},
});
const badAccessRes = await platform.app.inject({
method: "POST",
url: `${url}/companies/${publicFile.company_id}/anonymous/token`,
headers: {},
payload: {
company_id: publicFile.company_id,
document_id: publicFile.id,
token: publicFile.access_info.public?.token,
},
});
expect(badAccessRes.statusCode).toBe(401);
const accessRes = await platform.app.inject({
method: "POST",
url: `${url}/companies/${publicFile.company_id}/anonymous/token`,
headers: {},
payload: {
company_id: publicFile.company_id,
document_id: publicFile.id,
token: publicFile.access_info.public?.token,
token_password: "abcdef",
},
});
const { access_token } = deserialize<AccessTokenMockClass>(
AccessTokenMockClass,
accessRes.body,
);
let resPublicRaw = await platform.app.inject({
method: "GET",
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}?public_token=${publicFile.access_info.public?.token}%2Babcdef`,
headers: {},
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}`,
headers: {
Authorization: `Bearer ${access_token.value}`,
},
});
let resPublic = deserialize<DriveItemDetails>(FullDriveInfoMockClass, resPublicRaw.body);
expect(resPublicRaw.statusCode).toBe(200);
expect(resPublic.item?.id).toBe(publicFile.id);
resPublicRaw = await platform.app.inject({
method: "GET",
url: `${url}/companies/${publicFile.company_id}/item/${publicFile.id}?public_token=${publicFile.access_info.public?.token}`,
headers: {},
});
expect(resPublicRaw.statusCode).toBe(401);
await e2e_updateDocument(platform, publicFile.id, {
...publicFile,
access_info: {
@@ -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');