mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
fix: secure blobs (#10490)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
@@ -519,6 +519,7 @@ services:
|
||||
- REGION=cockroach
|
||||
- QUEUE_CONFIG=${QUEUE_CONFIG}
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
|
||||
- SECURE=true
|
||||
restart: unless-stopped
|
||||
hulylake:
|
||||
image: hardcoreeng/hulylake
|
||||
|
||||
@@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core'
|
||||
import { FileStorage, FileStorageUploadOptions } from '../types'
|
||||
import { uploadMultipart, uploadXhr } from '../upload'
|
||||
|
||||
const getPathname = (url: string): string => {
|
||||
const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost'
|
||||
return new URL(url, base).pathname
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class DatalakeStorage implements FileStorage {
|
||||
constructor (private readonly baseUrl: string) {}
|
||||
@@ -26,6 +31,11 @@ export class DatalakeStorage implements FileStorage {
|
||||
return concatLink(this.baseUrl, path)
|
||||
}
|
||||
|
||||
getCookiePath (workspace: string): string {
|
||||
const url = concatLink(this.baseUrl, `/blob/${workspace}`)
|
||||
return getPathname(url)
|
||||
}
|
||||
|
||||
async getFileMeta (token: string, workspace: string, file: string): Promise<Record<string, any>> {
|
||||
const url = concatLink(this.baseUrl, `/meta/${encodeURIComponent(workspace)}/${encodeURIComponent(file)}`)
|
||||
try {
|
||||
|
||||
@@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core'
|
||||
import { FileStorage, FileStorageUploadOptions } from '../types'
|
||||
import { uploadXhr } from '../upload'
|
||||
|
||||
const getPathname = (url: string): string => {
|
||||
const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost'
|
||||
return new URL(url, base).pathname
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class FrontStorage implements FileStorage {
|
||||
constructor (private readonly baseUrl: string) {}
|
||||
@@ -26,6 +31,11 @@ export class FrontStorage implements FileStorage {
|
||||
return concatLink(this.baseUrl, path)
|
||||
}
|
||||
|
||||
getCookiePath (workspace: string): string {
|
||||
const url = concatLink(this.baseUrl, `/${workspace}`)
|
||||
return getPathname(url)
|
||||
}
|
||||
|
||||
async getFileMeta (token: string, workspace: string, file: string): Promise<Record<string, any>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ import { concatLink } from '@hcengineering/core'
|
||||
import { FileStorage, FileStorageUploadOptions } from '../types'
|
||||
import { uploadXhr } from '../upload'
|
||||
|
||||
const getPathname = (url: string): string => {
|
||||
const base = window?.location?.href !== undefined ? window.location.href : 'http://localhost'
|
||||
return new URL(url, base).pathname
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class HulylakeStorage implements FileStorage {
|
||||
constructor (private readonly baseUrl: string) {}
|
||||
@@ -26,6 +31,11 @@ export class HulylakeStorage implements FileStorage {
|
||||
return concatLink(this.baseUrl, path)
|
||||
}
|
||||
|
||||
getCookiePath (workspace: string): string {
|
||||
const url = concatLink(this.baseUrl, `/api/${workspace}`)
|
||||
return getPathname(url)
|
||||
}
|
||||
|
||||
async getFileMeta (token: string, workspace: string, file: string): Promise<Record<string, any>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface FileStorageUploadOptions {
|
||||
/** @public */
|
||||
export interface FileStorage {
|
||||
getFileUrl: (workspace: string, file: string, filename?: string) => string
|
||||
getCookiePath: (workspace: string) => string
|
||||
getFileMeta: (token: string, workspace: string, file: string) => Promise<Record<string, any>>
|
||||
uploadFile: (
|
||||
token: string,
|
||||
|
||||
@@ -891,15 +891,19 @@ export function isSpaceClass (_class: Ref<Class<Doc>>): boolean {
|
||||
}
|
||||
|
||||
export function setPresentationCookie (token: string, workspaceUuid: WorkspaceUuid): void {
|
||||
function setToken (path: string): void {
|
||||
const res =
|
||||
encodeURIComponent(plugin.metadata.Token.replaceAll(':', '-')) +
|
||||
'=' +
|
||||
encodeURIComponent(token) +
|
||||
`; path=${path}`
|
||||
document.cookie = res
|
||||
const cookieName = encodeURIComponent(plugin.metadata.Token.replaceAll(':', '-'))
|
||||
const cookieValue = encodeURIComponent(token)
|
||||
|
||||
const storage = getMetadata(plugin.metadata.FileStorage)
|
||||
if (storage !== undefined) {
|
||||
let path = `/files/${workspaceUuid}`
|
||||
try {
|
||||
path = storage.getCookiePath(workspaceUuid)
|
||||
} catch {}
|
||||
|
||||
const normalized = path.startsWith('/') ? path : `/${path}`
|
||||
document.cookie = `${cookieName}=${cookieValue}; path=${normalized}`
|
||||
}
|
||||
setToken('/files/' + workspaceUuid)
|
||||
}
|
||||
|
||||
export const upgradeDownloadProgress = writable(-1)
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface Config {
|
||||
DbUrl: string
|
||||
Buckets: BucketConfig[]
|
||||
CleanupInterval: number
|
||||
Secure: boolean
|
||||
Readonly: boolean
|
||||
Cache: CacheConfig
|
||||
}
|
||||
@@ -86,6 +87,7 @@ const config: Config = (() => {
|
||||
AccountsUrl: process.env.ACCOUNTS_URL,
|
||||
DbUrl: process.env.DB_URL,
|
||||
Buckets: parseBucketsConfig(process.env.BUCKETS),
|
||||
Secure: process.env.SECURE === 'true',
|
||||
Readonly: process.env.READONLY === 'true',
|
||||
Cache: {
|
||||
enabled: process.env.CACHE_ENABLED !== 'false',
|
||||
|
||||
@@ -38,6 +38,14 @@ export const keepAlive = (options: KeepAliveOptions): RequestHandler => {
|
||||
}
|
||||
}
|
||||
|
||||
export const withOptionalAuth = (secure: boolean): RequestHandler => {
|
||||
return secure
|
||||
? withAuthorization
|
||||
: (req: Request, res: Response, next: NextFunction) => {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export const withAdminAuthorization = (req: RequestWithAuth, res: Response, next: NextFunction): void => {
|
||||
try {
|
||||
const token = extractToken(req.headers)
|
||||
|
||||
@@ -36,7 +36,8 @@ import {
|
||||
withAuthorization,
|
||||
withBlob,
|
||||
withWorkspace,
|
||||
withReadonly
|
||||
withReadonly,
|
||||
withOptionalAuth
|
||||
} from './middleware'
|
||||
import {
|
||||
handleBlobDelete,
|
||||
@@ -176,13 +177,33 @@ export async function createServer (
|
||||
|
||||
app.get('/blob/:workspace', withAdminAuthorization, withWorkspace, wrapRequest(ctx, 'listBlobs', handleBlobList))
|
||||
|
||||
app.head('/blob/:workspace/:name', withBlob, wrapRequest(ctx, 'headBlob', handleBlobHead))
|
||||
app.head(
|
||||
'/blob/:workspace/:name',
|
||||
withOptionalAuth(config.Secure),
|
||||
withBlob,
|
||||
wrapRequest(ctx, 'headBlob', handleBlobHead)
|
||||
)
|
||||
|
||||
app.head('/blob/:workspace/:name/:filename', withBlob, wrapRequest(ctx, 'headBlob', handleBlobHead))
|
||||
app.head(
|
||||
'/blob/:workspace/:name/:filename',
|
||||
withOptionalAuth(config.Secure),
|
||||
withBlob,
|
||||
wrapRequest(ctx, 'headBlob', handleBlobHead)
|
||||
)
|
||||
|
||||
app.get('/blob/:workspace/:name', withBlob, wrapRequest(ctx, 'getBlob', handleBlobGet))
|
||||
app.get(
|
||||
'/blob/:workspace/:name',
|
||||
withOptionalAuth(config.Secure),
|
||||
withBlob,
|
||||
wrapRequest(ctx, 'getBlob', handleBlobGet)
|
||||
)
|
||||
|
||||
app.get('/blob/:workspace/:name/:filename', withBlob, wrapRequest(ctx, 'getBlob', handleBlobGet))
|
||||
app.get(
|
||||
'/blob/:workspace/:name/:filename',
|
||||
withOptionalAuth(config.Secure),
|
||||
withBlob,
|
||||
wrapRequest(ctx, 'getBlob', handleBlobGet)
|
||||
)
|
||||
|
||||
app.delete('/blob/:workspace/:name', withAuthorization, withBlob, wrapRequest(ctx, 'deleteBlob', handleBlobDelete))
|
||||
|
||||
@@ -206,7 +227,12 @@ export async function createServer (
|
||||
|
||||
// Blob meta
|
||||
|
||||
app.get('/meta/:workspace/:name', withBlob, wrapRequest(ctx, 'getMeta', handleMetaGet))
|
||||
app.get(
|
||||
'/meta/:workspace/:name',
|
||||
withOptionalAuth(config.Secure),
|
||||
withBlob,
|
||||
wrapRequest(ctx, 'getMeta', handleMetaGet)
|
||||
)
|
||||
|
||||
app.put('/meta/:workspace/:name', withAuthorization, withBlob, wrapRequest(ctx, 'putMeta', handleMetaPut))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user