Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SHARD-892: Add password upper limit #43

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions src/gui-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ import path = require('path');
import {timingSafeEqual} from 'crypto';
import {Pm2ProcessStatus, statusFromPM2} from './pm2';
import merge from 'deepmerge';
import {defaultGuiConfig, guiConfigType, guiConfigSchema} from './config/default-gui-config';
import {
defaultGuiConfig,
guiConfigType,
guiConfigSchema,
} from './config/default-gui-config';
import fs from 'fs';
import * as yaml from 'js-yaml';
import * as cryptoShardus from '@shardus/crypto-utils';
import {getInstalledGuiVersion} from './utils/project-data';
import {File} from './utils'
import {File} from './utils';
import crypto from 'crypto';
import Ajv from "ajv"
import Ajv from 'ajv';
import argon2id from 'argon2';

let config = defaultGuiConfig;

const validateGuiConfig = new Ajv().compile(guiConfigSchema)
const validateGuiConfig = new Ajv().compile(guiConfigSchema);

cryptoShardus.init('64f152869ca2d473e4ba64ab53f49ccdb2edae22da192c126850970e788af347');
cryptoShardus.init(
'64f152869ca2d473e4ba64ab53f49ccdb2edae22da192c126850970e788af347'
);

function isNumber(n: string) {
const parsedN = parseInt(n);
Expand All @@ -28,23 +34,29 @@ function isNumber(n: string) {
function validPassword(password: string) {
return (
password.length >= 8 &&
password.length <= 128 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password) &&
/[!@#$%^&*()_+*$]/.test(password)
);
}

const guiConfigPath = path.join(__dirname, `../${File.GUI_CONFIG}`)
if (fs.existsSync(guiConfigPath)) { // eslint-disable-line security/detect-non-literal-fs-filename
const guiConfigPath = path.join(__dirname, `../${File.GUI_CONFIG}`);
if (fs.existsSync(guiConfigPath)) {
// eslint-disable-line security/detect-non-literal-fs-filename
// eslint-disable-next-line security/detect-non-literal-fs-filename
const fileConfig = JSON.parse(fs.readFileSync(guiConfigPath).toString())
const fileConfig = JSON.parse(fs.readFileSync(guiConfigPath).toString());
if (validateGuiConfig(fileConfig)) {
config = merge(config, fileConfig as guiConfigType, {arrayMerge: (target, source) => source})
config = merge(config, fileConfig as guiConfigType, {
arrayMerge: (target, source) => source,
});
// `as guiConfigType` above is valid because validateGuiConfig() passed
} else {
console.warn(`warning: config has been ignored due to invalid JSON schema:`)
console.warn(`${guiConfigPath}`)
console.warn(
`warning: config has been ignored due to invalid JSON schema:`
);
console.warn(`${guiConfigPath}`);
}
}

Expand Down Expand Up @@ -111,12 +123,12 @@ export function registerGuiCommands(program: Command) {
.description('Set the GUI server port')
.action(port => {
if (!isNumber(port)) {
console.error("Port is not a number");
console.error('Port is not a number');
return;
}
port = parseInt(port);
if(port < 1024) {
console.error("Port is reserved");
if (port < 1024) {
console.error('Port is reserved');
return;
}
config.gui.port = parseInt(port);
Expand All @@ -135,7 +147,9 @@ export function registerGuiCommands(program: Command) {
setCommand
.command('password')
.arguments('<password>')
.description('Set the GUI server password, requirements: min 8 characters, at least 1 lower case letter, at least 1 upper case letter, at least 1 number, at least 1 special character !@#$%^&*()_+*$')
.description(
'Set the GUI server password, requirements: min 8 characters, max 128 characters, at least 1 lower case letter, at least 1 upper case letter, at least 1 number, at least 1 special character !@#$%^&*()_+*$'
)
.option('-h', 'Changes how the password is hashed. For internal use only')
.action(async (password, options) => {
if (!options.h) {
Expand Down Expand Up @@ -163,14 +177,19 @@ export function registerGuiCommands(program: Command) {
.command('login')
.arguments('<password>')
.description('verify GUI password')
.action(password => {
if (
!timingSafeEqual(Buffer.from(password), Buffer.from(config.gui.pass))
) {
console.log(yaml.dump({login: 'unauthorized'}));
return;
.action(async password => {
try {
// Verify the password with saved hash
const isValid = await argon2id.verify(config.gui.pass, password);
if (!isValid) {
console.log(yaml.dump({login: 'unauthorized'}));
return;
}
console.log(yaml.dump({login: 'authorized'}));
} catch (err) {
console.error('Error during password verification:', err);
console.log(yaml.dump({login: 'unauthorized'})); // Fail-safe unauthorized output
}
console.log(yaml.dump({login: 'authorized'}));
});

function startGui() {
Expand Down
Loading