mirror of
https://github.com/logos-messaging/rln.waku.org.git
synced 2026-01-08 00:53:07 +00:00
feat: credentials can be used with an alias
This commit is contained in:
parent
de16452fa9
commit
6022885144
@ -7,13 +7,14 @@ import { readKeystoreFromFile, saveKeystoreCredentialToFile } from '../../../uti
|
|||||||
import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln';
|
import { DecryptedCredentials, MembershipInfo, MembershipState } from '@waku/rln';
|
||||||
import { TerminalWindow } from '../../ui/terminal-window';
|
import { TerminalWindow } from '../../ui/terminal-window';
|
||||||
import { Button } from '../../ui/button';
|
import { Button } from '../../ui/button';
|
||||||
import { Copy, Eye, Download, Trash2, ArrowDownToLine } from 'lucide-react';
|
import { Copy, Eye, Download, Trash2, ArrowDownToLine, Pencil, Check, X } from 'lucide-react';
|
||||||
import { KeystoreExporter } from '../../KeystoreExporter';
|
import { KeystoreExporter } from '../../KeystoreExporter';
|
||||||
import { keystoreManagement, type ContentSegment } from '../../../content/index';
|
import { keystoreManagement, type ContentSegment } from '../../../content/index';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { CredentialDetails } from '@/components/CredentialDetails';
|
import { CredentialDetails } from '@/components/CredentialDetails';
|
||||||
import { MembershipDetails } from '@/components/MembershipDetails';
|
import { MembershipDetails } from '@/components/MembershipDetails';
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
interface ExtendedMembershipInfo extends Omit<MembershipInfo, 'state'> {
|
interface ExtendedMembershipInfo extends Omit<MembershipInfo, 'state'> {
|
||||||
address: string;
|
address: string;
|
||||||
@ -40,6 +41,8 @@ export function KeystoreManagement({ tabId: _tabId }: KeystoreManagementProps) {
|
|||||||
const {
|
const {
|
||||||
hasStoredCredentials,
|
hasStoredCredentials,
|
||||||
storedCredentialsHashes,
|
storedCredentialsHashes,
|
||||||
|
credentialAliases,
|
||||||
|
setCredentialAlias,
|
||||||
error,
|
error,
|
||||||
exportCredential,
|
exportCredential,
|
||||||
importKeystore,
|
importKeystore,
|
||||||
@ -59,6 +62,8 @@ export function KeystoreManagement({ tabId: _tabId }: KeystoreManagementProps) {
|
|||||||
const [isDecrypting, setIsDecrypting] = useState(false);
|
const [isDecrypting, setIsDecrypting] = useState(false);
|
||||||
const [copiedHash, setCopiedHash] = useState<string | null>(null);
|
const [copiedHash, setCopiedHash] = useState<string | null>(null);
|
||||||
const [membershipInfo, setMembershipInfo] = useState<ExtendedMembershipInfo | null>(null);
|
const [membershipInfo, setMembershipInfo] = useState<ExtendedMembershipInfo | null>(null);
|
||||||
|
const [editingAliasHash, setEditingAliasHash] = useState<string | null>(null);
|
||||||
|
const [currentAliasInput, setCurrentAliasInput] = useState<string>('');
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -239,146 +244,189 @@ export function KeystoreManagement({ tabId: _tabId }: KeystoreManagementProps) {
|
|||||||
|
|
||||||
{hasStoredCredentials ? (
|
{hasStoredCredentials ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{storedCredentialsHashes.map((hash) => (
|
{storedCredentialsHashes.map((hash) => {
|
||||||
<div
|
const currentAlias = credentialAliases[hash] || '';
|
||||||
key={hash}
|
const displayValue = currentAlias || `${hash.substring(0, 6)}...${hash.substring(hash.length - 4)}`;
|
||||||
className="p-4 rounded-md border border-terminal-border bg-terminal-background/30 hover:border-terminal-border/80 transition-colors"
|
const isEditing = editingAliasHash === hash;
|
||||||
>
|
|
||||||
<div className="flex flex-col space-y-3">
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div
|
||||||
<div className="flex items-center space-x-2">
|
key={hash}
|
||||||
<code className="text-sm text-muted-foreground font-mono">
|
className="p-4 rounded-md border border-terminal-border bg-terminal-background/30 hover:border-terminal-border/80 transition-colors"
|
||||||
{hash.slice(0, 10)}...{hash.slice(-6)}
|
>
|
||||||
</code>
|
<div className="flex flex-col space-y-3">
|
||||||
<Button
|
<div className="flex items-center justify-between">
|
||||||
variant="ghost"
|
{/* Display Alias/Hash or Edit Input */}
|
||||||
size="sm"
|
{isEditing ? (
|
||||||
className={`h-6 w-6 p-0 ${copiedHash === hash ? 'text-success-DEFAULT' : 'text-muted-foreground hover:text-primary'}`}
|
<div className="flex items-center space-x-2 flex-grow mr-2">
|
||||||
onClick={() => copyToClipboard(hash)}
|
<Input
|
||||||
>
|
type="text"
|
||||||
<Copy className="h-3.5 w-3.5" />
|
value={currentAliasInput}
|
||||||
</Button>
|
onChange={(e) => setCurrentAliasInput(e.target.value)}
|
||||||
{copiedHash === hash && (
|
placeholder="Enter alias..."
|
||||||
<span className="text-xs text-success-DEFAULT">Copied!</span>
|
className="h-8 text-sm flex-grow"
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setViewingCredential(hash === viewingCredential ? null : hash);
|
|
||||||
setSelectedCredential(null);
|
|
||||||
setViewPassword('');
|
|
||||||
setDecryptedInfo(null);
|
|
||||||
}}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="text-accent hover:text-accent hover:border-accent flex items-center gap-1 py-1"
|
|
||||||
>
|
|
||||||
<Eye className="w-3 h-3" />
|
|
||||||
<span>{keystoreManagement.buttons.view}</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedCredential(hash === selectedCredential ? null : hash);
|
|
||||||
setViewingCredential(null);
|
|
||||||
setExportPassword('');
|
|
||||||
}}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="text-primary hover:text-primary hover:border-primary flex items-center gap-1 py-1"
|
|
||||||
>
|
|
||||||
<Download className="w-3 h-3" />
|
|
||||||
<span>Export</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => handleRemoveCredential(hash)}
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="text-destructive hover:text-destructive hover:border-destructive flex items-center gap-1 py-1"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-3 h-3" />
|
|
||||||
<span>Remove</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* View Credential Section */}
|
|
||||||
{viewingCredential === hash && (
|
|
||||||
<div className="mt-3 space-y-3 border-t border-terminal-border/40 pt-3 animate-in fade-in-50 duration-300">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
value={viewPassword}
|
|
||||||
onChange={(e) => setViewPassword(e.target.value)}
|
|
||||||
placeholder="Enter credential password"
|
|
||||||
className="flex-1 px-3 py-2 border border-terminal-border rounded-md bg-terminal-background text-foreground font-mono focus:ring-1 focus:ring-accent focus:border-accent text-sm"
|
|
||||||
disabled={isDecrypting}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={() => handleViewCredential(hash)}
|
|
||||||
disabled={!viewPassword || isDecrypting}
|
|
||||||
variant="terminal"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{isDecrypting ? 'Decrypting...' : 'Decrypt'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Decrypted Information Display */}
|
|
||||||
{decryptedInfo && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<CredentialDetails
|
|
||||||
decryptedInfo={decryptedInfo}
|
|
||||||
copyToClipboard={copyToClipboard}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{membershipInfo && (
|
|
||||||
<div className="flex flex-col border-t border-terminal-border/20 pt-2">
|
|
||||||
<MembershipDetails
|
|
||||||
membershipInfo={membershipInfo}
|
|
||||||
copyToClipboard={copyToClipboard}
|
|
||||||
hash={hash}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setDecryptedInfo(null)}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="icon"
|
||||||
className="mt-2 text-xs text-muted-foreground hover:text-accent"
|
className="h-7 w-7 text-primary hover:text-primary"
|
||||||
>
|
onClick={() => {
|
||||||
Hide Details
|
setCredentialAlias(hash, currentAliasInput);
|
||||||
</Button>
|
setEditingAliasHash(null);
|
||||||
|
setCurrentAliasInput('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingAliasHash(null);
|
||||||
|
setCurrentAliasInput('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-2 flex-grow">
|
||||||
|
<code className={`text-sm font-mono ${currentAlias ? 'text-foreground' : 'text-muted-foreground'}`}>
|
||||||
|
{displayValue}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-5 w-5 p-0 ml-1 text-muted-foreground hover:text-accent"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingAliasHash(hash);
|
||||||
|
setCurrentAliasInput(currentAlias); // Pre-fill input if alias exists
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pencil className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-5 w-5 p-0 text-muted-foreground hover:text-accent"
|
||||||
|
onClick={() => copyToClipboard(hash)}
|
||||||
|
title="Copy Full Hash"
|
||||||
|
>
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
{copiedHash === hash && <span className="text-xs ml-1">Copied!</span>}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Action Buttons (View, Export, Remove) - only show if not editing */}
|
||||||
|
{!isEditing && (
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-muted-foreground hover:text-accent"
|
||||||
|
onClick={() => {
|
||||||
|
setViewingCredential(hash);
|
||||||
|
// Optionally clear previous details or require password again
|
||||||
|
setDecryptedInfo(null);
|
||||||
|
setMembershipInfo(null);
|
||||||
|
setViewPassword(''); // Clear password for safety
|
||||||
|
}}
|
||||||
|
title="View Details"
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-muted-foreground hover:text-accent"
|
||||||
|
onClick={() => setSelectedCredential(hash)} // Set selected for export modal
|
||||||
|
title="Export Credential"
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
||||||
|
onClick={() => handleRemoveCredential(hash)}
|
||||||
|
title="Remove Credential"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{/* Password input for View Credential */}
|
||||||
{/* Export Credential Section */}
|
{viewingCredential === hash && !decryptedInfo && (
|
||||||
{selectedCredential === hash && (
|
<div className="flex items-center space-x-2 pt-2 border-t border-terminal-border/20">
|
||||||
<div className="mt-3 space-y-3 border-t border-terminal-border/40 pt-3 animate-in fade-in-50 duration-300">
|
<Input
|
||||||
<input
|
type="password"
|
||||||
type="password"
|
value={viewPassword}
|
||||||
value={exportPassword}
|
onChange={(e) => setViewPassword(e.target.value)}
|
||||||
onChange={(e) => setExportPassword(e.target.value)}
|
placeholder="Enter password to view details"
|
||||||
placeholder="Enter keystore password"
|
className="flex-grow h-8 text-sm"
|
||||||
className="w-full px-3 py-2 border border-terminal-border rounded-md bg-terminal-background text-foreground font-mono focus:ring-1 focus:ring-primary focus:border-primary text-sm"
|
/>
|
||||||
/>
|
<Button
|
||||||
<Button
|
variant="terminal"
|
||||||
onClick={() => handleExportKeystoreCredential(hash)}
|
size="sm"
|
||||||
variant="default"
|
onClick={() => handleViewCredential(hash)}
|
||||||
size="sm"
|
disabled={!viewPassword || isDecrypting}
|
||||||
className="w-full font-mono"
|
>
|
||||||
disabled={!exportPassword}
|
{isDecrypting ? 'Decrypting...' : 'View'}
|
||||||
>
|
</Button>
|
||||||
<Download className="w-3 h-3 mr-1" />
|
<Button
|
||||||
Export Credential
|
variant="ghost"
|
||||||
</Button>
|
size="sm"
|
||||||
</div>
|
onClick={() => setViewingCredential(null)} // Cancel view
|
||||||
)}
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Password input for Export Credential */}
|
||||||
|
{selectedCredential === hash && (
|
||||||
|
<div className="flex items-center space-x-2 pt-2 border-t border-terminal-border/20">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={exportPassword}
|
||||||
|
onChange={(e) => setExportPassword(e.target.value)}
|
||||||
|
placeholder="Enter password to export"
|
||||||
|
className="flex-grow h-8 text-sm"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="terminal"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleExportKeystoreCredential(hash)}
|
||||||
|
disabled={!exportPassword}
|
||||||
|
>
|
||||||
|
Confirm Export
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSelectedCredential(null)} // Cancel export
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Decrypted Credential Details */}
|
||||||
|
{viewingCredential === hash && decryptedInfo && (
|
||||||
|
<CredentialDetails decryptedInfo={decryptedInfo} copyToClipboard={copyToClipboard} />
|
||||||
|
)}
|
||||||
|
{/* Membership Details */}
|
||||||
|
{viewingCredential === hash && membershipInfo && (
|
||||||
|
<MembershipDetails membershipInfo={membershipInfo} copyToClipboard={copyToClipboard} hash={hash} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-sm text-muted-foreground font-mono bg-terminal-background/30 p-4 border border-terminal-border/50 rounded-md text-center">
|
<div className="text-sm text-muted-foreground font-mono bg-terminal-background/30 p-4 border border-terminal-border/50 rounded-md text-center">
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { createContext, useContext, useState, useEffect, ReactNode } from 'react
|
|||||||
import { Keystore, KeystoreEntity } from '@waku/rln';
|
import { Keystore, KeystoreEntity } from '@waku/rln';
|
||||||
|
|
||||||
export const LOCAL_STORAGE_KEYSTORE_KEY = 'waku-rln-keystore';
|
export const LOCAL_STORAGE_KEYSTORE_KEY = 'waku-rln-keystore';
|
||||||
|
export const LOCAL_STORAGE_ALIASES_KEY = 'waku-rln-keystore-aliases';
|
||||||
|
|
||||||
interface KeystoreContextType {
|
interface KeystoreContextType {
|
||||||
keystore: Keystore | null;
|
keystore: Keystore | null;
|
||||||
@ -11,6 +12,7 @@ interface KeystoreContextType {
|
|||||||
error: string | null;
|
error: string | null;
|
||||||
hasStoredCredentials: boolean;
|
hasStoredCredentials: boolean;
|
||||||
storedCredentialsHashes: string[];
|
storedCredentialsHashes: string[];
|
||||||
|
credentialAliases: { [hash: string]: string };
|
||||||
decryptedCredentials: KeystoreEntity | null;
|
decryptedCredentials: KeystoreEntity | null;
|
||||||
hideCredentials: () => void;
|
hideCredentials: () => void;
|
||||||
saveCredentials: (credentials: KeystoreEntity, password: string) => Promise<string>;
|
saveCredentials: (credentials: KeystoreEntity, password: string) => Promise<string>;
|
||||||
@ -19,6 +21,7 @@ interface KeystoreContextType {
|
|||||||
importKeystore: (keystore: Keystore) => boolean;
|
importKeystore: (keystore: Keystore) => boolean;
|
||||||
removeCredential: (hash: string) => void;
|
removeCredential: (hash: string) => void;
|
||||||
getDecryptedCredential: (hash: string, password: string) => Promise<KeystoreEntity | null>;
|
getDecryptedCredential: (hash: string, password: string) => Promise<KeystoreEntity | null>;
|
||||||
|
setCredentialAlias: (hash: string, alias: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeystoreContext = createContext<KeystoreContextType | undefined>(undefined);
|
const KeystoreContext = createContext<KeystoreContextType | undefined>(undefined);
|
||||||
@ -28,12 +31,14 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [storedCredentialsHashes, setStoredCredentialsHashes] = useState<string[]>([]);
|
const [storedCredentialsHashes, setStoredCredentialsHashes] = useState<string[]>([]);
|
||||||
|
const [credentialAliases, setCredentialAliases] = useState<{ [hash: string]: string }>({});
|
||||||
const [decryptedCredentials, setDecryptedCredentials] = useState<KeystoreEntity | null>(null);
|
const [decryptedCredentials, setDecryptedCredentials] = useState<KeystoreEntity | null>(null);
|
||||||
|
|
||||||
// Initialize keystore
|
// Initialize keystore and aliases
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const storedKeystore = localStorage.getItem(LOCAL_STORAGE_KEYSTORE_KEY);
|
const storedKeystore = localStorage.getItem(LOCAL_STORAGE_KEYSTORE_KEY);
|
||||||
|
const storedAliases = localStorage.getItem(LOCAL_STORAGE_ALIASES_KEY);
|
||||||
let keystoreInstance: Keystore;
|
let keystoreInstance: Keystore;
|
||||||
|
|
||||||
if (storedKeystore) {
|
if (storedKeystore) {
|
||||||
@ -49,6 +54,18 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
setKeystore(keystoreInstance);
|
setKeystore(keystoreInstance);
|
||||||
setStoredCredentialsHashes(keystoreInstance.keys());
|
setStoredCredentialsHashes(keystoreInstance.keys());
|
||||||
|
|
||||||
|
if (storedAliases) {
|
||||||
|
try {
|
||||||
|
setCredentialAliases(JSON.parse(storedAliases));
|
||||||
|
} catch (aliasErr) {
|
||||||
|
console.error("Error parsing aliases from localStorage:", aliasErr);
|
||||||
|
setCredentialAliases({});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCredentialAliases({});
|
||||||
|
}
|
||||||
|
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error initializing keystore:", err);
|
console.error("Error initializing keystore:", err);
|
||||||
@ -67,6 +84,17 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, [keystore, isInitialized]);
|
}, [keystore, isInitialized]);
|
||||||
|
|
||||||
|
// Save aliases to localStorage whenever they change
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInitialized) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(credentialAliases));
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Could not save aliases to localStorage:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [credentialAliases, isInitialized]);
|
||||||
|
|
||||||
const saveCredentials = async (credentials: KeystoreEntity, password: string): Promise<string> => {
|
const saveCredentials = async (credentials: KeystoreEntity, password: string): Promise<string> => {
|
||||||
if (!keystore) {
|
if (!keystore) {
|
||||||
throw new Error("Keystore not initialized");
|
throw new Error("Keystore not initialized");
|
||||||
@ -163,11 +191,16 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const importKeystore = (keystore: Keystore): boolean => {
|
const importKeystore = (importedKeystore: Keystore): boolean => {
|
||||||
try {
|
try {
|
||||||
setKeystore(keystore);
|
const newAliases = {};
|
||||||
setStoredCredentialsHashes(keystore.keys());
|
setCredentialAliases(newAliases);
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, keystore.toString());
|
localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(newAliases));
|
||||||
|
|
||||||
|
setKeystore(importedKeystore);
|
||||||
|
setStoredCredentialsHashes(importedKeystore.keys());
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, importedKeystore.toString());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error importing keystore:", err);
|
console.error("Error importing keystore:", err);
|
||||||
@ -182,8 +215,24 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keystore.removeCredential(hash);
|
keystore.removeCredential(hash);
|
||||||
setStoredCredentialsHashes(keystore.keys());
|
const remainingHashes = keystore.keys();
|
||||||
|
setStoredCredentialsHashes(remainingHashes);
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, keystore.toString());
|
localStorage.setItem(LOCAL_STORAGE_KEYSTORE_KEY, keystore.toString());
|
||||||
|
|
||||||
|
setCredentialAliases(prev => {
|
||||||
|
const newAliases = { ...prev };
|
||||||
|
delete newAliases[hash];
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(newAliases));
|
||||||
|
return newAliases;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCredentialAlias = (hash: string, alias: string) => {
|
||||||
|
setCredentialAliases(prev => {
|
||||||
|
const newAliases = { ...prev, [hash]: alias };
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_ALIASES_KEY, JSON.stringify(newAliases));
|
||||||
|
return newAliases;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextValue: KeystoreContextType = {
|
const contextValue: KeystoreContextType = {
|
||||||
@ -192,6 +241,7 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
error,
|
error,
|
||||||
hasStoredCredentials: storedCredentialsHashes.length > 0,
|
hasStoredCredentials: storedCredentialsHashes.length > 0,
|
||||||
storedCredentialsHashes,
|
storedCredentialsHashes,
|
||||||
|
credentialAliases,
|
||||||
saveCredentials,
|
saveCredentials,
|
||||||
exportCredential,
|
exportCredential,
|
||||||
exportEntireKeystore,
|
exportEntireKeystore,
|
||||||
@ -199,6 +249,7 @@ export function KeystoreProvider({ children }: { children: ReactNode }) {
|
|||||||
removeCredential,
|
removeCredential,
|
||||||
getDecryptedCredential,
|
getDecryptedCredential,
|
||||||
decryptedCredentials,
|
decryptedCredentials,
|
||||||
|
setCredentialAlias,
|
||||||
hideCredentials: () => {},
|
hideCredentials: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user