## Core Identity Management - Add secure key generation and import functionality - Implement AES-GCM encryption with PBKDF2 key derivation - Create password-protected identity storage - Add browser-compatible crypto utilities (no Buffer dependency) ## User Interface - Build identity management dialog with tabs for setup and profile - Add navbar integration with user dropdown and mobile support - Create password unlock dialog for encrypted identities - Integrate vue-sonner for toast notifications ## Nostr Protocol Integration - Implement event creation (notes, reactions, profiles, contacts) - Add reply thread detection and engagement metrics - Create social interaction composables for publishing - Support multi-relay publishing with failure handling - Add profile fetching and caching system ## Security Features - Web Crypto API with 100k PBKDF2 iterations - Secure random salt and IV generation - Automatic password prompts for encrypted storage - Legacy support for unencrypted identities ## Technical Improvements - Replace all Buffer usage with browser-native APIs - Add comprehensive error handling and validation - Implement reactive state management with Vue composables - Create reusable crypto utility functions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
193 lines
No EOL
5 KiB
TypeScript
193 lines
No EOL
5 KiB
TypeScript
import { ref, computed } from 'vue'
|
|
import { IdentityManager, type NostrIdentity, type NostrProfile } from '@/lib/nostr/identity'
|
|
|
|
const currentIdentity = ref<NostrIdentity | null>(null)
|
|
const currentProfile = ref<NostrProfile | null>(null)
|
|
const isAuthenticated = computed(() => !!currentIdentity.value)
|
|
|
|
export function useIdentity() {
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
/**
|
|
* Initialize identity on app start
|
|
*/
|
|
async function initialize(password?: string): Promise<void> {
|
|
try {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
const identity = await IdentityManager.loadIdentity(password)
|
|
if (identity) {
|
|
currentIdentity.value = identity
|
|
currentProfile.value = IdentityManager.loadProfile()
|
|
}
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : 'Failed to initialize identity'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate new identity
|
|
*/
|
|
async function generateNewIdentity(password?: string): Promise<NostrIdentity> {
|
|
try {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
const identity = IdentityManager.generateIdentity()
|
|
await IdentityManager.saveIdentity(identity, password)
|
|
|
|
currentIdentity.value = identity
|
|
currentProfile.value = null
|
|
|
|
return identity
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : 'Failed to generate identity'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import existing identity
|
|
*/
|
|
async function importIdentity(privateKey: string, password?: string): Promise<NostrIdentity> {
|
|
try {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
const identity = IdentityManager.importIdentity(privateKey)
|
|
await IdentityManager.saveIdentity(identity, password)
|
|
|
|
currentIdentity.value = identity
|
|
currentProfile.value = IdentityManager.loadProfile()
|
|
|
|
return identity
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : 'Failed to import identity'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update user profile
|
|
*/
|
|
async function updateProfile(profile: NostrProfile): Promise<void> {
|
|
try {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
IdentityManager.saveProfile(profile)
|
|
currentProfile.value = profile
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : 'Failed to update profile'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sign out and clear identity
|
|
*/
|
|
function signOut(): void {
|
|
IdentityManager.clearIdentity()
|
|
currentIdentity.value = null
|
|
currentProfile.value = null
|
|
error.value = null
|
|
}
|
|
|
|
/**
|
|
* Check if identity is stored
|
|
*/
|
|
function hasStoredIdentity(): boolean {
|
|
return IdentityManager.hasStoredIdentity()
|
|
}
|
|
|
|
/**
|
|
* Check if stored identity is encrypted
|
|
*/
|
|
function isStoredIdentityEncrypted(): boolean {
|
|
return IdentityManager.isStoredIdentityEncrypted()
|
|
}
|
|
|
|
/**
|
|
* Load identity with password (for encrypted identities)
|
|
*/
|
|
async function loadWithPassword(password: string): Promise<void> {
|
|
try {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
const identity = await IdentityManager.loadIdentity(password)
|
|
if (identity) {
|
|
currentIdentity.value = identity
|
|
currentProfile.value = IdentityManager.loadProfile()
|
|
}
|
|
} catch (err) {
|
|
error.value = err instanceof Error ? err.message : 'Failed to load identity'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current identity info
|
|
*/
|
|
const identityInfo = computed(() => {
|
|
if (!currentIdentity.value) return null
|
|
|
|
return {
|
|
npub: currentIdentity.value.npub,
|
|
publicKey: currentIdentity.value.publicKey,
|
|
shortPubkey: currentIdentity.value.publicKey.slice(0, 8) + '...' + currentIdentity.value.publicKey.slice(-8)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Get profile display info
|
|
*/
|
|
const profileDisplay = computed(() => {
|
|
if (!currentProfile.value && !identityInfo.value) return null
|
|
|
|
return {
|
|
name: currentProfile.value?.name || currentProfile.value?.display_name || identityInfo.value?.shortPubkey || 'Anonymous',
|
|
displayName: currentProfile.value?.display_name || currentProfile.value?.name,
|
|
about: currentProfile.value?.about,
|
|
picture: currentProfile.value?.picture,
|
|
website: currentProfile.value?.website,
|
|
lightningAddress: currentProfile.value?.lud16
|
|
}
|
|
})
|
|
|
|
return {
|
|
// State
|
|
currentIdentity: computed(() => currentIdentity.value),
|
|
currentProfile: computed(() => currentProfile.value),
|
|
isAuthenticated,
|
|
isLoading,
|
|
error,
|
|
identityInfo,
|
|
profileDisplay,
|
|
|
|
// Actions
|
|
initialize,
|
|
generateNewIdentity,
|
|
importIdentity,
|
|
updateProfile,
|
|
signOut,
|
|
hasStoredIdentity,
|
|
isStoredIdentityEncrypted,
|
|
loadWithPassword
|
|
}
|
|
}
|
|
|
|
// Export singleton instance for global state
|
|
export const identity = useIdentity() |