fix: reintegrate PasswordStrength into password prompt (#4111) (#4291)

This commit is contained in:
Piyush Singh 2026-03-18 13:48:05 +02:00 committed by GitHub
parent b186fb3f64
commit e74a72f482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 89 additions and 3 deletions

View File

@ -4,7 +4,7 @@ from typing import Any, Literal, override
from textual.validation import ValidationResult, Validator
from archinstall.lib.translationhandler import tr
from archinstall.tui.ui.components import InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen
from archinstall.tui.ui.components import InputInfo, InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen
from archinstall.tui.ui.menu_item import MenuItemGroup
from archinstall.tui.ui.result import Result, ResultType
@ -138,6 +138,7 @@ class Input:
allow_skip: bool = True,
allow_reset: bool = False,
validator_callback: Callable[[str], str | None] | None = None,
info_callback: Callable[[str], InputInfo | None] | None = None,
):
self._header = header
self._placeholder = placeholder
@ -146,6 +147,7 @@ class Input:
self._allow_skip = allow_skip
self._allow_reset = allow_reset
self._validator_callback = validator_callback
self._info_callback = info_callback
async def show(self) -> Result[str]:
validator = GenericValidator(self._validator_callback) if self._validator_callback else None
@ -158,6 +160,7 @@ class Input:
allow_skip=self._allow_skip,
allow_reset=self._allow_reset,
validator=validator,
info_callback=self._info_callback,
).run()
if result.type_ == ResultType.Reset:

View File

@ -3,9 +3,9 @@ import time
from pathlib import Path
from archinstall.lib.menu.helpers import Confirmation, Input
from archinstall.lib.models.users import Password
from archinstall.lib.models.users import Password, PasswordStrength
from archinstall.lib.translationhandler import tr
from archinstall.tui.ui.components import tui
from archinstall.tui.ui.components import InputInfo, InputInfoType, tui
from archinstall.tui.ui.result import ResultType
@ -15,12 +15,25 @@ async def get_password(
preset: str | None = None,
skip_confirmation: bool = False,
) -> Password | None:
def password_hint(value: str) -> InputInfo | None:
if not value:
return None
strength = PasswordStrength.strength(value)
if strength in (PasswordStrength.VERY_WEAK, PasswordStrength.WEAK):
return InputInfo(message=tr('Password strength: Weak'), info_type=InputInfoType.MsgError)
elif strength == PasswordStrength.MODERATE:
return InputInfo(message=tr('Password strength: Moderate'), info_type=InputInfoType.MsgWarning)
elif strength == PasswordStrength.STRONG:
return InputInfo(message=tr('Password strength: Strong'), info_type=InputInfoType.MsgInfo)
return None
while True:
result = await Input(
header=header,
allow_skip=allow_skip,
default_value=preset,
password=True,
info_callback=password_hint,
).show()
if result.type_ == ResultType.Skip:

View File

@ -1,6 +1,8 @@
import sys
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from enum import Enum, auto
from typing import Any, ClassVar, Literal, TypeVar, cast, override
from textual import work
@ -707,6 +709,18 @@ class NotifyScreen(ConfirmationScreen[ValueT]):
super().__init__(group, header)
class InputInfoType(Enum):
MsgInfo = auto()
MsgWarning = auto()
MsgError = auto()
@dataclass
class InputInfo:
message: str
info_type: InputInfoType
class InputScreen(BaseScreen[str]):
CSS = """
InputScreen {
@ -728,6 +742,22 @@ class InputScreen(BaseScreen[str]):
color: red;
text-align: center;
}
#input-info {
text-align: center;
}
.input-hint-msg-error {
color: red;
}
.input-hint-msg-warning {
color: yellow;
}
.input-hint-msg-info {
color: green;
}
"""
def __init__(
@ -739,6 +769,7 @@ class InputScreen(BaseScreen[str]):
allow_reset: bool = False,
allow_skip: bool = False,
validator: Validator | None = None,
info_callback: Callable[[str], InputInfo | None] | None = None,
):
super().__init__(allow_skip, allow_reset)
self._header = header or ''
@ -748,6 +779,7 @@ class InputScreen(BaseScreen[str]):
self._allow_reset = allow_reset
self._allow_skip = allow_skip
self._validator = validator
self._info_callback = info_callback
async def run(self) -> Result[str]:
assert TApp.app
@ -768,6 +800,7 @@ class InputScreen(BaseScreen[str]):
validate_on=['submitted'],
)
yield Label('', classes='input-failure', id='input-failure')
yield Label('', id='input-info')
yield Footer()
@ -784,6 +817,24 @@ class InputScreen(BaseScreen[str]):
else:
_ = self.dismiss(Result(ResultType.Selection, _data=event.value))
def on_input_changed(self, event: Input.Changed) -> None:
info_label = self.query_one('#input-info', Label)
if self._info_callback:
result = self._info_callback(event.value)
if result:
css_class = ''
if result.info_type == InputInfoType.MsgError:
css_class = 'input-hint-msg-error'
elif result.info_type == InputInfoType.MsgWarning:
css_class = 'input-hint-msg-warning'
elif result.info_type == InputInfoType.MsgInfo:
css_class = 'input-hint-msg-info'
info_label.update(result.message)
info_label.set_classes(css_class)
else:
info_label.update('')
info_label.set_classes('')
class _DataTable(DataTable[ValueT]):
BINDINGS: ClassVar = [

View File

@ -0,0 +1,19 @@
import pytest
from archinstall.lib.models.users import PasswordStrength
@pytest.mark.parametrize(
'password, expected',
[
('abc', PasswordStrength.VERY_WEAK),
('Abcdef1!', PasswordStrength.WEAK),
('Abcdef1234!', PasswordStrength.MODERATE),
('Abcdef12345!@', PasswordStrength.STRONG),
('', PasswordStrength.VERY_WEAK),
('123456789', PasswordStrength.VERY_WEAK),
('abcdefghijklmnopqr', PasswordStrength.STRONG),
],
)
def test_password_strength(password: str, expected: PasswordStrength) -> None:
assert PasswordStrength.strength(password) == expected