From 1d523cdab573f4ee9384c9a0f53c904301c0e8c2 Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 06:39:24 +0100 Subject: [PATCH 1/7] fix: Add username validation to admin user seeding process --- server/db/create-admin-user.js | 26 ++++++++++++++++++++++++++ server/db/seeds/default.js | 14 +++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index a41fa5e2..23c49b9f 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -62,6 +62,32 @@ const input = async (fieldName, options = {}) => { process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); + if (process.env.DEFAULT_ADMIN_USERNAME) { + const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/; + + // eslint-disable-next-line no-constant-condition + while (true) { + const isValid = + process.env.DEFAULT_ADMIN_USERNAME.length >= 3 && + process.env.DEFAULT_ADMIN_USERNAME.length <= 32 && + USERNAME_REGEX.test(process.env.DEFAULT_ADMIN_USERNAME); + + if (isValid) { + break; + } + + console.log( + 'Username must be 3-32 characters and contain only letters, digits, underscores, and dots (e.g., john_doe).', + ); + + process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); + + if (!process.env.DEFAULT_ADMIN_USERNAME) { + break; + } + } + } + await knex.seed.run({ specific: 'default.js', }); diff --git a/server/db/seeds/default.js b/server/db/seeds/default.js index 3da664e4..bad40473 100644 --- a/server/db/seeds/default.js +++ b/server/db/seeds/default.js @@ -3,8 +3,12 @@ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md */ +/* eslint-disable no-console */ + const bcrypt = require('bcrypt'); +const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/; + const buildUserData = () => { const data = { role: 'admin', @@ -19,7 +23,15 @@ const buildUserData = () => { data.name = process.env.DEFAULT_ADMIN_NAME; } if (process.env.DEFAULT_ADMIN_USERNAME) { - data.username = process.env.DEFAULT_ADMIN_USERNAME.toLowerCase(); + const username = process.env.DEFAULT_ADMIN_USERNAME.toLowerCase(); + + if (username.length < 3 || username.length > 32 || !USERNAME_REGEX.test(username)) { + console.warn( + `Warning: DEFAULT_ADMIN_USERNAME "${process.env.DEFAULT_ADMIN_USERNAME}" is invalid; skipping.`, + ); + } else { + data.username = username; + } } return data; From 062cc3c347fc9244740f4f3e93f1407213ad7e4e Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 06:41:54 +0100 Subject: [PATCH 2/7] fix: Add name length validation to admin user seeding process --- server/db/create-admin-user.js | 8 ++++++++ server/db/seeds/default.js | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index 23c49b9f..48b82345 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -60,6 +60,14 @@ const input = async (fieldName, options = {}) => { isRequired: true, }); + // eslint-disable-next-line no-constant-condition + while (process.env.DEFAULT_ADMIN_NAME.length > 128) { + console.log('Name must be no more than 128 characters.'); + process.env.DEFAULT_ADMIN_NAME = await input('Name', { + isRequired: true, + }); + } + process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); if (process.env.DEFAULT_ADMIN_USERNAME) { diff --git a/server/db/seeds/default.js b/server/db/seeds/default.js index bad40473..2096da7b 100644 --- a/server/db/seeds/default.js +++ b/server/db/seeds/default.js @@ -20,7 +20,12 @@ const buildUserData = () => { data.password = bcrypt.hashSync(process.env.DEFAULT_ADMIN_PASSWORD, 10); } if (process.env.DEFAULT_ADMIN_NAME) { + if (process.env.DEFAULT_ADMIN_NAME.length > 128) { + console.warn('Warning: DEFAULT_ADMIN_NAME exceeds 128 characters; truncating.'); + data.name = process.env.DEFAULT_ADMIN_NAME.slice(0, 128); + } else { data.name = process.env.DEFAULT_ADMIN_NAME; + } } if (process.env.DEFAULT_ADMIN_USERNAME) { const username = process.env.DEFAULT_ADMIN_USERNAME.toLowerCase(); From 0a2641db8458956797b9cb1279fc0b005592a2e8 Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 06:42:37 +0100 Subject: [PATCH 3/7] fix: Add email validation to admin user seeding process --- server/db/create-admin-user.js | 16 ++++++++++++++++ server/db/seeds/default.js | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index 48b82345..77f45194 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -7,6 +7,7 @@ /* eslint-disable no-console */ const { read } = require('read'); +const validator = require('validator'); const initKnex = require('knex'); const knexfile = require('./knexfile'); @@ -51,6 +52,21 @@ const input = async (fieldName, options = {}) => { isRequired: true, }); + // eslint-disable-next-line no-constant-condition + while (true) { + if ( + validator.isEmail(process.env.DEFAULT_ADMIN_EMAIL) && + process.env.DEFAULT_ADMIN_EMAIL.length <= 256 + ) { + break; + } + + console.log('Email must be a valid e-mail address with no more than 256 characters.'); + process.env.DEFAULT_ADMIN_EMAIL = await input('Email', { + isRequired: true, + }); + } + process.env.DEFAULT_ADMIN_PASSWORD = await input('Password', { isRequired: true, isPassword: true, diff --git a/server/db/seeds/default.js b/server/db/seeds/default.js index 2096da7b..ac0296b0 100644 --- a/server/db/seeds/default.js +++ b/server/db/seeds/default.js @@ -6,6 +6,7 @@ /* eslint-disable no-console */ const bcrypt = require('bcrypt'); +const validator = require('validator'); const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/; @@ -24,7 +25,7 @@ const buildUserData = () => { console.warn('Warning: DEFAULT_ADMIN_NAME exceeds 128 characters; truncating.'); data.name = process.env.DEFAULT_ADMIN_NAME.slice(0, 128); } else { - data.name = process.env.DEFAULT_ADMIN_NAME; + data.name = process.env.DEFAULT_ADMIN_NAME; } } if (process.env.DEFAULT_ADMIN_USERNAME) { @@ -63,6 +64,18 @@ exports.seed = async (knex) => { process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(); if (defaultAdminEmail) { + if (!validator.isEmail(defaultAdminEmail)) { + throw new Error( + `DEFAULT_ADMIN_EMAIL "${process.env.DEFAULT_ADMIN_EMAIL}" is not a valid e-mail address.`, + ); + } + + if (defaultAdminEmail.length > 256) { + throw new Error( + `DEFAULT_ADMIN_EMAIL "${process.env.DEFAULT_ADMIN_EMAIL}" exceeds 256 characters.`, + ); + } + const userData = buildUserData(); let userId; From 7fbc2ea737fd595bf87e11ebe87dc21e9536cc8d Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 07:03:05 +0100 Subject: [PATCH 4/7] fix: Add username uniqueness check to admin user seeding --- server/db/create-admin-user.js | 18 +++++++++++++----- server/db/seeds/default.js | 10 ++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index 77f45194..9c80b24b 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -97,12 +97,20 @@ const input = async (fieldName, options = {}) => { USERNAME_REGEX.test(process.env.DEFAULT_ADMIN_USERNAME); if (isValid) { - break; - } + const existingUser = await knex('user_account') + .where('username', process.env.DEFAULT_ADMIN_USERNAME.toLowerCase()) + .first(); - console.log( - 'Username must be 3-32 characters and contain only letters, digits, underscores, and dots (e.g., john_doe).', - ); + if (!existingUser) { + break; + } + + console.log('Username is already in use.'); + } else { + console.log( + 'Username must be 3-32 characters and contain only letters, digits, underscores, and dots (e.g., john_doe).', + ); + } process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); diff --git a/server/db/seeds/default.js b/server/db/seeds/default.js index ac0296b0..9ce0982d 100644 --- a/server/db/seeds/default.js +++ b/server/db/seeds/default.js @@ -78,6 +78,16 @@ exports.seed = async (knex) => { const userData = buildUserData(); + if (userData.username) { + const existingUsernameUser = await knex('user_account') + .where('username', userData.username) + .first(); + + if (existingUsernameUser) { + throw new Error(`User with DEFAULT_ADMIN_USERNAME "${userData.username}" already exists.`); + } + } + let userId; try { [{ id: userId }] = await knex('user_account').insert( From 2f5252faad5cfede2030088dd988204a9b964d79 Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 07:07:46 +0100 Subject: [PATCH 5/7] fix: Add email uniqueness check to admin user seeding process --- server/db/create-admin-user.js | 13 +++++++++++-- server/db/seeds/default.js | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index 9c80b24b..ee643602 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -58,10 +58,19 @@ const input = async (fieldName, options = {}) => { validator.isEmail(process.env.DEFAULT_ADMIN_EMAIL) && process.env.DEFAULT_ADMIN_EMAIL.length <= 256 ) { - break; + const existingUser = await knex('user_account') + .where('email', process.env.DEFAULT_ADMIN_EMAIL.toLowerCase()) + .first(); + + if (!existingUser) { + break; + } + + console.log('Email is already in use.'); + } else { + console.log('Email must be a valid e-mail address with no more than 256 characters.'); } - console.log('Email must be a valid e-mail address with no more than 256 characters.'); process.env.DEFAULT_ADMIN_EMAIL = await input('Email', { isRequired: true, }); diff --git a/server/db/seeds/default.js b/server/db/seeds/default.js index 9ce0982d..8ac183bc 100644 --- a/server/db/seeds/default.js +++ b/server/db/seeds/default.js @@ -76,6 +76,12 @@ exports.seed = async (knex) => { ); } + const existingEmailUser = await knex('user_account').where('email', defaultAdminEmail).first(); + + if (existingEmailUser) { + throw new Error(`User with DEFAULT_ADMIN_EMAIL "${defaultAdminEmail}" already exists.`); + } + const userData = buildUserData(); if (userData.username) { From aeb345609186f6b10620862b9c53cf93e16b9881 Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 07:42:04 +0100 Subject: [PATCH 6/7] refactor: Replace while true with flag based validation --- server/db/create-admin-user.js | 77 +++++++++++++++------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index ee643602..3282f3c3 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -48,32 +48,28 @@ const input = async (fieldName, options = {}) => { try { await knex.migrate.latest(); - process.env.DEFAULT_ADMIN_EMAIL = await input('Email', { - isRequired: true, - }); + let isEmailValid = false; + while (!isEmailValid) { + process.env.DEFAULT_ADMIN_EMAIL = await input('Email', { + isRequired: true, + }); - // eslint-disable-next-line no-constant-condition - while (true) { if ( - validator.isEmail(process.env.DEFAULT_ADMIN_EMAIL) && - process.env.DEFAULT_ADMIN_EMAIL.length <= 256 + !validator.isEmail(process.env.DEFAULT_ADMIN_EMAIL) || + process.env.DEFAULT_ADMIN_EMAIL.length > 256 ) { + console.log('Email must be a valid e-mail address with no more than 256 characters.'); + } else { const existingUser = await knex('user_account') .where('email', process.env.DEFAULT_ADMIN_EMAIL.toLowerCase()) .first(); - if (!existingUser) { - break; + if (existingUser) { + console.log('Email is already in use.'); + } else { + isEmailValid = true; } - - console.log('Email is already in use.'); - } else { - console.log('Email must be a valid e-mail address with no more than 256 characters.'); } - - process.env.DEFAULT_ADMIN_EMAIL = await input('Email', { - isRequired: true, - }); } process.env.DEFAULT_ADMIN_PASSWORD = await input('Password', { @@ -85,7 +81,6 @@ const input = async (fieldName, options = {}) => { isRequired: true, }); - // eslint-disable-next-line no-constant-condition while (process.env.DEFAULT_ADMIN_NAME.length > 128) { console.log('Name must be no more than 128 characters.'); process.env.DEFAULT_ADMIN_NAME = await input('Name', { @@ -93,38 +88,34 @@ const input = async (fieldName, options = {}) => { }); } - process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); + const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/; - if (process.env.DEFAULT_ADMIN_USERNAME) { - const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/; + let isUsernameValid = false; + while (!isUsernameValid) { + process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); - // eslint-disable-next-line no-constant-condition - while (true) { - const isValid = - process.env.DEFAULT_ADMIN_USERNAME.length >= 3 && - process.env.DEFAULT_ADMIN_USERNAME.length <= 32 && - USERNAME_REGEX.test(process.env.DEFAULT_ADMIN_USERNAME); + if (!process.env.DEFAULT_ADMIN_USERNAME) { + break; + } - if (isValid) { - const existingUser = await knex('user_account') - .where('username', process.env.DEFAULT_ADMIN_USERNAME.toLowerCase()) - .first(); + const isValid = + process.env.DEFAULT_ADMIN_USERNAME.length >= 3 && + process.env.DEFAULT_ADMIN_USERNAME.length <= 32 && + USERNAME_REGEX.test(process.env.DEFAULT_ADMIN_USERNAME); - if (!existingUser) { - break; - } + if (!isValid) { + console.log( + 'Username must be 3-32 characters and contain only letters, digits, underscores, and dots (e.g., john_doe).', + ); + } else { + const existingUser = await knex('user_account') + .where('username', process.env.DEFAULT_ADMIN_USERNAME.toLowerCase()) + .first(); + if (existingUser) { console.log('Username is already in use.'); } else { - console.log( - 'Username must be 3-32 characters and contain only letters, digits, underscores, and dots (e.g., john_doe).', - ); - } - - process.env.DEFAULT_ADMIN_USERNAME = await input('Username'); - - if (!process.env.DEFAULT_ADMIN_USERNAME) { - break; + isUsernameValid = true; } } } From c587a1441d8962e3e78a5ae32538544c47936334 Mon Sep 17 00:00:00 2001 From: Ayman Date: Wed, 8 Jul 2026 08:05:00 +0100 Subject: [PATCH 7/7] fix: Add password validation for admin user seeding process --- server/db/create-admin-user.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/server/db/create-admin-user.js b/server/db/create-admin-user.js index 3282f3c3..4edc8f70 100644 --- a/server/db/create-admin-user.js +++ b/server/db/create-admin-user.js @@ -8,6 +8,7 @@ const { read } = require('read'); const validator = require('validator'); +const zxcvbn = require('zxcvbn'); const initKnex = require('knex'); const knexfile = require('./knexfile'); @@ -72,10 +73,19 @@ const input = async (fieldName, options = {}) => { } } - process.env.DEFAULT_ADMIN_PASSWORD = await input('Password', { - isRequired: true, - isPassword: true, - }); + let isPasswordValid = false; + while (!isPasswordValid) { + process.env.DEFAULT_ADMIN_PASSWORD = await input('Password', { + isRequired: true, + isPassword: true, + }); + + if (zxcvbn(process.env.DEFAULT_ADMIN_PASSWORD).score >= 2) { + isPasswordValid = true; + } else { + console.log('Password is too weak. Choose a stronger password.'); + } + } process.env.DEFAULT_ADMIN_NAME = await input('Name', { isRequired: true,