From a6b8d6cd096f8d6a516ada6da95a6dabacf2189b Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Sun, 2 Mar 2025 22:31:25 +0700 Subject: [PATCH] UBERF-9537: Fix Invalid navigate to guest not authorised (#8121) Signed-off-by: Andrey Sobolev --- packages/presentation/src/utils.ts | 9 ++++- packages/ui/src/location.ts | 4 ++ .../src/components/Guest.svelte | 11 ++--- .../src/components/GuestApp.svelte | 15 ++++++- plugins/guest-resources/src/connect.ts | 14 +++---- .../src/components/SelectWorkspace.svelte | 21 +--------- plugins/login-resources/src/utils.ts | 31 ++++++++++++-- .../src/components/SelectWorkspaceMenu.svelte | 21 +++++++--- .../src/components/Workbench.svelte | 12 ++++-- server/account/src/__tests__/token.test.ts | 40 +++++++++++++++++++ server/token/src/token.ts | 5 ++- .../sanity/tests/model/tracker/issues-page.ts | 4 +- .../sanity/tests/workspace/archive.spec.ts | 14 ++++--- 13 files changed, 140 insertions(+), 61 deletions(-) create mode 100644 server/account/src/__tests__/token.test.ts diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 19b531f670..49e3d89691 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -743,7 +743,14 @@ export function isCollabAttr (hierarchy: Hierarchy, key: KeyedAttribute): boolea */ export function decodeTokenPayload (token: string): any { try { - return JSON.parse(atob(token.split('.')[1])) + if (token === '') { + return {} + } + const tsplit = token.split('.') + if (tsplit.length < 2) { + return {} + } + return JSON.parse(atob(tsplit[1])) } catch (err: any) { console.error(err) return {} diff --git a/packages/ui/src/location.ts b/packages/ui/src/location.ts index 17bcedbbfc..3633784509 100644 --- a/packages/ui/src/location.ts +++ b/packages/ui/src/location.ts @@ -234,6 +234,10 @@ export const setTreeCollapsed = (_id: any, collapsed: boolean, prefix?: string): collapsed ? localStorage.setItem(key, COLLAPSED) : localStorage.removeItem(key) } +export function isSameSegments (a: PlatformLocation, b: PlatformLocation, len = 2): boolean { + return a.path.slice(0, len).every((segment, index) => segment === b.path[index]) +} + export function restoreLocation (loc: PlatformLocation, app: Plugin): void { const last = localStorage.getItem(`${locationStorageKeyId}_${app}`) diff --git a/plugins/guest-resources/src/components/Guest.svelte b/plugins/guest-resources/src/components/Guest.svelte index 2d7f85a9fb..db6808a067 100644 --- a/plugins/guest-resources/src/components/Guest.svelte +++ b/plugins/guest-resources/src/components/Guest.svelte @@ -26,25 +26,22 @@ Popup, PopupAlignment, ResolvedLocation, - Separator, TooltipInstance, areLocationsEqual, closePanel, - getCurrentLocation, - getLocation, - navigate, - showPanel, defineSeparators, + deviceOptionsStore as deviceInfo, + getCurrentLocation, setResolvedLocation, - deviceOptionsStore as deviceInfo + showPanel } from '@hcengineering/ui' import view from '@hcengineering/view' import { ListSelectionProvider, parseLinkId, restrictionStore, updateFocus } from '@hcengineering/view-resources' import workbench, { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@hcengineering/workbench' import { SpaceView, buildNavModel } from '@hcengineering/workbench-resources' + import { workbenchGuestSeparators } from '..' import guest from '../plugin' import { checkAccess } from '../utils' - import { workbenchGuestSeparators } from '..' const excludedApps = getMetadata(workbench.metadata.ExcludedApplications) ?? [] $deviceInfo.navigator.visible = false diff --git a/plugins/guest-resources/src/components/GuestApp.svelte b/plugins/guest-resources/src/components/GuestApp.svelte index f4b71de44a..e055e45464 100644 --- a/plugins/guest-resources/src/components/GuestApp.svelte +++ b/plugins/guest-resources/src/components/GuestApp.svelte @@ -16,18 +16,24 @@ import { getMetadata } from '@hcengineering/platform' import { Label, Loading, Notifications, location } from '@hcengineering/ui' import { upgradeDownloadProgress } from '@hcengineering/presentation' - import { connect, versionError } from '../connect' + import { connect, versionError, invalidError } from '../connect' import { guestId } from '@hcengineering/guest' import workbench from '@hcengineering/workbench' import Guest from './Guest.svelte' + import plugin from '../plugin' {#if $location.path[0] === guestId} {#await connect(getMetadata(workbench.metadata.PlatformTitle) ?? 'Platform')} {:then client} - {#if $versionError} + {#if $invalidError} + {invalidError} +
+

+
+ {:else if $versionError}

@@ -52,14 +58,19 @@ diff --git a/plugins/guest-resources/src/connect.ts b/plugins/guest-resources/src/connect.ts index ffad8416a5..b4e90f41e1 100644 --- a/plugins/guest-resources/src/connect.ts +++ b/plugins/guest-resources/src/connect.ts @@ -26,6 +26,8 @@ import { logOut } from '@hcengineering/workbench' import { writable, get } from 'svelte/store' export const versionError = writable(undefined) + +export const invalidError = writable(false) const versionStorageKey = 'last_server_version' let _token: string | undefined @@ -37,9 +39,7 @@ export async function connect (title: string): Promise { const token = loc.query?.token const wsUrl = loc.path[1] if (wsUrl === undefined || token == null) { - navigate({ - path: [loginId] - }) + invalidError.set(true) return } @@ -51,7 +51,7 @@ export async function connect (title: string): Promise { ) // something went wrong with selecting workspace with the selected token await logOut() - navigate({ path: [loginId] }) + invalidError.set(true) return } @@ -118,10 +118,7 @@ export async function connect (title: string): Promise { }, onUnauthorized: () => { void logOut().then(() => { - navigate({ - path: [loginId], - query: {} - }) + invalidError.set(true) }) }, // We need to refresh all active live queries and clear old queries. @@ -224,6 +221,7 @@ export async function connect (title: string): Promise { } } + invalidError.set(false) versionError.set(undefined) // Update window title document.title = [wsUrl, title].filter((it) => it).join(' - ') diff --git a/plugins/login-resources/src/components/SelectWorkspace.svelte b/plugins/login-resources/src/components/SelectWorkspace.svelte index aeb6c40c4d..ffec72c8af 100644 --- a/plugins/login-resources/src/components/SelectWorkspace.svelte +++ b/plugins/login-resources/src/components/SelectWorkspace.svelte @@ -132,8 +132,6 @@ throw err } } - $: isAdmin = isAdminUser() - let search: string = '' @@ -151,7 +149,7 @@
- {#if isAdmin} + {#if workspaces.length > 10}
@@ -188,24 +186,7 @@ {/if} - {#if isAdmin} - {workspace.url} - {#if workspace.region !== undefined} - at ({workspace.region}) - {/if} - {/if}
- {#if isAdmin} - {#if workspace.backupInfo != null} - {@const sz = workspace.backupInfo.dataSize + workspace.backupInfo.blobsSize} - {@const szGb = Math.round((sz * 100) / 1024) / 100} - {#if szGb > 0} - - {Math.round((sz * 100) / 1024) / 100}Gb - - {:else} - - {Math.round(sz)}Mb - - {/if} - {/if} - {/if} ({lastUsageDays} days)
diff --git a/plugins/login-resources/src/utils.ts b/plugins/login-resources/src/utils.ts index 1796219cd6..3f7d945c2c 100644 --- a/plugins/login-resources/src/utils.ts +++ b/plugins/login-resources/src/utils.ts @@ -41,6 +41,7 @@ import platform, { import presentation from '@hcengineering/presentation' import { getCurrentLocation, + isSameSegments, locationStorageKeyId, locationToUrl, navigate, @@ -297,6 +298,21 @@ export async function getAccount (doNavigate: boolean = true): Promise
- {decodeTokenPayload(getMetadata(presentation.metadata.Token) ?? '').workspace} + {decodeTokenPayload(getMetadata(presentation.metadata.Token) ?? '').workspace ?? ''}
{/if}
diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte index f1d2323f90..290ce37bfb 100644 --- a/plugins/workbench-resources/src/components/Workbench.svelte +++ b/plugins/workbench-resources/src/components/Workbench.svelte @@ -78,7 +78,8 @@ showPopup, TooltipInstance, workbenchSeparators, - resizeObserver + resizeObserver, + isSameSegments } from '@hcengineering/ui' import view from '@hcengineering/view' import { @@ -429,9 +430,12 @@ const last = localStorage.getItem(`${locationStorageKeyId}_${loc.path[1]}`) if (last != null) { const lastValue = JSON.parse(last) - navigateDone = navigate(lastValue) - if (navigateDone) { - return + + if (isSameSegments(lastValue, loc, 2)) { + navigateDone = navigate(lastValue) + if (navigateDone) { + return + } } } if (app === undefined && !navigateDone) { diff --git a/server/account/src/__tests__/token.test.ts b/server/account/src/__tests__/token.test.ts new file mode 100644 index 0000000000..af878183f2 --- /dev/null +++ b/server/account/src/__tests__/token.test.ts @@ -0,0 +1,40 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { generateToken } from '@hcengineering/server-token' + +export function decodeTokenPayload (token: string): any { + try { + return JSON.parse(atob(token.split('.')[1])) + } catch (err: any) { + console.error(err) + return {} + } +} +describe('generate-tokens', () => { + it('should generate tokens', async () => { + const extra: Record = { confirmed: 'true' } + const token = generateToken('mike@some.host', { name: '' }, extra, 'secret') + console.log(token) + const decodedPayload = decodeTokenPayload(token) + expect(decodedPayload).toEqual({ + confirmed: 'true', + email: 'mike@some.host', + workspace: '' + }) + expect(decodedPayload.admin).not.toBe('true') + }) +}) diff --git a/server/token/src/token.ts b/server/token/src/token.ts index 13599e6450..580813ab51 100644 --- a/server/token/src/token.ts +++ b/server/token/src/token.ts @@ -32,11 +32,12 @@ const getSecret = (): string => { export function generateToken ( accountUuid: PersonUuid, workspaceUuid?: WorkspaceUuid, - extra?: Record + extra?: Record, + secret?: string ): string { return encode( { ...(extra !== undefined ? { extra } : {}), account: accountUuid, workspace: workspaceUuid }, - getSecret() + secret ?? getSecret() ) } diff --git a/tests/sanity/tests/model/tracker/issues-page.ts b/tests/sanity/tests/model/tracker/issues-page.ts index c883df7bc2..e42acb3a8a 100644 --- a/tests/sanity/tests/model/tracker/issues-page.ts +++ b/tests/sanity/tests/model/tracker/issues-page.ts @@ -167,7 +167,9 @@ export class IssuesPage extends CommonTrackerPage { appleseedJohnButton = (): Locator => this.page.locator('button.menu-item:has-text("Appleseed John")') estimationEditor = (): Locator => this.page.locator('#estimation-editor') dueDateButton = (): Locator => this.page.locator('button:has-text("Due date")') - specificDay = (day: string): Locator => this.page.locator(`.date-popup-container div.day >> text=${day}`).first() + specificDay = (day: string): Locator => + this.page.locator(`.date-popup-container div.day:not(.wrongMonth) >> text=${day}`).first() + inputTextPlaceholder = (): Locator => this.page.getByPlaceholder('Type text...') confirmInput = (): Locator => this.page.locator('.selectPopup button') diff --git a/ws-tests/sanity/tests/workspace/archive.spec.ts b/ws-tests/sanity/tests/workspace/archive.spec.ts index 1148df8b9e..8a0b04623c 100644 --- a/ws-tests/sanity/tests/workspace/archive.spec.ts +++ b/ws-tests/sanity/tests/workspace/archive.spec.ts @@ -27,16 +27,16 @@ test.describe('Workspace Archive tests', () => { test('New workspace with date, archive, unarchive', async ({ page, browser, request }) => { const api: ApiEndpoint = new ApiEndpoint(request) - const workspaceName = generateId() - const workspaceInfo = await api.createWorkspaceWithLogin(workspaceName, 'user1', '1234') + const wsId = generateId(5) + const workspaceInfo = await api.createWorkspaceWithLogin(wsId, 'user1', '1234') const newIssue: NewIssue = { - title: `Issue with all parameters and attachments-${generateId()}`, + title: `Issue with all parameters and attachments-${wsId}`, description: 'Created issue with all parameters and attachments description', status: 'In Progress', priority: 'Urgent', createLabel: true, - labels: `CREATE-ISSUE-${generateId()}`, + labels: `CREATE-ISSUE-${wsId}`, component: 'No component', estimation: '2', milestone: 'No Milestone', @@ -46,7 +46,7 @@ test.describe('Workspace Archive tests', () => { await loginPage.goto() await loginPage.login('user1', '1234') - await selectWorkspacePage.selectWorkspace(workspaceName) + await selectWorkspacePage.selectWorkspace(wsId) await trackerNavigationMenuPage.openIssuesForProject('Default') await issuesPage.clickModelSelectorAll() @@ -98,9 +98,11 @@ test.describe('Workspace Archive tests', () => { await test.step('Check workspace is active again', async () => { await page.reload() - await selectWorkspacePage.selectWorkspace(workspaceName) + await selectWorkspacePage.selectWorkspace(wsId) const issuesDetailsPage = new IssuesDetailsPage(page) + // Should be restored from previos remembered location. + // await issuesPage.openIssueByName(newIssue.title) await issuesDetailsPage.checkIssue(newIssue) }) })