mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 13:28:12 -04:00
Increase max password generator length to 256 chars in mobile app (#1701)
This commit is contained in:
committed by
Leendert de Borst
parent
438528a123
commit
18b61029ea
@@ -7,6 +7,7 @@ import { StyleSheet, View, TouchableOpacity, Switch, Platform } from 'react-nati
|
||||
|
||||
import type { PasswordSettings } from '@/utils/dist/core/models/vault';
|
||||
import { CreatePasswordGenerator } from '@/utils/dist/core/password-generator';
|
||||
import { sliderToLength, lengthToSlider, SLIDER_MIN, SLIDER_MAX } from '@/utils/passwordLengthSlider';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
import { useVaultMutate } from '@/hooks/useVaultMutate';
|
||||
@@ -60,7 +61,7 @@ export default function PasswordGeneratorSettingsScreen(): React.ReactNode {
|
||||
const passwordSettings = await dbContext.sqliteClient!.getPasswordSettings();
|
||||
|
||||
setSettings(passwordSettings);
|
||||
setSliderValue(passwordSettings.Length);
|
||||
setSliderValue(lengthToSlider(passwordSettings.Length));
|
||||
initialValues.current = passwordSettings;
|
||||
|
||||
// Generate initial preview password only once
|
||||
@@ -119,19 +120,19 @@ export default function PasswordGeneratorSettingsScreen(): React.ReactNode {
|
||||
* Handle slider value change.
|
||||
*/
|
||||
const handleSliderChange = useCallback((value: number): void => {
|
||||
const roundedLength = Math.round(value);
|
||||
setSliderValue(roundedLength);
|
||||
setSliderValue(value);
|
||||
const passwordLength = sliderToLength(value);
|
||||
|
||||
// Only generate if value actually changed and we're actively sliding
|
||||
if (roundedLength !== lastGeneratedLength.current && isSliding.current && settings) {
|
||||
lastGeneratedLength.current = roundedLength;
|
||||
if (passwordLength !== lastGeneratedLength.current && isSliding.current && settings) {
|
||||
lastGeneratedLength.current = passwordLength;
|
||||
|
||||
// Update settings and regenerate password
|
||||
const newSettings = { ...settings, Length: roundedLength };
|
||||
const newSettings = { ...settings, Length: passwordLength };
|
||||
setSettings(newSettings);
|
||||
|
||||
// Track the change
|
||||
pendingChanges.current = { ...pendingChanges.current, Length: roundedLength };
|
||||
pendingChanges.current = { ...pendingChanges.current, Length: passwordLength };
|
||||
|
||||
// Generate new preview password
|
||||
try {
|
||||
@@ -149,7 +150,7 @@ export default function PasswordGeneratorSettingsScreen(): React.ReactNode {
|
||||
*/
|
||||
const handleSliderStart = useCallback((): void => {
|
||||
isSliding.current = true;
|
||||
lastGeneratedLength.current = sliderValue ?? 0;
|
||||
lastGeneratedLength.current = sliderToLength(sliderValue ?? 0);
|
||||
}, [sliderValue]);
|
||||
|
||||
/**
|
||||
@@ -157,18 +158,18 @@ export default function PasswordGeneratorSettingsScreen(): React.ReactNode {
|
||||
*/
|
||||
const handleSliderComplete = useCallback((value: number): void => {
|
||||
isSliding.current = false;
|
||||
const roundedLength = Math.round(value);
|
||||
const passwordLength = sliderToLength(value);
|
||||
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update settings with final value
|
||||
const newSettings = { ...settings, Length: roundedLength };
|
||||
const newSettings = { ...settings, Length: passwordLength };
|
||||
setSettings(newSettings);
|
||||
|
||||
// Track the change
|
||||
pendingChanges.current = { ...pendingChanges.current, Length: roundedLength };
|
||||
pendingChanges.current = { ...pendingChanges.current, Length: passwordLength };
|
||||
|
||||
// Generate password with final value
|
||||
try {
|
||||
@@ -325,7 +326,7 @@ export default function PasswordGeneratorSettingsScreen(): React.ReactNode {
|
||||
<View style={styles.previewContainer}>
|
||||
<ThemedText style={styles.previewLabel}>{t('settings.passwordGeneratorSettings.preview')}</ThemedText>
|
||||
<View style={styles.previewInputContainer}>
|
||||
<ThemedText style={styles.previewInput}>{previewPassword}</ThemedText>
|
||||
<ThemedText style={styles.previewInput} numberOfLines={1} ellipsizeMode="tail">{previewPassword}</ThemedText>
|
||||
<TouchableOpacity
|
||||
style={styles.refreshButton}
|
||||
onPress={handleRefreshPreview}
|
||||
@@ -340,17 +341,16 @@ export default function PasswordGeneratorSettingsScreen(): React.ReactNode {
|
||||
<View style={styles.sliderContainer}>
|
||||
<View style={styles.sliderHeader}>
|
||||
<ThemedText style={styles.sliderLabel}>{t('items.passwordLength')}</ThemedText>
|
||||
<ThemedText style={styles.sliderValue}>{sliderValue ?? 0}</ThemedText>
|
||||
<ThemedText style={styles.sliderValue}>{sliderToLength(sliderValue ?? 0)}</ThemedText>
|
||||
</View>
|
||||
<Slider
|
||||
style={styles.slider}
|
||||
minimumValue={8}
|
||||
maximumValue={64}
|
||||
minimumValue={SLIDER_MIN}
|
||||
maximumValue={SLIDER_MAX}
|
||||
value={sliderValue ?? 0}
|
||||
onValueChange={handleSliderChange}
|
||||
onSlidingStart={handleSliderStart}
|
||||
onSlidingComplete={handleSliderComplete}
|
||||
step={1}
|
||||
minimumTrackTintColor={colors.primary}
|
||||
maximumTrackTintColor={colors.accentBorder}
|
||||
thumbTintColor={colors.primary}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { View, TextInput, TextInputProps, StyleSheet, TouchableOpacity, Platform
|
||||
|
||||
import type { PasswordSettings } from '@/utils/dist/core/models/vault';
|
||||
import { CreatePasswordGenerator } from '@/utils/dist/core/password-generator';
|
||||
import { sliderToLength, lengthToSlider, SLIDER_MIN, SLIDER_MAX } from '@/utils/passwordLengthSlider';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
|
||||
@@ -58,12 +59,12 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
// Initialize slider value immediately from initialSettings or value length, otherwise default to 16
|
||||
const [sliderValue, setSliderValue] = useState<number>(() => {
|
||||
if (initialSettings) {
|
||||
return initialSettings.Length;
|
||||
return lengthToSlider(initialSettings.Length);
|
||||
}
|
||||
if (!isNewCredential && value && value.length > 0) {
|
||||
return value.length;
|
||||
return lengthToSlider(value.length);
|
||||
}
|
||||
return 16;
|
||||
return lengthToSlider(16);
|
||||
});
|
||||
const lastGeneratedLength = useRef<number>(0);
|
||||
const isSliding = useRef(false);
|
||||
@@ -92,7 +93,7 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
setCurrentSettings(settings);
|
||||
// Only update slider if we haven't set it from value yet
|
||||
if (!hasSetInitialLength.current) {
|
||||
setSliderValue(settings.Length);
|
||||
setSliderValue(lengthToSlider(settings.Length));
|
||||
hasSetInitialLength.current = true;
|
||||
}
|
||||
}
|
||||
@@ -107,7 +108,7 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
useEffect(() => {
|
||||
if (!hasSetInitialLength.current) {
|
||||
if (!isNewCredential && value && value.length > 0) {
|
||||
setSliderValue(value.length);
|
||||
setSliderValue(lengthToSlider(value.length));
|
||||
hasSetInitialLength.current = true;
|
||||
} else if (isNewCredential) {
|
||||
hasSetInitialLength.current = true;
|
||||
@@ -146,17 +147,17 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
}, [currentSettings, generatePassword, onChangeText, setShowPasswordState]);
|
||||
|
||||
const handleSliderChange = useCallback((sliderVal: number) => {
|
||||
const roundedLength = Math.round(sliderVal);
|
||||
setSliderValue(roundedLength);
|
||||
setSliderValue(sliderVal);
|
||||
const passwordLength = sliderToLength(sliderVal);
|
||||
|
||||
if (roundedLength !== lastGeneratedLength.current && isSliding.current) {
|
||||
lastGeneratedLength.current = roundedLength;
|
||||
if (passwordLength !== lastGeneratedLength.current && isSliding.current) {
|
||||
lastGeneratedLength.current = passwordLength;
|
||||
|
||||
if (!showPassword) {
|
||||
setShowPasswordState(true);
|
||||
}
|
||||
|
||||
const newSettings = { ...(currentSettings || {}), Length: roundedLength } as PasswordSettings;
|
||||
const newSettings = { ...(currentSettings || {}), Length: passwordLength } as PasswordSettings;
|
||||
if (currentSettings) {
|
||||
const password = generatePassword(newSettings);
|
||||
if (password) {
|
||||
@@ -168,14 +169,14 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
|
||||
const handleSliderStart = useCallback(() => {
|
||||
isSliding.current = true;
|
||||
lastGeneratedLength.current = sliderValue;
|
||||
lastGeneratedLength.current = sliderToLength(sliderValue);
|
||||
}, [sliderValue]);
|
||||
|
||||
const handleSliderComplete = useCallback((sliderVal: number) => {
|
||||
isSliding.current = false;
|
||||
const roundedLength = Math.round(sliderVal);
|
||||
const passwordLength = sliderToLength(sliderVal);
|
||||
if (currentSettings) {
|
||||
const newSettings = { ...currentSettings, Length: roundedLength };
|
||||
const newSettings = { ...currentSettings, Length: passwordLength };
|
||||
setCurrentSettings(newSettings);
|
||||
}
|
||||
lastGeneratedLength.current = 0;
|
||||
@@ -531,7 +532,7 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
<View style={styles.sliderHeader}>
|
||||
<ThemedText style={styles.sliderLabel}>{t('items.passwordLength')}</ThemedText>
|
||||
<View style={styles.sliderValueContainer}>
|
||||
<ThemedText style={styles.sliderValue}>{sliderValue}</ThemedText>
|
||||
<ThemedText style={styles.sliderValue}>{sliderToLength(sliderValue)}</ThemedText>
|
||||
<TouchableOpacity
|
||||
style={styles.settingsButton}
|
||||
onPress={handleOpenSettings}
|
||||
@@ -544,13 +545,12 @@ const AdvancedPasswordFieldComponent = forwardRef<AdvancedPasswordFieldRef, Adva
|
||||
|
||||
<Slider
|
||||
style={styles.slider}
|
||||
minimumValue={8}
|
||||
maximumValue={64}
|
||||
minimumValue={SLIDER_MIN}
|
||||
maximumValue={SLIDER_MAX}
|
||||
value={sliderValue}
|
||||
onValueChange={handleSliderChange}
|
||||
onSlidingStart={handleSliderStart}
|
||||
onSlidingComplete={handleSliderComplete}
|
||||
step={1}
|
||||
minimumTrackTintColor={colors.primary}
|
||||
maximumTrackTintColor={colors.accentBorder}
|
||||
thumbTintColor={colors.primary}
|
||||
|
||||
57
apps/mobile-app/utils/passwordLengthSlider.ts
Normal file
57
apps/mobile-app/utils/passwordLengthSlider.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Utility functions for password length slider with non-linear scaling.
|
||||
*
|
||||
* The slider uses a power curve to provide fine-grained control at lower values
|
||||
* (where most users operate, e.g., 12-32 chars) and coarser control at higher values
|
||||
* (64-256 chars).
|
||||
*
|
||||
* This makes it easy to select common password lengths while still allowing
|
||||
* very long passwords when needed.
|
||||
*/
|
||||
|
||||
/** Minimum password length */
|
||||
export const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
/** Maximum password length */
|
||||
export const MAX_PASSWORD_LENGTH = 256;
|
||||
|
||||
/** Slider minimum value (internal representation) */
|
||||
export const SLIDER_MIN = 0;
|
||||
|
||||
/** Slider maximum value (internal representation) */
|
||||
export const SLIDER_MAX = 100;
|
||||
|
||||
/**
|
||||
* Exponent for the power curve.
|
||||
* Higher values = more precision at lower lengths.
|
||||
* 2.0 gives a good balance where ~50% slider = ~70 chars
|
||||
*/
|
||||
const EXPONENT = 2.0;
|
||||
|
||||
/**
|
||||
* Convert a slider position (0-100) to an actual password length (8-256).
|
||||
* Uses a power curve for non-linear scaling.
|
||||
*
|
||||
* @param sliderValue - The slider position (0-100)
|
||||
* @returns The password length (8-256)
|
||||
*/
|
||||
export function sliderToLength(sliderValue: number): number {
|
||||
const normalized = Math.max(0, Math.min(1, sliderValue / SLIDER_MAX));
|
||||
const curved = Math.pow(normalized, EXPONENT);
|
||||
const length = MIN_PASSWORD_LENGTH + curved * (MAX_PASSWORD_LENGTH - MIN_PASSWORD_LENGTH);
|
||||
return Math.round(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a password length (8-256) to a slider position (0-100).
|
||||
* Inverse of sliderToLength.
|
||||
*
|
||||
* @param length - The password length (8-256)
|
||||
* @returns The slider position (0-100)
|
||||
*/
|
||||
export function lengthToSlider(length: number): number {
|
||||
const clampedLength = Math.max(MIN_PASSWORD_LENGTH, Math.min(MAX_PASSWORD_LENGTH, length));
|
||||
const normalized = (clampedLength - MIN_PASSWORD_LENGTH) / (MAX_PASSWORD_LENGTH - MIN_PASSWORD_LENGTH);
|
||||
const curved = Math.pow(normalized, 1 / EXPONENT);
|
||||
return curved * SLIDER_MAX;
|
||||
}
|
||||
Reference in New Issue
Block a user