feat: Implement comprehensive Nostr identity and social features

## 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>
This commit is contained in:
padreug 2025-07-02 16:25:20 +02:00
parent d3e8b23c86
commit ee7eb461c4
12 changed files with 1777 additions and 21 deletions

View file

@ -0,0 +1,193 @@
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()