From b1167ad599e305cfdd8cddff6059f9888c5d5f1e Mon Sep 17 00:00:00 2001 From: Efe Date: Mon, 27 Jan 2025 10:36:09 +0100 Subject: [PATCH 1/8] Refactor change password page ux --- apps/wallet/locales/en/common_wallet.json | 8 +- apps/wallet/pages/[param]/password.tsx | 454 ++++++++++++++-------- 2 files changed, 292 insertions(+), 170 deletions(-) diff --git a/apps/wallet/locales/en/common_wallet.json b/apps/wallet/locales/en/common_wallet.json index 94870528c..a4a7b7e25 100644 --- a/apps/wallet/locales/en/common_wallet.json +++ b/apps/wallet/locales/en/common_wallet.json @@ -327,12 +327,12 @@ "change_password_page": { "change_password": "Change Password", "account_name": "Account Name", - "current_password": "Current Password", + "current_password": "Current Master Password", "recover_password": "Recover Account", - "generated_password": "Generated Password", + "generated_password": "Generated Password and Keys", "new": "New", - "click_to_generate_password": "Click to generate password", - "re_enter_generate_password": "Re-enter Generated Password", + "click_to_generate_password": "Click to generate new master password", + "re_enter_generate_password": "Re-enter Generated Master Password", "understand_that": "I understand that Hive cannot recover lost passwords", "i_saved_password": "I have securely saved my generated password", "update_password": "Update Password", diff --git a/apps/wallet/pages/[param]/password.tsx b/apps/wallet/pages/[param]/password.tsx index 5901ef418..f1271a191 100644 --- a/apps/wallet/pages/[param]/password.tsx +++ b/apps/wallet/pages/[param]/password.tsx @@ -30,7 +30,23 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { }; }; -export default function PostForm({ metadata }: { metadata: MetadataProps }) { +// New type for derived keys +interface DerivedKeys { + master: string; + owner: string; + active: string; + posting: string; + memo: string; +} + +// Add new types for key display +interface KeyDisplay { + type: 'master' | 'owner' | 'active' | 'posting' | 'memo'; + value: string; + description: string; +} + +export default function PostForm() { const { t } = useTranslation('common_wallet'); const [isKeyGenerated, setIsKeyGenerated] = useState(false); const [loading, setLoading] = useState(false); @@ -43,20 +59,25 @@ export default function PostForm({ metadata }: { metadata: MetadataProps }) { const { username } = useSiteParams(); const changePasswordMutation = useChangePasswordMutation(); const [generatedPassword, setGeneratedPassword] = useState(''); + const [derivedKeys, setDerivedKeys] = useState(null); + const [securityAccepted, setSecurityAccepted] = useState(false); const accountFormSchema = useMemo( () => z.object({ name: z.string().min(2, 'Account name should be longer'), - curr_password: z.string().min(2, { message: 'Required owner key or current password' }), + curr_password: z.string().min(2, { message: 'Required master password' }), genereted_password: z.string().refine((value) => value === generatedPassword, { message: 'Passwords do not match' }), - understand: z.boolean().refine((value) => value === true, { + understand_master: z.boolean().refine((value) => value === true, { message: 'Required' }), saved_password: z.boolean().refine((value) => value === true, { message: 'Required' + }), + understand_security: z.boolean().refine((value) => value === true, { + message: 'Required' }) }), [generatedPassword] @@ -70,7 +91,8 @@ export default function PostForm({ metadata }: { metadata: MetadataProps }) { name: username, curr_password: '', genereted_password: '', - understand: false, + understand_master: false, + understand_security: false, saved_password: false } }); @@ -142,176 +164,276 @@ export default function PostForm({ metadata }: { metadata: MetadataProps }) { const brainKeyData = wax.suggestBrainKey(); const passwordToBeSavedByUser = 'P' + brainKeyData.wifPrivateKey; - const newOwner = wax.getPrivateKeyFromPassword(username, 'owner', passwordToBeSavedByUser); - const newActive = wax.getPrivateKeyFromPassword(username, 'active', passwordToBeSavedByUser); - const newPosting = wax.getPrivateKeyFromPassword(username, 'posting', passwordToBeSavedByUser); - - setPublicKeys({ - active: newActive.associatedPublicKey, - owner: newOwner.associatedPublicKey, - posting: newPosting.associatedPublicKey - }); + const newKeys = { + master: passwordToBeSavedByUser, + owner: wax.getPrivateKeyFromPassword(username, 'owner', passwordToBeSavedByUser).wifPrivateKey, + active: wax.getPrivateKeyFromPassword(username, 'active', passwordToBeSavedByUser).wifPrivateKey, + posting: wax.getPrivateKeyFromPassword(username, 'posting', passwordToBeSavedByUser).wifPrivateKey, + memo: wax.getPrivateKeyFromPassword(username, 'memo', passwordToBeSavedByUser).wifPrivateKey + }; + setDerivedKeys(newKeys); setGeneratedPassword(passwordToBeSavedByUser); setIsKeyGenerated(true); } + // Modify the UI part for displaying generated credentials + const KeyDisplaySection = ({ derivedKeys }: { derivedKeys: DerivedKeys }) => { + const { t } = useTranslation('common_wallet'); + const [showKeys, setShowKeys] = useState(false); + + const keys: KeyDisplay[] = [ + { + type: 'master', + value: derivedKeys.master, + description: t('permissions.master_key.info') + }, + { + type: 'owner', + value: derivedKeys.owner, + description: t('permissions.owner_key.info') + }, + { + type: 'active', + value: derivedKeys.active, + description: t('permissions.active_key.info') + }, + { + type: 'posting', + value: derivedKeys.posting, + description: t('permissions.posting_key.info') + }, + { + type: 'memo', + value: derivedKeys.memo, + description: t('permissions.memo_key.info') + } + ]; + + const downloadKeys = () => { + const content = `HIVE ACCOUNT: ${username} +GENERATED: ${new Date().toISOString()} + +${keys.map((k) => `${k.type.toUpperCase()} KEY:\n${k.value}\n`).join('\n---\n\n')} + +IMPORTANT: +- Save these keys immediately in a secure password manager +- The master password can derive all other keys +- Lost keys cannot be recovered +- Never share your private keys`; + + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `hive-keys-${username}.txt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + }; + + return ( +
+
+

Security Notes

+
    +
  • Save these keys immediately in a secure password manager
  • +
  • The master password can derive all other keys
  • +
  • Lost keys cannot be recovered
  • +
  • Never share your private keys
  • +
+
+ ( + + + { + field.onChange(checked); + setSecurityAccepted(checked as boolean); + }} + /> + + + I understand and accept these security implications + + + )} + /> +
+
+ + {securityAccepted && form.getValues().understand_security && ( + <> +
+

New Account Keys

+
+ + +
+
+ +
+ {keys.map((key) => ( +
+
+ {key.type} Key + +
+
+ {showKeys ? key.value : '••••••••••••••••••••••••••'} +
+
+ ))} +
+ + )} +
+ ); + }; + return ( - <> - - {metadata.tabTitle} - - - - - - -
-
{t('change_password_page.change_password')}
- -

- {t('change_password_page.the_rules.one')} -
- {t('change_password_page.the_rules.second')} -
- {t('change_password_page.the_rules.third')} -
- {t('change_password_page.the_rules.fourth')} -
- {t('change_password_page.the_rules.fifth')} -
- {t('change_password_page.the_rules.sixth')} -
- {t('change_password_page.the_rules.seventh')} -

- -
- - ( - - {t('change_password_page.account_name')} - - - - - - )} - /> - ( - - - {t('change_password_page.current_password')}{' '} - - {t('change_password_page.recover_password')} - - - - - + + +
+
{t('change_password_page.change_password')}
+ + + + ( + + {t('change_password_page.account_name')} + + + + + + )} + /> + ( + + + {t('change_password_page.current_password')}{' '} + + {t('change_password_page.recover_password')} + + + + + + + + )} + /> +
+
{t('change_password_page.generated_password')}
+ {isKeyGenerated && derivedKeys ? ( + + ) : ( + + )} +
+ ( + + {t('change_password_page.re_enter_generate_password')} + + + + + + )} + /> + ( + + + + +
+ {t('change_password_page.understand_that')} {' '} - - )} - /> -
-
- {t('change_password_page.generated_password')} - ({t('change_password_page.new')}) -
- {isKeyGenerated ? ( -
- - {generatedPassword} - -
- {t('change_password_page.backup_password_by_storing_it')} -
- ) : ( - - )} -
- ( - - {t('change_password_page.re_enter_generate_password')} - - - - - - )} - /> - ( - - - - -
- {t('change_password_page.understand_that')} {' '} - -
-
- )} - />{' '} - ( - - - - + + )} + /> + ( + + + + -
- {t('change_password_page.i_saved_password')}{' '} - -
-
- )} - /> - - - -
- - +
+ {t('change_password_page.i_saved_password')}{' '} + +
+
+ )} + /> + + + +
+
); } -- GitLab From 65ef4a0af9a880b10f711838bca2b73d946c7328 Mon Sep 17 00:00:00 2001 From: Efe Date: Mon, 27 Jan 2025 11:14:29 +0100 Subject: [PATCH 2/8] Tidy up static text --- apps/wallet/locales/en/common_wallet.json | 28 ++++++--- apps/wallet/pages/[param]/password.tsx | 70 +++++++++++++++-------- 2 files changed, 64 insertions(+), 34 deletions(-) diff --git a/apps/wallet/locales/en/common_wallet.json b/apps/wallet/locales/en/common_wallet.json index a4a7b7e25..a8623a5c4 100644 --- a/apps/wallet/locales/en/common_wallet.json +++ b/apps/wallet/locales/en/common_wallet.json @@ -331,6 +331,8 @@ "recover_password": "Recover Account", "generated_password": "Generated Password and Keys", "new": "New", + "hide": "Hide", + "show": "Show", "click_to_generate_password": "Click to generate new master password", "re_enter_generate_password": "Re-enter Generated Master Password", "understand_that": "I understand that Hive cannot recover lost passwords", @@ -340,15 +342,23 @@ "account_name_should_be_longer": "Account name should be longer.", "backup_password_by_storing_it": "Back it up by storing in your password manager or a text file", "passwords_do_not_match": "Passwords do not match", - "the_rules": { - "one": "The first rule of Hive is: Do not lose your password.", - "second": "The second rule of Hive is: Do not lose your password.", - "third": "The third rule of Hive is: We cannot recover your password.", - "fourth": "The fourth rule: If you can remember the password, it's not secure.", - "fifth": "The fifth rule: Use only randomly-generated passwords.", - "sixth": "The sixth rule: Do not tell anyone your password.", - "seventh": "The seventh rule: Always back up your password." - } + "key_titles": { + "master": "Master Password", + "owner": "Owner Key", + "active": "Active Key", + "posting": "Posting Key", + "memo": "Memo Key" + }, + "new_keys": "New Keys", + "security_notes": { + "title": "Security Notes", + "m1": "Save these keys immediately in a secure password manager", + "m2": "The master password can derive all other keys", + "m3": "Lost keys cannot be recovered", + "m4": "Never share your private keys", + "confirm_security": "I understand and accept these security implications" + }, + "save_all": "Save All" }, "four_oh_four": { "this_page_does_not_exist": "Sorry! This page does not exist.", diff --git a/apps/wallet/pages/[param]/password.tsx b/apps/wallet/pages/[param]/password.tsx index f1271a191..e31760b6a 100644 --- a/apps/wallet/pages/[param]/password.tsx +++ b/apps/wallet/pages/[param]/password.tsx @@ -18,7 +18,7 @@ import { useChangePasswordMutation } from '@/wallet/components/hooks/use-change- import { handleError } from '@ui/lib/handle-error'; import { Icons } from '@ui/components/icons'; import { hiveChainService } from '@transaction/lib/hive-chain-service'; -import Head from 'next/head'; +import { cn } from '@ui/lib/utils'; export const getServerSideProps: GetServerSideProps = async (ctx) => { const username = ctx.params?.param as string; @@ -51,16 +51,13 @@ export default function PostForm() { const [isKeyGenerated, setIsKeyGenerated] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [publicKeys, setPublicKeys] = useState<{ - active: string; - owner: string; - posting: string; - }>(); + const { username } = useSiteParams(); const changePasswordMutation = useChangePasswordMutation(); const [generatedPassword, setGeneratedPassword] = useState(''); const [derivedKeys, setDerivedKeys] = useState(null); const [securityAccepted, setSecurityAccepted] = useState(false); + const [copiedStates, setCopiedStates] = useState>({}); const accountFormSchema = useMemo( () => @@ -233,27 +230,33 @@ IMPORTANT: URL.revokeObjectURL(url); }; - const copyToClipboard = (text: string) => { + const copyToClipboard = (text: string, keyType: string) => { navigator.clipboard.writeText(text); + setCopiedStates((prev) => ({ ...prev, [keyType]: true })); + setTimeout(() => { + setCopiedStates((prev) => ({ ...prev, [keyType]: false })); + }, 2000); }; return (
-

Security Notes

+

+ {t('change_password_page.security_notes.title')} +

    -
  • Save these keys immediately in a secure password manager
  • -
  • The master password can derive all other keys
  • -
  • Lost keys cannot be recovered
  • -
  • Never share your private keys
  • +
  • {t('change_password_page.security_notes.m1')}
  • +
  • {t('change_password_page.security_notes.m2')}
  • +
  • {t('change_password_page.security_notes.m3')}
  • +
  • {t('change_password_page.security_notes.m4')}
( - - + + { @@ -262,9 +265,12 @@ IMPORTANT: }} /> - - I understand and accept these security implications - +
+ + {t('change_password_page.security_notes.confirm_security')} + + +
)} /> @@ -274,14 +280,18 @@ IMPORTANT: {securityAccepted && form.getValues().understand_security && ( <>
-

New Account Keys

-
+

{t('change_password_page.new_keys')}

+
@@ -290,14 +300,24 @@ IMPORTANT: {keys.map((key) => (
- {key.type} Key + + {t(`change_password_page.key_titles.${key.type}`)} +
-- GitLab From fdb06159977ef0cff32bcda6df7afb6163eed9b6 Mon Sep 17 00:00:00 2001 From: Efe Date: Mon, 27 Jan 2025 11:22:41 +0100 Subject: [PATCH 3/8] Clean up key view --- apps/wallet/pages/[param]/password.tsx | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/apps/wallet/pages/[param]/password.tsx b/apps/wallet/pages/[param]/password.tsx index e31760b6a..450aa0fbb 100644 --- a/apps/wallet/pages/[param]/password.tsx +++ b/apps/wallet/pages/[param]/password.tsx @@ -178,6 +178,7 @@ export default function PostForm() { const KeyDisplaySection = ({ derivedKeys }: { derivedKeys: DerivedKeys }) => { const { t } = useTranslation('common_wallet'); const [showKeys, setShowKeys] = useState(false); + const [copiedStates, setCopiedStates] = useState>({}); const keys: KeyDisplay[] = [ { @@ -207,6 +208,14 @@ export default function PostForm() { } ]; + const copyToClipboard = (text: string, keyType: string) => { + navigator.clipboard.writeText(text); + setCopiedStates((prev) => ({ ...prev, [keyType]: true })); + setTimeout(() => { + setCopiedStates((prev) => ({ ...prev, [keyType]: false })); + }, 2000); + }; + const downloadKeys = () => { const content = `HIVE ACCOUNT: ${username} GENERATED: ${new Date().toISOString()} @@ -230,14 +239,6 @@ IMPORTANT: URL.revokeObjectURL(url); }; - const copyToClipboard = (text: string, keyType: string) => { - navigator.clipboard.writeText(text); - setCopiedStates((prev) => ({ ...prev, [keyType]: true })); - setTimeout(() => { - setCopiedStates((prev) => ({ ...prev, [keyType]: false })); - }, 2000); - }; - return (
@@ -283,11 +284,7 @@ IMPORTANT:

{t('change_password_page.new_keys')}

- @@ -381,7 +381,7 @@ IMPORTANT: ) : (
); } diff --git a/packages/smart-signer/components/auth/methods/safestorage-key-update.tsx b/packages/smart-signer/components/auth/methods/safestorage-key-update.tsx new file mode 100644 index 000000000..36a327022 --- /dev/null +++ b/packages/smart-signer/components/auth/methods/safestorage-key-update.tsx @@ -0,0 +1,372 @@ +/* Sign-in with safe storage (use beekeeper wallet through hb-auth) */ +import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'next-i18next'; +import { AuthUser, AuthorizationError, OnlineClient } from '@hiveio/hb-auth'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { username } from '@smart-signer/lib/auth/utils'; +import { hbauthService } from '@smart-signer/lib/hbauth-service'; +import { Icons } from '@hive/ui/components/icons'; +import { + Input, + RadioGroup, + RadioGroupItem, + Label, + Button, + Separator, + Form, + FormField, + FormItem, + FormControl, + FormMessage, + Checkbox, + TooltipProvider, + Tooltip, + TooltipTrigger, + TooltipContent +} from '@hive/ui'; +import Step from '../step'; +import { Steps } from '../form'; +import { KeyType, LoginType } from '@smart-signer/types/common'; +import { TFunction } from 'i18next'; +import { validateWifKey } from '@smart-signer/lib/validators/validate-wif-key'; +import { KeyAuthorityType } from '@smart-signer/lib/utils'; +import { toast } from '@ui/components/hooks/use-toast'; +import { logger } from '@ui/lib/logger'; + +export interface SafeStorageKeyUpdateProps { + onSetStep: (step: Steps) => void; + i18nNamespace: string; + preferredKeyTypes: KeyType[]; + username: string; + onUsernameChange: (username: string) => void; +} + +function getFormSchema(t: TFunction<'smart-signer', undefined>) { + return z + .object({ + username, + password: z.string().min(1, { + message: t('login_form.zod_error.password_required') + }), + wif: z.string().min(1, { + message: t('login_form.zod_error.invalid_wif') + }), + keyType: z.nativeEnum(KeyType, { + invalid_type_error: t('login_form.zod_error.invalid_keytype'), + required_error: t('login_form.zod_error.keytype_required') + }), + isStrict: z.boolean().default(false) + }) + .superRefine((val, ctx) => { + const result = validateWifKey(val.wif, t); + if (result) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: result, + path: ['wif'], + fatal: true + }); + return z.NEVER; + } + return true; + }); +} + +export type SafeStorageKeyUpdateRef = { cancel: () => Promise }; + +type SafeStorageKeyUpdateForm = z.infer>; + +const SafeStorageKeyUpdate = forwardRef( + ({ onSetStep, preferredKeyTypes, i18nNamespace, username, onUsernameChange }, ref) => { + useImperativeHandle(ref, () => ({ + async cancel() { + // No need to cancel anything for key update + } + })); + + const authClient = useRef(); + const { t } = useTranslation(i18nNamespace); + const [loading, setLoading] = useState(undefined); + const [error, setError] = useState(null); + const [registeredUser, setRegisteredUser] = useState(null); + const [availableKeyTypes, setAvailableKeyTypes] = useState([]); + const [updateSuccess, setUpdateSuccess] = useState(false); + + const form = useForm({ + mode: 'onChange', + resolver: zodResolver(getFormSchema(t)), + defaultValues: { + username, + password: '', + wif: '', + keyType: preferredKeyTypes[0], + isStrict: false + } + }); + + // Check if user exists in hbauth and get available key types + useEffect(() => { + (async () => { + try { + setLoading(true); + setError(null); + + // Get the hbauth client + authClient.current = await hbauthService.getOnlineClient(); + + // Check if user exists in hbauth + const user = await authClient.current.getRegisteredUserByUsername(username); + + if (user) { + setRegisteredUser(user); + + // Filter preferred key types to only those registered in hbauth + const availableTypes = preferredKeyTypes.filter((keyType) => + user.registeredKeyTypes.includes(keyType as KeyAuthorityType) + ); + + setAvailableKeyTypes(availableTypes.length > 0 ? availableTypes : preferredKeyTypes); + + // Set the first available key type as default + if (availableTypes.length > 0) { + form.setValue('keyType', availableTypes[0]); + } + } else { + setError(t('login_form.signin_safe_storage.user_not_found')); + } + } catch (error) { + setError((error as AuthorizationError).message); + } finally { + setLoading(false); + } + })(); + }, [username, preferredKeyTypes, form, t]); + + // Handle key update + async function onUpdateKey(values: SafeStorageKeyUpdateForm) { + const { username, password, wif, keyType, isStrict } = values; + try { + setLoading(true); + setError(null); + + if (!registeredUser) { + setError(t('login_form.signin_safe_storage.user_not_found')); + return; + } + + // Try to unlock the wallet with the provided password + try { + await authClient.current?.unlock(username, password); + logger.info('Wallet unlocked successfully for user: %s', username); + } catch (unlockError) { + // If unlock fails, we'll continue with the key update process + logger.warn('Failed to unlock wallet for user: %s, continuing with key update', username); + } + + // Invalidate the existing key + await authClient.current?.invalidateExistingKey(username, keyType as KeyAuthorityType); + + // Import the new key + await authClient.current?.register(username, password, wif, keyType as KeyAuthorityType, isStrict); + + setUpdateSuccess(true); + + toast({ + title: t('login_form.signin_safe_storage.key_updated_title'), + description: t('login_form.signin_safe_storage.key_updated_description'), + variant: 'success' + }); + + logger.info('Key updated successfully for user: %s, keyType: %s', username, keyType); + + // Reset form after successful update + form.reset(); + + // Go back to previous step after a short delay + setTimeout(() => { + onSetStep(Steps.SAFE_STORAGE_LOGIN); + }, 2000); + } catch (error) { + if (typeof error === 'string') { + setError(t(error)); + } else { + setError(t('login_form.zod_error.invalid_wif')); + } + setLoading(false); + } + } + + if (loading === undefined) return ; + + return ( + +
+ {registeredUser + ? t('login_form.signin_safe_storage.update_key_description', { + username, + keyType: form.getValues().keyType + }) + : t('login_form.signin_safe_storage.user_not_found')} +
+ {error &&
{error}
} + {updateSuccess && ( +
+ {t('login_form.signin_safe_storage.key_updated_success')} +
+ )} +
+ } + loading={loading} + > +
+ + ( + + + + + + + )} + /> + + {availableKeyTypes.length > 1 && ( + ( + + + + {availableKeyTypes.map((type) => { + return ( + + + + + + + ); + })} + + + + + )} + /> + )} + + ( + + + + + + + )} + /> + + ( + + + + + + + )} + /> + + ( +
+ { + field.onChange(isStrict); + }} + {...field} + checked={field.value} + value={field.value as unknown as string} + /> + + + + + + + + + {t('login_form.signin_safe_storage.strict_mode_tooltip')} + + + +
+ )} + /> + +
+ + +
+ + + + ); + } +); + +SafeStorageKeyUpdate.displayName = 'SafeStorageKeyUpdate'; + +export default SafeStorageKeyUpdate; diff --git a/packages/smart-signer/components/auth/methods/safestorage.tsx b/packages/smart-signer/components/auth/methods/safestorage.tsx index 12551e49d..12317341f 100644 --- a/packages/smart-signer/components/auth/methods/safestorage.tsx +++ b/packages/smart-signer/components/auth/methods/safestorage.tsx @@ -35,6 +35,7 @@ import { Steps } from '../form'; import { KeyType, LoginType } from '@smart-signer/types/common'; import { TFunction } from 'i18next'; import { validateWifKey } from '@smart-signer/lib/validators/validate-wif-key'; +import { useRouter } from 'next/router'; function getFormSchema(t: TFunction<'smart-signer', undefined>) { return z @@ -112,6 +113,7 @@ const SafeStorage = forwardRef( strict: true } }); + const router = useRouter(); async function onSave(values: SafeStorageForm) { const { username, password, wif, keyType, strict } = values; @@ -135,7 +137,11 @@ const SafeStorage = forwardRef( await authClient.current?.authenticate(username, password, keyType); await finalize(values); } catch (error) { - setError((error as AuthorizationError).message); + const authError = error as AuthorizationError; + if (authError.message.includes('Not authorized') || authError.message.includes('Authentication failed')) { + onSetStep(Steps.SAFE_STORAGE_KEY_UPDATE); + } + setError(authError.message); setLoading(false); } } @@ -417,6 +423,13 @@ const SafeStorage = forwardRef( )} /> )} + {authUsers?.length > 0 && ( +
+ onSetStep(Steps.SAFE_STORAGE_KEY_UPDATE)} className="max-w-max cursor-pointer text-xs text-destructive hover:opacity-80 active:opacity-60"> + {t('login_form.signin_safe_storage.key_update')} + +
+ )} {/* Show this for new user, otherwise show unlock then authorize */}
{userFound ? ( diff --git a/packages/smart-signer/locales/en/smart-signer.json b/packages/smart-signer/locales/en/smart-signer.json index d5288562a..19fa5177c 100644 --- a/packages/smart-signer/locales/en/smart-signer.json +++ b/packages/smart-signer/locales/en/smart-signer.json @@ -88,7 +88,18 @@ "button_sign_auth": "Sign auth tx", "button_signin_unlocked": "Sign in with signed tx", "strict_mode": "Direct Authority Mode", - "strict_mode_tooltip": "When enabled, the app will only allow adding keys with account's own authority. If you want to add keys with other authority, please disable this mode." + "strict_mode_tooltip": "When enabled, the app will only allow adding keys with account's own authority. If you want to add keys with other authority, please disable this mode.", + "key_update": "Update existing saved key", + "update_key_title": "Update Key in Safe Storage", + "update_key_description": "Update {{keyType}} key for user {{username}}", + "update_key_button": "Update Key", + "key_updated_title": "Key Updated", + "key_updated_description": "Your key has been updated successfully", + "key_updated_success": "Key updated successfully! Now you can sign in again.", + "user_not_found": "User not found in safe storage", + "back": "Back", + "authenticated_title": "Authentication Successful", + "authenticated_description": "You have been authenticated successfully" }, "zod_error": { "password_length": "Password length should be more than 6 characters", @@ -96,7 +107,9 @@ "keytype_required": "keyType is required", "invalid_wif": "Invalid WIF key", "no_wif": "No WIF key from user", - "no_wif_key": "No WIF key" + "no_wif_key": "No WIF key", + "password_required": "Password is required", + "wif_required": "WIF key is required" }, "signin_with_keychain": "Hive Keychain extension", "signin_with_wif": "Sign in with WIF (Legacy)", -- GitLab