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

feat: added configurable password validation #7640

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
11 changes: 6 additions & 5 deletions dev/prod/public/config.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"ACCOUNTS_URL":"/account",
"ACCOUNTS_URL": "/account",
"COLLABORATOR_URL": "ws://localhost:3078",
"UPLOAD_URL":"/files",
"UPLOAD_URL": "/files",
"TELEGRAM_URL": "http://localhost:8086",
"GMAIL_URL": "http://localhost:8088",
"CALENDAR_URL": "http://localhost:8095",
"REKONI_URL": "/rekoni",
"GITHUB_APP": "uberflow-dev",
"GITHUB_CLIENTID": "Iv1.43f9cac43bd68617",
"GITHUB_URL":"http://localhost:3500",
"GITHUB_URL": "http://localhost:3500",
"LAST_NAME_FIRST": "true",
"PRINT_URL": "http://localhost:4005",
"SIGN_URL": "http://localhost:4006",
Expand All @@ -17,5 +17,6 @@
"BRANDING_URL": "/branding.json",
"VERSION": null,
"MODEL_VERSION": null,
"STATS_URL": "http://localhost:4900"
}
"STATS_URL": "http://localhost:4900",
"PASSWORD_STRICTNESS": "none"
}
34 changes: 34 additions & 0 deletions dev/prod/src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export interface Config {
AI_URL?:string
DISABLE_SIGNUP?: string
LINK_PREVIEW_URL?: string
PASSWORD_STRICTNESS?: "very_strict" | "strict" | "normal" | "none"
// Could be defined for dev environment
FRONT_URL?: string
PREVIEW_CONFIG?: string
Expand Down Expand Up @@ -198,6 +199,37 @@ const configs: Record<string, string> = {
'dev-worker-local': '/config-worker-local.json',
}

const PASSWORD_REQUIREMENTS : Record<Config['PASSWORD_STRICTNESS'], Record<string, number>> = {
very_strict: {
MinDigits: 4,
MinLength: 32,
MinLowerChars: 4,
MinSpecialChars: 4,
MinUpperChars: 4
},
strict: {
MinDigits: 2,
MinLength: 16,
MinLowerChars: 2,
MinSpecialChars: 2,
MinUpperChars: 2
},
normal: {
MinDigits: 1,
MinLength: 8,
MinLowerChars: 1,
MinSpecialChars: 1,
MinUpperChars: 1
},
none: {
MinDigits: 0,
MinLength: 0,
MinLowerChars: 0,
MinSpecialChars: 0,
MinUpperChars: 0
}
}

function configureI18n(): void {
//Add localization
addStringsLoader(platformId, async (lang: string) => await import(`@hcengineering/platform/lang/${lang}.json`))
Expand Down Expand Up @@ -306,6 +338,8 @@ export async function configurePlatform() {

setMetadata(login.metadata.AccountsUrl, config.ACCOUNTS_URL)
setMetadata(login.metadata.DisableSignUp, config.DISABLE_SIGNUP === 'true')

setMetadata(login.metadata.PasswordValidations, PASSWORD_REQUIREMENTS[config.PASSWORD_STRICTNESS ?? 'none'])

setMetadata(presentation.metadata.FilesURL, config.FILES_URL)
setMetadata(presentation.metadata.UploadURL, config.UPLOAD_URL)
Expand Down
7 changes: 6 additions & 1 deletion plugins/login-assets/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Přeskočit",
"SignUpCompleted": "Registrace dokončena",
"StartUsingHuly": "Začněte používat Huly",
"PasswordMinLength": "{count, plural, =1 {Heslo musí být alespoň # znak dlouhé} other {Heslo musí být alespoň # znaků dlouhé}}",
"PasswordMinSpecialChars": "{count, plural, =1 {Heslo musí obsahovat alespoň # speciální znak} other {Heslo musí obsahovat alespoň # speciálních znaků}}",
"PasswordMinDigits": "{count, plural, =1 {Heslo musí obsahovat alespoň # číslo} other {Heslo musí obsahovat alespoň # čísel}}",
"PasswordMinUpperChars": "{count, plural, =1 {Heslo musí obsahovat alespoň # velké písmeno} other {Heslo musí obsahovat alespoň # velkých písmen}}",
"PasswordMinLowerChars": "{count, plural, =1 {Heslo musí obsahovat alespoň # malé písmeno} other {Heslo musí obsahovat alespoň # malých písmen}}",
"WorkspaceIsArchived": "Pracovní prostor je archivován kvůli nečinnosti. Kontaktujte nás prosím pro obnovení..."
}
}
}
5 changes: 5 additions & 0 deletions plugins/login-assets/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Überspringen",
"SignUpCompleted": "Registrierung abgeschlossen",
"StartUsingHuly": "Starten Sie mit Huly",
"PasswordMinLength": "{count, plural, =1 {Das Passwort muss mindestens # Zeichen lang sein} other {Das Passwort muss mindestens # Zeichen lang sein}}",
"PasswordMinSpecialChars": "{count, plural, =1 {Das Passwort muss mindestens # Sonderzeichen enthalten} other {Das Passwort muss mindestens # Sonderzeichen enthalten}}",
"PasswordMinDigits": "{count, plural, =1 {Das Passwort muss mindestens # Zahl enthalten} other {Das Passwort muss mindestens # Zahlen enthalten}}",
"PasswordMinUpperChars": "{count, plural, =1 {Das Passwort muss mindestens # Großbuchstaben enthalten} other {Das Passwort muss mindestens # Großbuchstaben enthalten}}",
"PasswordMinLowerChars": "{count, plural, =1 {Das Passwort muss mindestens # Kleinbuchstaben enthalten} other {Das Passwort muss mindestens # Kleinbuchstaben enthalten}}",
"WorkspaceArchivedDesc": "Workspace wurde wegen Inaktivität archiviert. Bitte kontaktieren Sie uns zur Wiederherstellung..."
}
}
5 changes: 5 additions & 0 deletions plugins/login-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Skip",
"SignUpCompleted": "Sign up completed",
"StartUsingHuly": "Start using Huly",
"PasswordMinLength": "{count, plural, =1 {Password must be at least # character long} other {Password must be at least # characters long}}",
"PasswordMinSpecialChars": "{count, plural, =1 {Password must contain at least # special character} other {Password must contain at least # special characters}}",
"PasswordMinDigits": "{count, plural, =1 {Password must contain at least # number} other {Password must contain at least # numbers}}",
"PasswordMinUpperChars": "{count, plural, =1 {Password must contain at least # uppercase letter} other {Password must contain at least # uppercase letters}}",
"PasswordMinLowerChars": "{count, plural, =1 {Password must contain at least # lowercase letter} other {Password must contain at least # lowercase letters}}",
"WorkspaceArchivedDesc": "Workspace is archived because of being unused, Please contact us to restore..."
}
}
7 changes: 6 additions & 1 deletion plugins/login-assets/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Saltar",
"SignUpCompleted": "Registro completado",
"StartUsingHuly": "Comienza a usar Huly",
"PasswordMinLength": "{count, plural, =1 {La contraseña debe tener al menos # carácter} other {La contraseña debe tener al menos # caracteres}}",
"PasswordMinSpecialChars": "{count, plural, =1 {La contraseña debe contener al menos # carácter especial} other {La contraseña debe contener al menos # caracteres especiales}}",
"PasswordMinDigits": "{count, plural, =1 {La contraseña debe contener al menos # número} other {La contraseña debe contener al menos # números}}",
"PasswordMinUpperChars": "{count, plural, =1 {La contraseña debe contener al menos # letra mayúscula} other {La contraseña debe contener al menos # letras mayúsculas}}",
"PasswordMinLowerChars": "{count, plural, =1 {La contraseña debe contener al menos # letra minúscula} other {La contraseña debe contener al menos # letras minúsculas}}",
"WorkspaceArchivedDesc": "El espacio de trabajo está archivado por no estar en uso, por favor contáctenos para restaurarlo..."
}
}
}
9 changes: 7 additions & 2 deletions plugins/login-assets/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Passer",
"SignUpCompleted": "Inscription terminée",
"StartUsingHuly": "Commencez à utiliser Huly",
"WorkspaceArchivedDesc": "L'espace de travail est archivé en raison de son inactivité, veuillez nous contacter pour le restaurer..."
"PasswordMinLength": "{count, plural, =1 {Le mot de passe doit contenir au moins # caractère} other {Le mot de passe doit contenir au moins # caractères}}",
"PasswordMinSpecialChars": "{count, plural, =1 {Le mot de passe doit contenir au moins # caractère spécial} other {Le mot de passe doit contenir au moins # caractères spéciaux}}",
"PasswordMinDigits": "{count, plural, =1 {Le mot de passe doit contenir au moins # chiffre} other {Le mot de passe doit contenir au moins # chiffres}}",
"PasswordMinUpperChars": "{count, plural, =1 {Le mot de passe doit contenir au moins # lettre majuscule} other {Le mot de passe doit contenir au moins # lettres majuscules}}",
"PasswordMinLowerChars": "{count, plural, =1 {Le mot de passe doit contenir au moins # lettre minuscule} other {Le mot de passe doit contenir au moins # lettres minuscules}}",
"WorkspaceArchivedDesc": "L'espace de travail est archivé en raison de son inactivité, veuillez nous contacter pour le restaurer..."
}
}
}
5 changes: 5 additions & 0 deletions plugins/login-assets/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Salta",
"SignUpCompleted": "Registrazione completata",
"StartUsingHuly": "Inizia a usare Huly",
"PasswordMinLength": "{count, plural, =1 {La password deve contenere almeno # carattere} other {La password deve contenere almeno # caratteri}}",
"PasswordMinSpecialChars": "{count, plural, =1 {La password deve contenere almeno # carattere speciale} other {La password deve contenere almeno # caratteri speciali}}",
"PasswordMinDigits": "{count, plural, =1 {La password deve contenere almeno # numero} other {La password deve contenere almeno # numeri}}",
"PasswordMinUpperChars": "{count, plural, =1 {La password deve contenere almeno # lettera maiuscola} other {La password deve contenere almeno # lettere maiuscole}}",
"PasswordMinLowerChars": "{count, plural, =1 {La password deve contenere almeno # lettera minuscola} other {La password deve contenere almeno # lettere minuscole}}",
"WorkspaceArchivedDesc": "Il workspace è stato archiviato perché inutilizzato. Si prega di contattarci per ripristinarlo..."
}
}
7 changes: 6 additions & 1 deletion plugins/login-assets/lang/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Saltar",
"SignUpCompleted": "Registo concluído",
"StartUsingHuly": "Começar a usar Huly",
"PasswordMinLength": "{count, plural, =1 {A senha deve ter pelo menos # caractere} other {A senha deve ter pelo menos # caracteres}}",
"PasswordMinSpecialChars": "{count, plural, =1 {A senha deve conter pelo menos # caractere especial} other {A senha deve conter pelo menos # caracteres especiais}}",
"PasswordMinDigits": "{count, plural, =1 {A senha deve conter pelo menos # número} other {A senha deve conter pelo menos # números}}",
"PasswordMinUpperChars": "{count, plural, =1 {A senha deve conter pelo menos # letra maiúscula} other {A senha deve conter pelo menos # letras maiúsculas}}",
"PasswordMinLowerChars": "{count, plural, =1 {A senha deve conter pelo menos # letra minúscula} other {A senha deve conter pelo menos # letras minúsculas}}",
"WorkspaceArchivedDesc": "O espaço de trabalho está arquivado por estar inativo, por favor, entre em contato conosco para restaurá-lo..."
}
}
}
5 changes: 5 additions & 0 deletions plugins/login-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "Пропустить",
"SignUpCompleted": "Регистрация завершена",
"StartUsingHuly": "Начать использовать Huly",
"PasswordMinLength": "{count, plural, =1 {Пароль должен содержать минимум # символ} other {Пароль должен содержать минимум # символов}}",
"PasswordMinSpecialChars": "{count, plural, =1 {Пароль должен содержать минимум # специальный символ} other {Пароль должен содержать минимум # специальных символов}}",
"PasswordMinDigits": "{count, plural, =1 {Пароль должен содержать минимум # цифру} other {Пароль должен содержать минимум # цифр}}",
"PasswordMinUpperChars": "{count, plural, =1 {Пароль должен содержать минимум # заглавную букву} other {Пароль должен содержать минимум # заглавных букв}}",
"PasswordMinLowerChars": "{count, plural, =1 {Пароль должен содержать минимум # строчную букву} other {Пароль должен содержать минимум # строчных букв}}",
"WorkspaceArchivedDesc": "Рабочее пространство архивировано из-за неиспользования, пожалуйста, свяжитесь с нами для восстановления..."
}
}
5 changes: 5 additions & 0 deletions plugins/login-assets/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
"Skip": "跳过",
"SignUpCompleted": "注册完成",
"StartUsingHuly": "开始使用 Huly",
"PasswordMinLength": "{count, plural, =1 {密码至少需要 # 个字符} other {密码至少需要 # 个字符}}",
"PasswordMinSpecialChars": "{count, plural, =1 {密码至少需要 # 个特殊字符} other {密码至少需要 # 个特殊字符}}",
"PasswordMinDigits": "{count, plural, =1 {密码至少需要 # 个数字} other {密码至少需要 # 个数字}}",
"PasswordMinUpperChars": "{count, plural, =1 {密码至少需要 # 个大写字母} other {密码至少需要 # 个大写字母}}",
"PasswordMinLowerChars": "{count, plural, =1 {密码至少需要 # 个小写字母} other {密码至少需要 # 个小写字母}}",
"WorkspaceArchivedDesc": "工作区因未使用而归档,请与我们联系以恢复..."
}
}
22 changes: 6 additions & 16 deletions plugins/login-resources/src/components/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,12 @@
import { onMount } from 'svelte'
import { BottomAction } from '..'
import { makeSequential } from '../mutex'
import type { Field } from '../types'
import login from '../plugin'
import BottomActionComponent from './BottomAction.svelte'
import Providers from './Providers.svelte'
import Tabs from './Tabs.svelte'

interface Field {
id?: string
name: string
i18n: IntlString
password?: boolean
optional?: boolean
short?: boolean
rules?: {
rule: RegExp
notMatch: boolean
ruleDescr: IntlString
}[]
}

interface Action {
i18n: IntlString
func: () => Promise<void>
Expand Down Expand Up @@ -85,8 +72,11 @@
}
if (f.rules !== undefined) {
for (const rule of f.rules) {
if (rule.rule.test(v) === rule.notMatch) {
status = new Status(Severity.INFO, rule.ruleDescr, {})
const isValid =
typeof rule.rule === 'function' ? rule.rule(v) !== rule.notMatch : rule.rule.test(v) !== rule.notMatch

if (!isValid) {
status = new Status(Severity.INFO, rule.ruleDescr, rule.ruleDescrParams ?? {})
return false
}
}
Expand Down
12 changes: 10 additions & 2 deletions plugins/login-resources/src/components/PasswordRestore.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@
import presentation from '@hcengineering/presentation'
import { getCurrentLocation, setMetadataLocalStorage } from '@hcengineering/ui'
import login from '../plugin'
import type { Field } from '../types'
import { goTo, restorePassword } from '../utils'
import Form from './Form.svelte'
import { getPasswordValidationRules } from '../validations'

const fields = [
{ id: 'new-password', name: 'password', i18n: login.string.Password, password: true },
const fields: Array<Field> = [
{
id: 'new-password',
name: 'password',
i18n: login.string.Password,
password: true,
rules: getPasswordValidationRules()
},
{ id: 'new-password', name: 'password2', i18n: login.string.PasswordRepeat, password: true }
]

Expand Down
12 changes: 10 additions & 2 deletions plugins/login-resources/src/components/SignupForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@
import presentation from '@hcengineering/presentation'
import { setMetadataLocalStorage } from '@hcengineering/ui'
import login from '../plugin'
import { getPasswordValidationRules } from '../validations'
import { goTo, signUp } from '../utils'
import Form from './Form.svelte'
import type { Field } from '../types'

export let signUpDisabled = false

const fields = [
const fields: Array<Field> = [
{ id: 'given-name', name: 'first', i18n: login.string.FirstName, short: true },
{ id: 'family-name', name: 'last', i18n: login.string.LastName, short: true },
{ id: 'email', name: 'username', i18n: login.string.Email },
{ id: 'new-password', name: 'password', i18n: login.string.Password, password: true },
{
id: 'new-password',
name: 'password',
i18n: login.string.Password,
password: true,
rules: getPasswordValidationRules()
},
{ id: 'new-password', name: 'password2', i18n: login.string.PasswordRepeat, password: true }
]

Expand Down
31 changes: 31 additions & 0 deletions plugins/login-resources/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright © 2024 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 IntlString } from '@hcengineering/platform'

export interface Field {
id?: string
name: string
i18n: IntlString
password?: boolean
optional?: boolean
short?: boolean
rules?: Array<{
rule: RegExp | ((value: string) => boolean)
notMatch: boolean
ruleDescr: IntlString | IntlString<Record<string, any>>
ruleDescrParams?: Record<string, any>
}>
}
64 changes: 64 additions & 0 deletions plugins/login-resources/src/validations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// Copyright © 2022 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 { getMetadata } from '@hcengineering/platform'

import login from './plugin'
import type { Field } from './types'

/**
* Generates password validation rules based on metadata.
*
* @returns {Field['rules']} An array of password validation rules.
*/
export function getPasswordValidationRules (): Field['rules'] {
const passwordValidations = getMetadata(login.metadata.PasswordValidations)

const passwordValidationRules: Field['rules'] = [
{
rule: (value: string) => value.length >= (passwordValidations?.MinLength ?? 0),
notMatch: false,
ruleDescr: login.string.PasswordMinLength,
ruleDescrParams: { count: passwordValidations?.MinLength }
},
{
rule: (value: string) =>
(value.match(/[^a-zA-Z0-9]/g)?.length ?? 0) >= (passwordValidations?.MinSpecialChars ?? 0),
notMatch: false,
ruleDescr: login.string.PasswordMinSpecialChars,
ruleDescrParams: { count: passwordValidations?.MinSpecialChars }
},
{
rule: (value: string) => (value.match(/[0-9]/g)?.length ?? 0) >= (passwordValidations?.MinDigits ?? 0),
notMatch: false,
ruleDescr: login.string.PasswordMinDigits,
ruleDescrParams: { count: passwordValidations?.MinDigits }
},
{
rule: (value: string) => (value.match(/[A-Z]/g)?.length ?? 0) >= (passwordValidations?.MinUpperChars ?? 0),
notMatch: false,
ruleDescr: login.string.PasswordMinUpperChars,
ruleDescrParams: { count: passwordValidations?.MinUpperChars }
},
{
rule: (value: string) => (value.match(/[a-z]/g)?.length ?? 0) >= (passwordValidations?.MinLowerChars ?? 0),
notMatch: false,
ruleDescr: login.string.PasswordMinLowerChars,
ruleDescrParams: { count: passwordValidations?.MinLowerChars }
}
]

return passwordValidationRules
}
Loading
Loading