mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 04:25:03 +02:00
fix: ensure workspace and account uuid when generating token (#9246)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Generated
+4
-2
@@ -4960,7 +4960,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/presentation@file:projects/presentation.tgz':
|
||||
resolution: {integrity: sha512-iKdFwGx+ciUZVYBDuytjG/Ort+6bkuKoIDtTUGA7X+z5YQD+neMusa4Skd9TsmsvsqMcjdxhhI0+TQpEnAjf1A==, tarball: file:projects/presentation.tgz}
|
||||
resolution: {integrity: sha512-bMSkIMVw78JU9foCyHxngjWO5uPLhczp/XgPsqn+rVsQ+g4fm5WiLwEYQt504vk0DspkFc7xPjDFnMyOpwfR6w==, tarball: file:projects/presentation.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/print-assets@file:projects/print-assets.tgz':
|
||||
@@ -5352,7 +5352,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-token@file:projects/server-token.tgz':
|
||||
resolution: {integrity: sha512-4cC2GIyVh+Wrl5bVS3x7/x+ZbYwECOSyuVVUuHjcpSsEELsA0fLflSobf6SIiyVVoIJ5aZfJSDS1orukYRYiiA==, tarball: file:projects/server-token.tgz}
|
||||
resolution: {integrity: sha512-KWi8JET7wqjy0yZvPBx6LhInIBN7iXI9Af0sfQMAdKHAyxkFBAZJVY/Moun8gM22JMirucC4l0SQJklsLOZwxw==, tarball: file:projects/server-token.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/server-tool@file:projects/server-tool.tgz':
|
||||
@@ -25974,6 +25974,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 22.15.29
|
||||
'@types/uuid': 8.3.4
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3)
|
||||
eslint: 8.56.0
|
||||
@@ -25986,6 +25987,7 @@ snapshots:
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3)))(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
uuid: 8.3.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
|
||||
@@ -35,7 +35,7 @@ prepare()
|
||||
jest.setTimeout(500000)
|
||||
|
||||
class TestWorkspaceManager extends WorkspaceManager {
|
||||
public async getWorkspaceInfo (token?: string): Promise<WorkspaceInfoWithStatus | undefined> {
|
||||
public async getWorkspaceInfo (ctx: MeasureContext, token?: string): Promise<WorkspaceInfoWithStatus | undefined> {
|
||||
const decodedToken = decodeToken(token ?? '')
|
||||
return {
|
||||
uuid: decodedToken.workspace,
|
||||
|
||||
@@ -116,14 +116,15 @@ export class WorkspaceManager {
|
||||
for (const m of msg) {
|
||||
const ws = m.id as WorkspaceUuid
|
||||
|
||||
const indexer = await this.getIndexer(
|
||||
this.ctx,
|
||||
ws,
|
||||
generateToken(systemAccountUuid, ws, {
|
||||
service: 'fulltext'
|
||||
}),
|
||||
true
|
||||
)
|
||||
let token: string
|
||||
try {
|
||||
token = generateToken(systemAccountUuid, ws, { service: 'fulltext' })
|
||||
} catch (err: any) {
|
||||
this.ctx.error('Error generating token', { err, systemAccountUuid, ws })
|
||||
continue
|
||||
}
|
||||
|
||||
const indexer = await this.getIndexer(this.ctx, ws, token, true)
|
||||
await indexer?.fulltext.processDocuments(this.ctx, m.value, control)
|
||||
}
|
||||
}
|
||||
@@ -137,17 +138,20 @@ export class WorkspaceManager {
|
||||
const ws = m.id as WorkspaceUuid
|
||||
|
||||
for (const mm of m.value) {
|
||||
let token: string
|
||||
try {
|
||||
token = generateToken(systemAccountUuid, ws, { service: 'fulltext' })
|
||||
} catch (err: any) {
|
||||
this.ctx.error('Error generating token', { err, systemAccountUuid, ws })
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
mm.type === QueueWorkspaceEvent.Created ||
|
||||
mm.type === QueueWorkspaceEvent.Restored ||
|
||||
mm.type === QueueWorkspaceEvent.FullReindex
|
||||
) {
|
||||
const indexer = await this.getIndexer(
|
||||
this.ctx,
|
||||
ws,
|
||||
generateToken(systemAccountUuid, ws, { service: 'fulltext' }),
|
||||
true
|
||||
)
|
||||
const indexer = await this.getIndexer(this.ctx, ws, token, true)
|
||||
if (indexer !== undefined) {
|
||||
await indexer.dropWorkspace() // TODO: Add heartbeat
|
||||
const classes = await indexer.getIndexClassess()
|
||||
@@ -161,8 +165,7 @@ export class WorkspaceManager {
|
||||
mm.type === QueueWorkspaceEvent.Archived ||
|
||||
mm.type === QueueWorkspaceEvent.ClearIndex
|
||||
) {
|
||||
const token = generateToken(systemAccountUuid, ws, { service: 'fulltext' })
|
||||
const workspaceInfo = await this.getWorkspaceInfo(token)
|
||||
const workspaceInfo = await this.getWorkspaceInfo(this.ctx, token)
|
||||
if (workspaceInfo !== undefined) {
|
||||
if (workspaceInfo.dataId != null) {
|
||||
await this.fulltextAdapter.clean(this.ctx, workspaceInfo.dataId as unknown as WorkspaceUuid)
|
||||
@@ -170,12 +173,7 @@ export class WorkspaceManager {
|
||||
await this.fulltextAdapter.clean(this.ctx, workspaceInfo.uuid)
|
||||
}
|
||||
} else if (mm.type === QueueWorkspaceEvent.Reindex) {
|
||||
const indexer = await this.getIndexer(
|
||||
this.ctx,
|
||||
ws,
|
||||
generateToken(systemAccountUuid, ws, { service: 'fulltext' }),
|
||||
true
|
||||
)
|
||||
const indexer = await this.getIndexer(this.ctx, ws, token, true)
|
||||
const mmd = mm as QueueWorkspaceReindexMessage
|
||||
await indexer?.reindex(this.ctx, mmd.domain, mmd.classes, control)
|
||||
}
|
||||
@@ -183,9 +181,14 @@ export class WorkspaceManager {
|
||||
}
|
||||
}
|
||||
|
||||
public async getWorkspaceInfo (token?: string): Promise<WorkspaceInfoWithStatus | undefined> {
|
||||
public async getWorkspaceInfo (ctx: MeasureContext, token?: string): Promise<WorkspaceInfoWithStatus | undefined> {
|
||||
const accountClient = getAccountClient(token)
|
||||
return await accountClient.getWorkspaceInfo(false)
|
||||
try {
|
||||
return await accountClient.getWorkspaceInfo(false)
|
||||
} catch (err: any) {
|
||||
ctx.error('Workspace not available for token', { err })
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async getTransactorAPIEndpoint (token: string): Promise<string | undefined> {
|
||||
@@ -200,9 +203,8 @@ export class WorkspaceManager {
|
||||
): Promise<WorkspaceIndexer | undefined> {
|
||||
let idx = this.indexers.get(workspace)
|
||||
if (idx === undefined && create) {
|
||||
const workspaceInfo = await this.getWorkspaceInfo(token)
|
||||
const workspaceInfo = await this.getWorkspaceInfo(ctx, token)
|
||||
if (workspaceInfo === undefined) {
|
||||
ctx.error('Workspace not available for token')
|
||||
return
|
||||
}
|
||||
ctx.warn('indexer created', { workspace })
|
||||
|
||||
@@ -109,7 +109,10 @@ describe('server', () => {
|
||||
const serverShutdown = startHttpServer(toolCtx, sessionMgr, port, opt.accountsUrl, createDummyStorageAdapter())
|
||||
|
||||
function connect (): WebSocket {
|
||||
const token: string = generateToken('' as PersonUuid, 'latest' as WorkspaceUuid)
|
||||
const token: string = generateToken(
|
||||
'123e4567-e89b-12d3-a456-426614174000' as PersonUuid,
|
||||
'123e4567-e89b-12d3-a456-426614174001' as WorkspaceUuid
|
||||
)
|
||||
return new WebSocket(`ws://localhost:${port}/${token}`)
|
||||
}
|
||||
|
||||
@@ -271,7 +274,10 @@ describe('server', () => {
|
||||
|
||||
try {
|
||||
//
|
||||
const token: string = generateToken('my-account-uuid' as PersonUuid, 'latest' as WorkspaceUuid)
|
||||
const token: string = generateToken(
|
||||
'123e4567-e89b-12d3-a456-426614174000' as PersonUuid,
|
||||
'123e4567-e89b-12d3-a456-426614174001' as WorkspaceUuid
|
||||
)
|
||||
let clearTo: any
|
||||
const timeoutPromise = new Promise<void>((resolve) => {
|
||||
clearTo = setTimeout(resolve, 4000)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// 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 type { PersonUuid, WorkspaceUuid } from '@hcengineering/core'
|
||||
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<string, any> = { confirmed: 'true' }
|
||||
const token = generateToken('mike@some.host' as PersonUuid, '' as WorkspaceUuid, extra, 'secret')
|
||||
console.log(token)
|
||||
const decodedPayload = decodeTokenPayload(token)
|
||||
expect(decodedPayload).toEqual({
|
||||
extra: {
|
||||
confirmed: 'true'
|
||||
},
|
||||
account: 'mike@some.host',
|
||||
workspace: ''
|
||||
})
|
||||
expect(decodedPayload.admin).not.toBe('true')
|
||||
})
|
||||
})
|
||||
@@ -35,12 +35,14 @@
|
||||
"typescript": "^5.8.3",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"@types/jest": "^29.5.5"
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/uuid": "^8.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"jwt-simple": "^0.5.6"
|
||||
"jwt-simple": "^0.5.6",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"repository": "https://github.com/hcengineering/platform",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright © 2025 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 type { PersonUuid, WorkspaceUuid } from '@hcengineering/core'
|
||||
import { generateToken } from '../token'
|
||||
|
||||
export function decodeTokenPayload (token: string): any {
|
||||
try {
|
||||
return JSON.parse(atob(token.split('.')[1]))
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
describe('generateToken', () => {
|
||||
it('throws TokenError for invalid account uuid', () => {
|
||||
expect(() => {
|
||||
generateToken('invalid-uuid' as PersonUuid, '' as WorkspaceUuid, {}, 'secret')
|
||||
}).toThrow('Invalid account uuid')
|
||||
})
|
||||
|
||||
it('throws TokenError for invalid workspace uuid', () => {
|
||||
expect(() => {
|
||||
generateToken('123e4567-e89b-12d3-a456-426614174000' as PersonUuid, 'invalid-uuid' as WorkspaceUuid, {}, 'secret')
|
||||
}).toThrow('Invalid workspace uuid')
|
||||
})
|
||||
|
||||
it('generates token without extra and workspace', () => {
|
||||
const token = generateToken('123e4567-e89b-12d3-a456-426614174000' as PersonUuid, undefined, undefined, 'secret')
|
||||
const decodedPayload = decodeTokenPayload(token)
|
||||
expect(decodedPayload).toEqual({
|
||||
account: '123e4567-e89b-12d3-a456-426614174000',
|
||||
workspace: undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('should generate token with only required fields', () => {
|
||||
const token = generateToken(
|
||||
'123e4567-e89b-12d3-a456-426614174000' as PersonUuid,
|
||||
'123e4567-e89b-12d3-a456-426614174001' as WorkspaceUuid,
|
||||
undefined,
|
||||
'secret'
|
||||
)
|
||||
const decodedPayload = decodeTokenPayload(token)
|
||||
expect(decodedPayload).toEqual({
|
||||
account: '123e4567-e89b-12d3-a456-426614174000',
|
||||
workspace: '123e4567-e89b-12d3-a456-426614174001'
|
||||
})
|
||||
})
|
||||
|
||||
it('should generate token with extra fields', () => {
|
||||
const extra = { service: 'test' }
|
||||
const token = generateToken(
|
||||
'123e4567-e89b-12d3-a456-426614174000' as PersonUuid,
|
||||
'123e4567-e89b-12d3-a456-426614174001' as WorkspaceUuid,
|
||||
extra,
|
||||
'secret'
|
||||
)
|
||||
const decodedPayload = decodeTokenPayload(token)
|
||||
expect(decodedPayload).toEqual({
|
||||
extra,
|
||||
account: '123e4567-e89b-12d3-a456-426614174000',
|
||||
workspace: '123e4567-e89b-12d3-a456-426614174001'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AccountUuid, MeasureContext, PersonUuid, WorkspaceUuid } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { decode, encode } from 'jwt-simple'
|
||||
import { validate } from 'uuid'
|
||||
import serverPlugin from './plugin'
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,13 @@ export function generateToken (
|
||||
extra?: Record<string, string>,
|
||||
secret?: string
|
||||
): string {
|
||||
if (!validate(accountUuid)) {
|
||||
throw new TokenError('Invalid account uuid')
|
||||
}
|
||||
if (workspaceUuid !== undefined && !validate(workspaceUuid)) {
|
||||
throw new TokenError('Invalid workspace uuid')
|
||||
}
|
||||
|
||||
return encode(
|
||||
{ ...(extra !== undefined ? { extra } : {}), account: accountUuid, workspace: workspaceUuid },
|
||||
secret ?? getSecret()
|
||||
|
||||
Reference in New Issue
Block a user