From 50f4e1a5a0c6a2c379592221746a6433735ed394 Mon Sep 17 00:00:00 2001 From: Antoine Jaussoin Date: Sat, 4 Feb 2023 14:07:48 +0000 Subject: [PATCH] Improve self-hosted licence generation (#467) --- backend/package.json | 1 + backend/scripts/hardcode-licence.sh | 2 ++ backend/src/admin/router.ts | 3 ++- backend/src/auth/logins/password-user.ts | 2 +- backend/src/auth/register/register-user.ts | 2 +- backend/src/db/actions/licences.ts | 4 +-- backend/src/db/actions/users.ts | 2 +- backend/src/encryption.ts | 31 ++++++++++++++++++++++ backend/src/hardcoded.ts | 30 ++++++++++++++++++--- backend/src/index.ts | 2 +- backend/src/security/is-licenced.ts | 8 +++++- backend/src/utils.ts | 31 ---------------------- docs/yarn.lock | 6 ++--- 13 files changed, 78 insertions(+), 46 deletions(-) create mode 100755 backend/scripts/hardcode-licence.sh create mode 100644 backend/src/encryption.ts diff --git a/backend/package.json b/backend/package.json index d68ae47eb..9f1167cf2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,6 +9,7 @@ "start": "nodemon --exec 'yarn fix & ts-node' --esm --files ./src/index.ts", "create-migration": "scripty", "create-empty-migration": "scripty", + "hardcode-licence": "scripty", "migrate": "typeorm-ts-node-esm -d src/db/index.ts migration:run", "revert": "typeorm-ts-node-esm -d src/db/index.ts migration:revert", "lint": "eslint 'src/**/*.ts'", diff --git a/backend/scripts/hardcode-licence.sh b/backend/scripts/hardcode-licence.sh new file mode 100755 index 000000000..caf810fdb --- /dev/null +++ b/backend/scripts/hardcode-licence.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +./node_modules/.bin/ts-node --esm src/hardcoded.ts $1 $2 \ No newline at end of file diff --git a/backend/src/admin/router.ts b/backend/src/admin/router.ts index f237157bf..6b2abdbb7 100644 --- a/backend/src/admin/router.ts +++ b/backend/src/admin/router.ts @@ -13,7 +13,8 @@ import { BackendCapabilities, MergeUsersPayload, } from '../common/index.js'; -import { getIdentityFromRequest, hashPassword } from '../utils.js'; +import { getIdentityFromRequest } from '../utils.js'; +import { hashPassword } from '../encryption.js'; import { canSendEmails } from '../email/utils.js'; import { mergeUsers } from '../db/actions/merge.js'; diff --git a/backend/src/auth/logins/password-user.ts b/backend/src/auth/logins/password-user.ts index 4ce289651..fff3acb18 100644 --- a/backend/src/auth/logins/password-user.ts +++ b/backend/src/auth/logins/password-user.ts @@ -1,6 +1,6 @@ import { UserIdentityEntity } from '../../db/entities/index.js'; import { getIdentityByUsername } from '../../db/actions/users.js'; -import { comparePassword } from '../../utils.js'; +import { comparePassword } from '../../encryption.js'; export default async function loginUser( username: string, diff --git a/backend/src/auth/register/register-user.ts b/backend/src/auth/register/register-user.ts index a91990e49..cab54928a 100644 --- a/backend/src/auth/register/register-user.ts +++ b/backend/src/auth/register/register-user.ts @@ -1,6 +1,6 @@ import { RegisterPayload } from '../../common/index.js'; import { v4 } from 'uuid'; -import { hashPassword } from '../../utils.js'; +import { hashPassword } from '../../encryption.js'; import { UserIdentityEntity } from '../../db/entities/index.js'; import { getIdentityByUsername, registerUser } from '../../db/actions/users.js'; import { canSendEmails } from '../../email/utils.js'; diff --git a/backend/src/db/actions/licences.ts b/backend/src/db/actions/licences.ts index 2fd42b20e..9cbdb459a 100644 --- a/backend/src/db/actions/licences.ts +++ b/backend/src/db/actions/licences.ts @@ -41,7 +41,7 @@ export async function validateLicence(key: string): Promise { }); return found > 0; } catch (err) { - console.log('Error while retriving the licence: ', err); + console.log('Error while retrieving the licence: ', err); return false; } }); @@ -63,7 +63,7 @@ export async function fetchLicence( }; } } catch (err) { - console.log('Error while retriving the licence: ', err); + console.log('Error while retrieving the licence: ', err); return null; } return null; diff --git a/backend/src/db/actions/users.ts b/backend/src/db/actions/users.ts index 3aa59d5c1..6366ac20d 100644 --- a/backend/src/db/actions/users.ts +++ b/backend/src/db/actions/users.ts @@ -13,7 +13,7 @@ import { transaction } from './transaction.js'; import { AccountType, FullUser } from '../../common/index.js'; import { isSelfHostedAndLicenced } from '../../security/is-licenced.js'; import { v4 } from 'uuid'; -import { comparePassword, hashPassword } from '../../utils.js'; +import { hashPassword, comparePassword } from '../../encryption.js'; import { saveAndReload } from '../repositories/BaseRepository.js'; export async function getUser(userId: string): Promise { diff --git a/backend/src/encryption.ts b/backend/src/encryption.ts new file mode 100644 index 000000000..b4798ccba --- /dev/null +++ b/backend/src/encryption.ts @@ -0,0 +1,31 @@ +import bcryptjs from 'bcryptjs'; +import crypto from 'crypto-js'; + +const aes = crypto.AES; +const stringify = crypto.enc.Utf8.stringify; +const { compare, genSalt, hash } = bcryptjs; + +export async function hashPassword(clearTextPassword: string): Promise { + const salt = await genSalt(); + const hashedPassword = await hash(clearTextPassword, salt); + return hashedPassword; +} + +export async function comparePassword( + clearTextPassword: string, + hashedPassword: string +): Promise { + const match = await compare(clearTextPassword, hashedPassword); + return match; +} + +export function encrypt(clear: string, key: string): string { + const encrypted = aes.encrypt(clear, key).toString(); + return encrypted; +} + +export function decrypt(encrypted: string, key: string): string { + const bytes = aes.decrypt(encrypted, key); + const clear = stringify(bytes); + return clear; +} diff --git a/backend/src/hardcoded.ts b/backend/src/hardcoded.ts index a1cdcc427..47872fafd 100644 --- a/backend/src/hardcoded.ts +++ b/backend/src/hardcoded.ts @@ -1,11 +1,33 @@ -import { encrypt, hashPassword } from './utils.js'; +import { encrypt, hashPassword } from './encryption.js'; +import { v4 as uuid } from 'uuid'; +import chalk from 'chalk-template'; + +if (!process.argv[2]) { + console.log( + chalk`No company name provided. {red Please provide the company name as the first argument}.` + ); + process.exit(1); +} + +const company = process.argv[2].trim(); +const key = process.argv[3] ? process.argv[3].trim() : uuid(); + +buildHardcodedLicence(key, company); export async function buildHardcodedLicence( licenceKey: string, company: string ): Promise { - console.log('Building hardcoded licence for: ', licenceKey); + console.log( + chalk`Building hardcoded licence for company: {yellow ${company}}` + ); + console.log(chalk`Licence key to communicate to them: {yellow ${key}}`); const hash = await hashPassword(licenceKey); - console.log('Hash: ', hash); - console.log('Encrypted company name: ', encrypt(company, licenceKey)); + const encryptedOwner = encrypt(company, licenceKey); + const obj = { + hash, + encryptedOwner, + }; + console.log('Copy the following object to the hardcodedLicences array:'); + console.log(chalk`{red ${JSON.stringify(obj, null, 2)}}`); } diff --git a/backend/src/index.ts b/backend/src/index.ts index e3895323e..d76bb0948 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -16,11 +16,11 @@ import stripeRouter from './stripe/router.js'; import slackRouter from './slack/router.js'; import game from './game.js'; import { - hashPassword, getIdentityFromRequest, getUserViewFromRequest, getUserQuota, } from './utils.js'; +import { hashPassword } from './encryption.js'; import { initSentry, setupSentryErrorHandler, diff --git a/backend/src/security/is-licenced.ts b/backend/src/security/is-licenced.ts index 1450f19d1..0539d8ead 100644 --- a/backend/src/security/is-licenced.ts +++ b/backend/src/security/is-licenced.ts @@ -1,8 +1,9 @@ import { SelfHostedCheckPayload } from '../common/index.js'; import config from '../config.js'; import fetch from 'node-fetch'; -import wait, { comparePassword, decrypt } from '../utils.js'; +import wait from '../utils.js'; import { LicenceMetadata } from './../types.js'; +import { comparePassword, decrypt } from '../encryption.js'; let licenced: LicenceMetadata | null = null; @@ -33,6 +34,11 @@ const hardcodedLicences: HardcodedLicence[] = [ encryptedOwner: 'U2FsdGVkX1+xZTCbhmVh4jBPCZfiJ5kipc0Yeo8bm/8CjEoLG8VK/Z1mwTEKxKlR', }, + { + // BAM + hash: '$2a$10$udpIRa0hWeurSsaXtM0iveT4geuQBuGvnNS9UqczkgxOYzHaPqau.', + encryptedOwner: 'U2FsdGVkX1/DQ2JJ57C+LGM7XBLLg9NDxviOwRwj0pI=', + }, ]; export function isSelfHostedAndLicenced() { diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 9df9a27b2..f6929840f 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -1,15 +1,9 @@ import { Request } from 'express'; -import bcryptjs from 'bcryptjs'; -import crypto from 'crypto-js'; import { UserView, UserIdentityEntity } from './db/entities/index.js'; import { getUserView, getUser, getIdentity } from './db/actions/users.js'; import { Quota } from './common/index.js'; import { getNumberOfPosts } from './db/actions/posts.js'; -const aes = crypto.AES; -const stringify = crypto.enc.Utf8.stringify; -const { compare, genSalt, hash } = bcryptjs; - export async function getUserViewFromRequest( request: Request ): Promise { @@ -52,31 +46,6 @@ export async function getIdentityFromRequest( return null; } -export async function hashPassword(clearTextPassword: string): Promise { - const salt = await genSalt(); - const hashedPassword = await hash(clearTextPassword, salt); - return hashedPassword; -} - -export async function comparePassword( - clearTextPassword: string, - hashedPassword: string -): Promise { - const match = await compare(clearTextPassword, hashedPassword); - return match; -} - -export function encrypt(clear: string, key: string): string { - const encrypted = aes.encrypt(clear, key).toString(); - return encrypted; -} - -export function decrypt(encrypted: string, key: string): string { - const bytes = aes.decrypt(encrypted, key); - const clear = stringify(bytes); - return clear; -} - export default async function wait(delay = 1000) { return new Promise((resolve) => { setTimeout(resolve, delay); diff --git a/docs/yarn.lock b/docs/yarn.lock index 8f687a9aa..3726fd872 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -4833,9 +4833,9 @@ htmlparser2@^8.0.1: entities "^4.3.0" http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== http-deceiver@^1.2.7: version "1.2.7"