From 6011a75e05035f37050b1fffde1c2b36a01dc9a1 Mon Sep 17 00:00:00 2001 From: Andrey Platov Date: Thu, 9 Sep 2021 20:42:34 +0200 Subject: [PATCH] initial `account` contribution Signed-off-by: Andrey Platov --- common/config/rush/pnpm-lock.yaml | 30 ++++++- rush.json | 5 ++ server/account/.eslintrc.js | 6 ++ server/account/.npmignore | 4 + server/account/config/rig.json | 18 ++++ server/account/package.json | 25 ++++++ server/account/src/index.ts | 140 ++++++++++++++++++++++++++++++ server/account/tsconfig.json | 8 ++ 8 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 server/account/.eslintrc.js create mode 100644 server/account/.npmignore create mode 100644 server/account/config/rig.json create mode 100644 server/account/package.json create mode 100644 server/account/src/index.ts create mode 100644 server/account/tsconfig.json diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 96aff10246..fab81f6b09 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -3,6 +3,7 @@ lockfileVersion: 5.3 specifiers: '@elastic/elasticsearch': ^7.14.0 '@microsoft/api-extractor': ^7.18.4 + '@rush-temp/account': file:./projects/account.tgz '@rush-temp/chunter': file:./projects/chunter.tgz '@rush-temp/chunter-assets': file:./projects/chunter-assets.tgz '@rush-temp/chunter-resources': file:./projects/chunter-resources.tgz @@ -72,6 +73,7 @@ specifiers: '@tiptap/extension-typography': ~2.0.0-beta.13 '@tiptap/starter-kit': ~2.0.0-beta.89 '@types/cors': ^2.8.12 + '@types/deep-equal': ^1.0.1 '@types/express': ^4.17.13 '@types/express-fileupload': ^1.1.7 '@types/heft-jest': ^1.0.2 @@ -99,7 +101,6 @@ specifiers: intl-messageformat: ^9.7.1 mini-css-extract-plugin: ^2.2.0 minio: ^7.0.19 - mongodb: ^4.1.0 node-html-parser: ^4.1.3 postcss: ^8.3.4 postcss-load-config: ^3.1.0 @@ -122,6 +123,7 @@ specifiers: dependencies: '@elastic/elasticsearch': 7.14.0 '@microsoft/api-extractor': 7.18.4 + '@rush-temp/account': file:projects/account.tgz_6c259fadfeb3a4b20890aefe87070b8b '@rush-temp/chunter': file:projects/chunter.tgz_6c259fadfeb3a4b20890aefe87070b8b '@rush-temp/chunter-assets': file:projects/chunter-assets.tgz '@rush-temp/chunter-resources': file:projects/chunter-resources.tgz_c38cf1a7a413db8918b0b4754c21e4c5 @@ -191,6 +193,7 @@ dependencies: '@tiptap/extension-typography': 2.0.0-beta.13_@tiptap+core@2.0.0-beta.93 '@tiptap/starter-kit': 2.0.0-beta.89 '@types/cors': 2.8.12 + '@types/deep-equal': 1.0.1 '@types/express': 4.17.13 '@types/express-fileupload': 1.1.7 '@types/heft-jest': 1.0.2 @@ -218,7 +221,6 @@ dependencies: intl-messageformat: 9.8.1 mini-css-extract-plugin: 2.2.0_webpack@5.48.0 minio: 7.0.19 - mongodb: 4.1.1 node-html-parser: 4.1.3 postcss: 8.3.6 postcss-load-config: 3.1.0 @@ -1670,7 +1672,7 @@ packages: /@types/whatwg-url/8.2.1: resolution: {integrity: sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==} dependencies: - '@types/node': 16.7.1 + '@types/node': 16.7.5 '@types/webidl-conversions': 6.1.1 dev: false @@ -9090,6 +9092,26 @@ packages: commander: 2.20.3 dev: false + file:projects/account.tgz_6c259fadfeb3a4b20890aefe87070b8b: + resolution: {integrity: sha512-jykJZHqGILUDDIrPnSs2RQYlkU5YgPheSAJaxztshirE23ZgdqZRUb9VgqfHIhIRm6etoo0KliQCAurKoS6/kw==, tarball: file:projects/account.tgz} + id: file:projects/account.tgz + name: '@rush-temp/account' + version: 0.0.0 + dependencies: + '@types/heft-jest': 1.0.2 + '@typescript-eslint/eslint-plugin': 4.28.5_a8e83fcad666e1ba86be4b2e27a20aea + eslint: 7.32.0 + eslint-plugin-import: 2.23.4_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 4.3.1 + jwt-simple: 0.5.6 + mongodb: 4.1.1 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - supports-color + - typescript + dev: false + file:projects/chunter-assets.tgz: resolution: {integrity: sha512-kMTEO1cP8cmeoA2tImDBtT2SXfjo4AW0MWEbzFvzhZkLZaMmJGtl5ZHclKn/Wwg/LBA1zDc21fKlpeahmuNS4A==, tarball: file:projects/chunter-assets.tgz} name: '@rush-temp/chunter-assets' @@ -9834,7 +9856,7 @@ packages: dev: false file:projects/recruit-resources.tgz_c38cf1a7a413db8918b0b4754c21e4c5: - resolution: {integrity: sha512-ZTxU5DPTttq85I1jEwAbJ+RCuL1t9sFCQ0gJo/ax4VzfEN6M/6inkXZ5HQF812UD9vGcYlRF2+zqfnuoAfZxmA==, tarball: file:projects/recruit-resources.tgz} + resolution: {integrity: sha512-vsSwhXuC8vyxhYf1emb/0aTu4FCsXtuz2MPwgUskCveuUBtF2ROAqzo9Qy9HXZNHX1eOeDWVDcjbFmRuU4Peqg==, tarball: file:projects/recruit-resources.tgz} id: file:projects/recruit-resources.tgz name: '@rush-temp/recruit-resources' version: 0.0.0 diff --git a/rush.json b/rush.json index a0fafd232b..c06204a7e3 100644 --- a/rush.json +++ b/rush.json @@ -746,5 +746,10 @@ "projectFolder": "server/front", "shouldPublish": true }, + { + "packageName": "@anticrm/account", + "projectFolder": "server/account", + "shouldPublish": true + }, ] } diff --git a/server/account/.eslintrc.js b/server/account/.eslintrc.js new file mode 100644 index 0000000000..89f8151bd4 --- /dev/null +++ b/server/account/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + project: './tsconfig.json' + } +} \ No newline at end of file diff --git a/server/account/.npmignore b/server/account/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/server/account/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/server/account/config/rig.json b/server/account/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/server/account/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/server/account/package.json b/server/account/package.json new file mode 100644 index 0000000000..05e7cbd891 --- /dev/null +++ b/server/account/package.json @@ -0,0 +1,25 @@ +{ + "name": "@anticrm/account", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "lint:fix": "eslint --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig":"~0.6.0", + "@types/heft-jest":"^1.0.2", + "@typescript-eslint/eslint-plugin":"4", + "eslint-plugin-import":"2", + "eslint-plugin-promise":"4", + "eslint-plugin-node":"11", + "eslint":"^7.32.0" + }, + "dependencies": { + "mongodb":"^4.1.1", + "@anticrm/platform":"~0.6.5", + "jwt-simple":"~0.5.6" + } +} diff --git a/server/account/src/index.ts b/server/account/src/index.ts new file mode 100644 index 0000000000..9f35632483 --- /dev/null +++ b/server/account/src/index.ts @@ -0,0 +1,140 @@ +// +// 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 { Plugin, StatusCode } from '@anticrm/platform' +import { PlatformError, Severity, Status, plugin } from '@anticrm/platform' +import { Binary, Db, ObjectId } from 'mongodb' +import { pbkdf2Sync } from 'crypto' +import { encode } from 'jwt-simple' + +const WORKSPACE_COLLECTION = 'workspace' +const ACCOUNT_COLLECTION = 'account' + +const endpoint = 'wss://transactor.hc.engineering/' +const secret = 'secret' + +/** + * @public + */ +export const accountId = 'account' as Plugin + +/** + * @public + */ +const accountPlugin = plugin(accountId, { + status: { + AccountNotFound: '' as StatusCode<{account: string}>, + InvalidPassword: '' as StatusCode<{account: string}>, + Forbidden: '' as StatusCode + } +}) + +interface Account { + _id: ObjectId + email: string + hash: Binary + salt: Binary + workspaces: ObjectId[] +} + +interface Workspace { + _id: ObjectId + workspace: string + organisation: string + accounts: ObjectId[] +} + +/** + * @public + */ +export interface LoginInfo { + email: string + token: string + endpoint: string +} + +type AccountInfo = Omit + +function hashWithSalt (password: string, salt: Buffer): Buffer { + return pbkdf2Sync(password, salt, 1000, 32, 'sha256') +} + +function verifyPassword (password: string, hash: Buffer, salt: Buffer): boolean { + return Buffer.compare(hash, hashWithSalt(password, salt)) === 0 +} + +async function getAccount (db: Db, email: string): Promise { + return await db.collection(ACCOUNT_COLLECTION).findOne({ email }) +} + +async function getWorkspace (db: Db, workspace: string): Promise { + return await db.collection(WORKSPACE_COLLECTION).findOne({ + workspace + }) +} + +function toAccountInfo (account: Account): AccountInfo { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { hash, salt, ...result } = account + return result +} + +async function getAccountInfo (db: Db, email: string, password: string): Promise { + const account = await getAccount(db, email) + if (account === null) { + throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email })) + } + if (!verifyPassword(password, account.hash.buffer, account.salt.buffer)) { + throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.InvalidPassword, { account: email })) + } + return toAccountInfo(account) +} + +function generateToken (email: string, workspace: string): string { + return encode({ email, workspace }, secret) +} + +/** + * @public + * @param db - + * @param email - + * @param password - + * @param workspace - + * @returns + */ +export async function login (db: Db, email: string, password: string, workspace: string): Promise { + const accountInfo = await getAccountInfo(db, email, password) + const workspaceInfo = await getWorkspace(db, workspace) + + if (workspaceInfo !== null) { + const workspaces = accountInfo.workspaces + + for (const w of workspaces) { + if (w.equals(workspaceInfo._id)) { + const result = { + endpoint, + email, + token: generateToken(email, workspace) + } + return result + } + } + } + + throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.Forbidden, {})) +} + +export default accountPlugin diff --git a/server/account/tsconfig.json b/server/account/tsconfig.json new file mode 100644 index 0000000000..aeb0517b13 --- /dev/null +++ b/server/account/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + } +} \ No newline at end of file