mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-5283 Validate token in collaborator (#4517)
Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
parent
a1f64176fd
commit
697fb2732c
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@ -90,6 +90,7 @@
|
||||
"env": {
|
||||
"SECRET": "secret",
|
||||
"METRICS_CONSOLE": "true",
|
||||
"ACCOUNTS_URL": "http://localhost:3000",
|
||||
"TRANSACTOR_URL": "ws://localhost:3333",
|
||||
"UPLOAD_URL": "/files",
|
||||
"MONGO_URL": "mongodb://localhost:27017",
|
||||
|
@ -101,6 +101,7 @@ services:
|
||||
environment:
|
||||
- COLLABORATOR_PORT=3078
|
||||
- SECRET=secret
|
||||
- ACCOUNTS_URL=http://account:3000
|
||||
- TRANSACTOR_URL=ws://transactor:3333
|
||||
- UPLOAD_URL=/files
|
||||
- MONGO_URL=mongodb://mongodb:27017
|
||||
|
36
server/collaborator/src/account.ts
Normal file
36
server/collaborator/src/account.ts
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright © 2024 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 { WorkspaceLoginInfo } from '@hcengineering/account'
|
||||
import config from './config'
|
||||
|
||||
export async function getWorkspaceInfo (token: string): Promise<WorkspaceLoginInfo> {
|
||||
const accountsUrl = config.AccountsUrl
|
||||
const workspaceInfo = await (
|
||||
await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'getWorkspaceInfo',
|
||||
params: []
|
||||
})
|
||||
})
|
||||
).json()
|
||||
|
||||
return workspaceInfo.result as WorkspaceLoginInfo
|
||||
}
|
@ -24,6 +24,7 @@ export interface Config {
|
||||
|
||||
Port: number
|
||||
|
||||
AccountsUrl: string
|
||||
TransactorUrl: string
|
||||
MongoUrl: string
|
||||
UploadUrl: string
|
||||
@ -38,6 +39,7 @@ const envMap: { [key in keyof Config]: string } = {
|
||||
Secret: 'SECRET',
|
||||
Interval: 'INTERVAL',
|
||||
Port: 'COLLABORATOR_PORT',
|
||||
AccountsUrl: 'ACCOUNTS_URL',
|
||||
TransactorUrl: 'TRANSACTOR_URL',
|
||||
MongoUrl: 'MONGO_URL',
|
||||
UploadUrl: 'UPLOAD_URL',
|
||||
@ -50,6 +52,7 @@ const required: Array<keyof Config> = [
|
||||
'Secret',
|
||||
'ServiceID',
|
||||
'Port',
|
||||
'AccountsUrl',
|
||||
'TransactorUrl',
|
||||
'MongoUrl',
|
||||
'MinioEndpoint',
|
||||
@ -63,6 +66,7 @@ const config: Config = (() => {
|
||||
ServiceID: process.env[envMap.ServiceID] ?? 'collaborator-service',
|
||||
Interval: parseInt(process.env[envMap.Interval] ?? '30000'),
|
||||
Port: parseInt(process.env[envMap.Port] ?? '3078'),
|
||||
AccountsUrl: process.env[envMap.AccountsUrl],
|
||||
TransactorUrl: process.env[envMap.TransactorUrl],
|
||||
MongoUrl: process.env[envMap.MongoUrl],
|
||||
UploadUrl: process.env[envMap.UploadUrl] ?? '/files',
|
||||
|
@ -27,6 +27,7 @@ import { MongoClient } from 'mongodb'
|
||||
import { WebSocket, WebSocketServer } from 'ws'
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs'
|
||||
|
||||
import { getWorkspaceInfo } from './account'
|
||||
import { Config } from './config'
|
||||
import { Context, buildContext } from './context'
|
||||
import { ActionsExtension } from './extensions/action'
|
||||
@ -145,20 +146,25 @@ export async function start (
|
||||
ctx.measure('authenticate', 1)
|
||||
const context = buildContext(data, controller)
|
||||
|
||||
// verify workspace can be accessed with the token
|
||||
const workspaceInfo = await getWorkspaceInfo(data.token)
|
||||
|
||||
// verify document name
|
||||
let documentName = data.documentName
|
||||
if (documentName.includes('://')) {
|
||||
documentName = documentName.split('://', 2)[1]
|
||||
}
|
||||
|
||||
// if (documentName.includes('/')) {
|
||||
// const [workspace] = documentName.split('/', 2)
|
||||
// if (workspace !== context.workspaceId.name) {
|
||||
// throw new Error('documentName must include workspace')
|
||||
// }
|
||||
// } else {
|
||||
// throw new Error('documentName must include workspace')
|
||||
// }
|
||||
if (documentName.includes('/')) {
|
||||
const [workspaceUrl] = documentName.split('/', 2)
|
||||
|
||||
// verify workspace url in the document matches the token
|
||||
if (workspaceInfo.workspace !== workspaceUrl) {
|
||||
throw new Error('documentName must include workspace')
|
||||
}
|
||||
} else {
|
||||
throw new Error('documentName must include workspace')
|
||||
}
|
||||
|
||||
return context
|
||||
},
|
||||
|
@ -23,20 +23,20 @@ import { Context } from '../context'
|
||||
import { StorageAdapter } from './adapter'
|
||||
|
||||
interface MinioDocumentId {
|
||||
workspace: string
|
||||
workspaceUrl: string
|
||||
minioDocumentId: string
|
||||
}
|
||||
|
||||
function parseDocumentId (documentId: string): MinioDocumentId {
|
||||
const [workspace, minioDocumentId] = documentId.split('/')
|
||||
const [workspaceUrl, minioDocumentId] = documentId.split('/')
|
||||
return {
|
||||
workspace: workspace ?? '',
|
||||
workspaceUrl: workspaceUrl ?? '',
|
||||
minioDocumentId: minioDocumentId ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
function isValidDocumentId (documentId: MinioDocumentId, context: Context): boolean {
|
||||
return documentId.minioDocumentId !== '' // && documentId.workspace === context.workspaceId.name
|
||||
function isValidDocumentId (documentId: MinioDocumentId): boolean {
|
||||
return documentId.minioDocumentId !== '' && documentId.workspaceUrl !== ''
|
||||
}
|
||||
|
||||
function maybePlatformDocumentId (documentId: string): boolean {
|
||||
@ -52,9 +52,9 @@ export class MinioStorageAdapter implements StorageAdapter {
|
||||
async loadDocument (documentId: string, context: Context): Promise<YDoc | undefined> {
|
||||
const { workspaceId } = context
|
||||
|
||||
const { workspace, minioDocumentId } = parseDocumentId(documentId)
|
||||
const { workspaceUrl, minioDocumentId } = parseDocumentId(documentId)
|
||||
|
||||
if (!isValidDocumentId({ workspace, minioDocumentId }, context)) {
|
||||
if (!isValidDocumentId({ workspaceUrl, minioDocumentId })) {
|
||||
console.warn('malformed document id', documentId)
|
||||
return undefined
|
||||
}
|
||||
@ -91,9 +91,9 @@ export class MinioStorageAdapter implements StorageAdapter {
|
||||
async saveDocument (documentId: string, document: YDoc, context: Context): Promise<void> {
|
||||
const { clientFactory, workspaceId } = context
|
||||
|
||||
const { workspace, minioDocumentId } = parseDocumentId(documentId)
|
||||
const { workspaceUrl, minioDocumentId } = parseDocumentId(documentId)
|
||||
|
||||
if (!isValidDocumentId({ workspace, minioDocumentId }, context)) {
|
||||
if (!isValidDocumentId({ workspaceUrl, minioDocumentId })) {
|
||||
console.warn('malformed document id', documentId)
|
||||
return undefined
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import { Context } from '../context'
|
||||
import { StorageAdapter } from './adapter'
|
||||
|
||||
interface MongodbDocumentId {
|
||||
workspace: string
|
||||
workspaceUrl: string
|
||||
objectDomain: string
|
||||
objectId: string
|
||||
objectAttr: string
|
||||
@ -32,7 +32,7 @@ interface MongodbDocumentId {
|
||||
function parseDocumentId (documentId: string): MongodbDocumentId {
|
||||
const [workspace, objectDomain, objectId, objectAttr] = documentId.split('/')
|
||||
return {
|
||||
workspace: workspace ?? '',
|
||||
workspaceUrl: workspace ?? '',
|
||||
objectId: objectId ?? '',
|
||||
objectDomain: objectDomain ?? '',
|
||||
objectAttr: objectAttr ?? ''
|
||||
@ -54,9 +54,9 @@ export class MongodbStorageAdapter implements StorageAdapter {
|
||||
) {}
|
||||
|
||||
async loadDocument (documentId: string, context: Context): Promise<YDoc | undefined> {
|
||||
const { workspace, objectId, objectDomain, objectAttr } = parseDocumentId(documentId)
|
||||
const { workspaceUrl, objectId, objectDomain, objectAttr } = parseDocumentId(documentId)
|
||||
|
||||
if (!isValidDocumentId({ workspace, objectId, objectDomain, objectAttr }, context)) {
|
||||
if (!isValidDocumentId({ workspaceUrl, objectId, objectDomain, objectAttr }, context)) {
|
||||
console.warn('malformed document id', documentId)
|
||||
return undefined
|
||||
}
|
||||
|
@ -22,16 +22,16 @@ import { Context } from '../context'
|
||||
import { StorageAdapter } from './adapter'
|
||||
|
||||
interface PlatformDocumentId {
|
||||
workspace: string
|
||||
workspaceUrl: string
|
||||
objectClass: Ref<Class<Doc>>
|
||||
objectId: Ref<Doc>
|
||||
objectAttr: string
|
||||
}
|
||||
|
||||
function parseDocumentId (documentId: string): PlatformDocumentId {
|
||||
const [workspace, objectClass, objectId, objectAttr] = documentId.split('/')
|
||||
const [workspaceUrl, objectClass, objectId, objectAttr] = documentId.split('/')
|
||||
return {
|
||||
workspace: workspace ?? '',
|
||||
workspaceUrl: workspaceUrl ?? '',
|
||||
objectClass: (objectClass ?? '') as Ref<Class<Doc>>,
|
||||
objectId: (objectId ?? '') as Ref<Doc>,
|
||||
objectAttr: objectAttr ?? ''
|
||||
@ -53,9 +53,9 @@ export class PlatformStorageAdapter implements StorageAdapter {
|
||||
|
||||
async loadDocument (documentId: string, context: Context): Promise<YDoc | undefined> {
|
||||
const { clientFactory } = context
|
||||
const { workspace, objectId, objectClass, objectAttr } = parseDocumentId(documentId)
|
||||
const { workspaceUrl, objectId, objectClass, objectAttr } = parseDocumentId(documentId)
|
||||
|
||||
if (!isValidDocumentId({ workspace, objectId, objectClass, objectAttr }, context)) {
|
||||
if (!isValidDocumentId({ workspaceUrl, objectId, objectClass, objectAttr }, context)) {
|
||||
console.warn('malformed document id', documentId)
|
||||
return undefined
|
||||
}
|
||||
@ -82,9 +82,9 @@ export class PlatformStorageAdapter implements StorageAdapter {
|
||||
|
||||
async saveDocument (documentId: string, document: YDoc, context: Context): Promise<void> {
|
||||
const { clientFactory } = context
|
||||
const { workspace, objectId, objectClass, objectAttr } = parseDocumentId(documentId)
|
||||
const { workspaceUrl, objectId, objectClass, objectAttr } = parseDocumentId(documentId)
|
||||
|
||||
if (!isValidDocumentId({ workspace, objectId, objectClass, objectAttr }, context)) {
|
||||
if (!isValidDocumentId({ workspaceUrl, objectId, objectClass, objectAttr }, context)) {
|
||||
console.warn('malformed document id', documentId)
|
||||
return undefined
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ services:
|
||||
environment:
|
||||
- COLLABORATOR_PORT=3078
|
||||
- SECRET=secret
|
||||
- ACCOUNTS_URL=http://account:3003
|
||||
- TRANSACTOR_URL=ws://transactor:3334
|
||||
- UPLOAD_URL=/files
|
||||
- MONGO_URL=mongodb://mongodb:27018
|
||||
|
Loading…
Reference in New Issue
Block a user