mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-25 01:04:55 +03:00
Support secret changes and token become unauchorized (#2205)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
55e49257f8
commit
1f64039473
@ -13,7 +13,7 @@
|
||||
"docker:build": "docker build -t hardcoreeng/tool .",
|
||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/tool staging",
|
||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/tool",
|
||||
"run-local": "cross-env MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost MONGO_URL=mongodb://localhost:27017 TRANSACTOR_URL=ws:/localhost:3333 TELEGRAM_DATABASE=telegram-service ELASTIC_URL=http://localhost:9200 REKONI_URL=http://localhost:4004 ts-node ./src/index.ts",
|
||||
"run-local": "cross-env SERVER_SECRET=secret MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost MONGO_URL=mongodb://localhost:27017 TRANSACTOR_URL=ws:/localhost:3333 TELEGRAM_DATABASE=telegram-service ELASTIC_URL=http://localhost:9200 REKONI_URL=http://localhost:4004 ts-node ./src/index.ts",
|
||||
"upgrade": "rushx run-local upgrade",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src && eslint --fix src"
|
||||
|
@ -30,7 +30,7 @@ import {
|
||||
} from '@anticrm/account'
|
||||
import { setMetadata } from '@anticrm/platform'
|
||||
import { backup, backupList, createFileBackupStorage, createMinioBackupStorage, restore } from '@anticrm/server-backup'
|
||||
import { decodeToken, generateToken } from '@anticrm/server-token'
|
||||
import serverToken, { decodeToken, generateToken } from '@anticrm/server-token'
|
||||
import toolPlugin, { prepareTools, version } from '@anticrm/server-tool'
|
||||
import { program } from 'commander'
|
||||
import { Db, MongoClient } from 'mongodb'
|
||||
@ -46,6 +46,12 @@ import { diffWorkspace, dumpWorkspace, restoreWorkspace } from './workspace'
|
||||
|
||||
const { mongodbUri, minio } = prepareTools()
|
||||
|
||||
const serverSecret = process.env.SERVER_SECRET
|
||||
if (serverSecret === undefined) {
|
||||
console.error('please provide server secret')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const transactorUrl = process.env.TRANSACTOR_URL
|
||||
if (transactorUrl === undefined) {
|
||||
console.error('please provide transactor url.')
|
||||
@ -60,6 +66,7 @@ if (elasticUrl === undefined) {
|
||||
|
||||
setMetadata(toolPlugin.metadata.Endpoint, transactorUrl)
|
||||
setMetadata(toolPlugin.metadata.Transactor, transactorUrl)
|
||||
setMetadata(serverToken.metadata.Secret, serverSecret)
|
||||
|
||||
async function withDatabase (uri: string, f: (db: Db, client: MongoClient) => Promise<any>): Promise<void> {
|
||||
console.log(`connecting to database '${uri}'...`)
|
||||
|
@ -73,6 +73,12 @@ export const OK = new Status(Severity.OK, platform.status.OK, {})
|
||||
*/
|
||||
export const ERROR = new Status(Severity.ERROR, platform.status.BadError, {})
|
||||
|
||||
/**
|
||||
* Error Status for Unauthorized
|
||||
* @public
|
||||
*/
|
||||
export const UNAUTHORIZED = new Status(Severity.ERROR, platform.status.Unauthorized, {})
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param message -
|
||||
|
@ -22,9 +22,20 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
}
|
||||
|
||||
const getClient = await getResource(client.function.GetClient)
|
||||
const instance = await getClient(token, endpoint, () => {
|
||||
location.reload()
|
||||
})
|
||||
const instance = await getClient(
|
||||
token,
|
||||
endpoint,
|
||||
() => {
|
||||
location.reload()
|
||||
},
|
||||
() => {
|
||||
clearMetadata()
|
||||
navigate({
|
||||
path: [login.component.LoginApp],
|
||||
query: {}
|
||||
})
|
||||
}
|
||||
)
|
||||
console.log('logging in as', email)
|
||||
|
||||
const me = await instance.findOne(contact.class.EmployeeAccount, { email })
|
||||
@ -33,10 +44,7 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
setCurrentAccount(me)
|
||||
} else {
|
||||
console.error('WARNING: no employee account found.')
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||
setMetadataLocalStorage(login.metadata.CurrentWorkspace, null)
|
||||
clearMetadata()
|
||||
navigate({
|
||||
path: [login.component.LoginApp],
|
||||
query: { navigateUrl: encodeURIComponent(JSON.stringify(getCurrentLocation())) }
|
||||
@ -73,3 +81,9 @@ export async function connect (title: string): Promise<Client | undefined> {
|
||||
|
||||
return instance
|
||||
}
|
||||
function clearMetadata (): void {
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||
setMetadataLocalStorage(login.metadata.CurrentWorkspace, null)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import type {
|
||||
TxResult
|
||||
} from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import { getMetadata, PlatformError, readResponse, ReqId, serialize } from '@anticrm/platform'
|
||||
import { getMetadata, PlatformError, readResponse, ReqId, serialize, UNAUTHORIZED } from '@anticrm/platform'
|
||||
|
||||
class DeferredPromise {
|
||||
readonly promise: Promise<any>
|
||||
@ -53,7 +53,8 @@ class Connection implements ClientConnection {
|
||||
constructor (
|
||||
private readonly url: string,
|
||||
private readonly handler: TxHander,
|
||||
private readonly onUpgrade?: () => void
|
||||
private readonly onUpgrade?: () => void,
|
||||
private readonly onUnauthorized?: () => void
|
||||
) {
|
||||
console.log('connection created')
|
||||
this.interval = setInterval(() => {
|
||||
@ -72,7 +73,11 @@ class Connection implements ClientConnection {
|
||||
try {
|
||||
return await this.openConnection()
|
||||
} catch (err: any) {
|
||||
console.log('failed to connect')
|
||||
console.log('failed to connect', err)
|
||||
if (err.code === UNAUTHORIZED.code) {
|
||||
this.onUnauthorized?.()
|
||||
throw err
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
@ -93,6 +98,10 @@ class Connection implements ClientConnection {
|
||||
websocket.onmessage = (event: MessageEvent) => {
|
||||
const resp = readResponse(event.data)
|
||||
if (resp.id === -1 && resp.result === 'hello') {
|
||||
if (resp.error !== undefined) {
|
||||
reject(resp.error)
|
||||
return
|
||||
}
|
||||
resolve(websocket)
|
||||
return
|
||||
}
|
||||
@ -191,6 +200,11 @@ class Connection implements ClientConnection {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function connect (url: string, handler: TxHander, onUpgrade?: () => void): Promise<ClientConnection> {
|
||||
return new Connection(url, handler, onUpgrade)
|
||||
export async function connect (
|
||||
url: string,
|
||||
handler: TxHander,
|
||||
onUpgrade?: () => void,
|
||||
onUnauthorized?: () => void
|
||||
): Promise<ClientConnection> {
|
||||
return new Connection(url, handler, onUpgrade, onUnauthorized)
|
||||
}
|
||||
|
@ -32,7 +32,12 @@ export default async () => {
|
||||
|
||||
return {
|
||||
function: {
|
||||
GetClient: async (token: string, endpoint: string, onUpgrade?: () => void): Promise<Client> => {
|
||||
GetClient: async (
|
||||
token: string,
|
||||
endpoint: string,
|
||||
onUpgrade?: () => void,
|
||||
onUnauthorized?: () => void
|
||||
): Promise<Client> => {
|
||||
if (token !== _token && client !== undefined) {
|
||||
await client.close()
|
||||
client = undefined
|
||||
@ -43,7 +48,7 @@ export default async () => {
|
||||
(handler: TxHander) => {
|
||||
const url = new URL(`/${token}`, endpoint)
|
||||
console.log('connecting to', url.href)
|
||||
return connect(url.href, handler, onUpgrade)
|
||||
return connect(url.href, handler, onUpgrade, onUnauthorized)
|
||||
},
|
||||
filterModel ? getPlugins() : undefined
|
||||
)
|
||||
|
@ -52,7 +52,12 @@ export interface ClientSocket {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ClientFactory = (token: string, endpoint: string, onUpgrade?: () => void) => Promise<Client>
|
||||
export type ClientFactory = (
|
||||
token: string,
|
||||
endpoint: string,
|
||||
onUpgrade?: () => void,
|
||||
onUnauthorized?: () => void
|
||||
) => Promise<Client>
|
||||
|
||||
export default plugin(clientId, {
|
||||
metadata: {
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { fetchMetadataLocalStorage, location, Popup } from '@anticrm/ui'
|
||||
import { fetchMetadataLocalStorage, location, Popup, ticker } from '@anticrm/ui'
|
||||
|
||||
import LoginForm from './LoginForm.svelte'
|
||||
import SignupForm from './SignupForm.svelte'
|
||||
@ -28,7 +28,10 @@
|
||||
|
||||
let navigateUrl: string | undefined
|
||||
|
||||
const token = fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
function getToken (timer: number): string | null {
|
||||
return fetchMetadataLocalStorage(login.metadata.LoginToken)
|
||||
}
|
||||
$: token = getToken($ticker)
|
||||
|
||||
onDestroy(
|
||||
location.subscribe(async (loc) => {
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { Button, getCurrentLocation, Label, navigate, setMetadataLocalStorage } from '@anticrm/ui'
|
||||
import { workbenchId } from '@anticrm/workbench'
|
||||
import login from '../plugin'
|
||||
import { getWorkspaces, selectWorkspace } from '../utils'
|
||||
import { getWorkspaces, selectWorkspace, Workspace } from '../utils'
|
||||
import StatusControl from './StatusControl.svelte'
|
||||
|
||||
export let navigateUrl: string | undefined = undefined
|
||||
@ -44,6 +44,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function _getWorkspaces (): Promise<Workspace[]> {
|
||||
try {
|
||||
return getWorkspaces()
|
||||
} catch (err: any) {
|
||||
setMetadataLocalStorage(login.metadata.LoginToken, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||
setMetadataLocalStorage(login.metadata.CurrentWorkspace, null)
|
||||
changeAccount()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function createWorkspace (): void {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = 'createWorkspace'
|
||||
@ -65,7 +78,7 @@
|
||||
<div class="status">
|
||||
<StatusControl {status} />
|
||||
</div>
|
||||
{#await getWorkspaces() then workspaces}
|
||||
{#await _getWorkspaces() then workspaces}
|
||||
<div class="form">
|
||||
{#each workspaces as workspace}
|
||||
<div
|
||||
|
@ -13,8 +13,17 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Request, Response } from '@anticrm/platform'
|
||||
import { getMetadata, OK, serialize, Status, unknownError, unknownStatus } from '@anticrm/platform'
|
||||
import {
|
||||
PlatformError,
|
||||
Request,
|
||||
Response,
|
||||
getMetadata,
|
||||
OK,
|
||||
serialize,
|
||||
Status,
|
||||
unknownError,
|
||||
unknownStatus
|
||||
} from '@anticrm/platform'
|
||||
import login from '@anticrm/login'
|
||||
import { fetchMetadataLocalStorage, getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
|
||||
@ -204,6 +213,9 @@ export async function getWorkspaces (): Promise<Workspace[]> {
|
||||
})
|
||||
const result: Response<any> = await response.json()
|
||||
console.log(result)
|
||||
if (result.error != null) {
|
||||
throw new PlatformError(result.error)
|
||||
}
|
||||
return result.result
|
||||
} catch (err) {
|
||||
return []
|
||||
|
@ -14,30 +14,30 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { readResponse, serialize } from '@anticrm/platform'
|
||||
import { start, disableLogging } from '../server'
|
||||
import { readResponse, serialize, UNAUTHORIZED } from '@anticrm/platform'
|
||||
import { generateToken } from '@anticrm/server-token'
|
||||
import WebSocket from 'ws'
|
||||
import { disableLogging, start } from '../server'
|
||||
|
||||
import {
|
||||
Doc,
|
||||
Ref,
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
Domain,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
Tx,
|
||||
TxResult,
|
||||
ModelDb,
|
||||
MeasureMetricsContext,
|
||||
toFindResult,
|
||||
Hierarchy,
|
||||
MeasureMetricsContext,
|
||||
ModelDb,
|
||||
Ref,
|
||||
ServerStorage,
|
||||
Domain
|
||||
toFindResult,
|
||||
Tx,
|
||||
TxResult
|
||||
} from '@anticrm/core'
|
||||
import { SessionContext } from '@anticrm/server-core'
|
||||
import { genMinModel } from './minmodel'
|
||||
import { ClientSession } from '../client'
|
||||
import { genMinModel } from './minmodel'
|
||||
|
||||
describe('server', () => {
|
||||
disableLogging()
|
||||
@ -101,6 +101,12 @@ describe('server', () => {
|
||||
conn.on('error', () => {
|
||||
conn.close()
|
||||
})
|
||||
conn.on('message', (msg: string) => {
|
||||
const resp = readResponse(msg)
|
||||
expect(resp.result === 'hello')
|
||||
expect(resp.error?.code).toBe(UNAUTHORIZED.code)
|
||||
conn.close()
|
||||
})
|
||||
conn.on('close', () => {
|
||||
done()
|
||||
})
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import core, { MeasureContext, Ref, Space, TxFactory } from '@anticrm/core'
|
||||
import { readRequest, Response, serialize, unknownError } from '@anticrm/platform'
|
||||
import { readRequest, Response, serialize, UNAUTHORIZED, unknownError } from '@anticrm/platform'
|
||||
import type { Pipeline } from '@anticrm/server-core'
|
||||
import { decodeToken, Token } from '@anticrm/server-token'
|
||||
import { createServer, IncomingMessage } from 'http'
|
||||
@ -263,9 +263,20 @@ export function start (
|
||||
console.log('client connected with payload', payload)
|
||||
wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request, payload))
|
||||
} catch (err) {
|
||||
console.log('unauthorized client')
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
|
||||
socket.destroy()
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
const resp: Response<any> = {
|
||||
id: -1,
|
||||
error: UNAUTHORIZED,
|
||||
result: 'hello'
|
||||
}
|
||||
ws.send(serialize(resp))
|
||||
ws.onmessage = (msg) => {
|
||||
const resp: Response<any> = {
|
||||
error: UNAUTHORIZED
|
||||
}
|
||||
ws.send(serialize(resp))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
8
tests/install-elastic-plugin-setup.sh
Executable file
8
tests/install-elastic-plugin-setup.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Usage
|
||||
# ./install-elastic-plugin-setup.sh sanity_elastic_1
|
||||
|
||||
curdir=$(dirname $0)
|
||||
${curdir}/install-elastic-plugin.sh $@
|
||||
${curdir}/setup-elastic.sh 9200
|
@ -3,7 +3,7 @@
|
||||
docker-compose -p sanity kill
|
||||
docker-compose -p sanity down --volumes
|
||||
docker-compose -p sanity up -d --force-recreate --renew-anon-volumes
|
||||
./setup-elastic.sh
|
||||
./setup-elastic.sh 9201
|
||||
|
||||
# Creae workspace record in accounts
|
||||
./tool.sh create-workspace sanity-ws -o SanityTest
|
||||
|
@ -1,8 +1,9 @@
|
||||
res=''
|
||||
echo "Warning Elastic to up and running with attachment processor..."
|
||||
port=$1
|
||||
echo "Warning Elastic to up and running with attachment processor... ${port}"
|
||||
while true
|
||||
do
|
||||
res=$(curl -s -XPUT "localhost:9201/_ingest/pipeline/attachment?pretty" -H 'Content-Type: application/json' -d'
|
||||
res=$(curl -s -XPUT "localhost:${port}/_ingest/pipeline/attachment?pretty" -H 'Content-Type: application/json' -d'
|
||||
{
|
||||
"description" : "Field for processing file attachments",
|
||||
"processors" : [
|
||||
|
@ -6,5 +6,6 @@ export MINIO_ENDPOINT=localhost:9002
|
||||
export MONGO_URL=mongodb://localhost:27018
|
||||
export TRANSACTOR_URL=ws:/localhost:3334
|
||||
export ELASTIC_URL=http://localhost:9201
|
||||
export SERVER_SECRET=secret
|
||||
|
||||
node ../dev/tool/bundle.js $@
|
Loading…
Reference in New Issue
Block a user