Files
plebeian-signer/projects/chrome/src/app/components/home/identity/identity.component.ts
mleku ebe2b695cc Release v1.0.0 - Major security upgrade with Argon2id encryption
- Upgrade vault encryption from PBKDF2 (1000 iterations) to Argon2id
  (256MB memory, 8 iterations, 4 threads, ~3 second derivation)
- Add automatic migration from v1 to v2 vault format on unlock
- Add WebAssembly CSP support for hash-wasm Argon2id implementation
- Add NIP-42 relay authentication support for auth-required relays
- Add profile edit feature with pencil icon on identity page
- Add direct NIP-05 validation (removes NDK dependency for validation)
- Add deriving modal with progress timer during key derivation
- Add client tag "plebeian-signer" to profile events
- Fix modal colors (dark theme for visibility)
- Fix NIP-05 badge styling to include check/error indicator
- Add release zip packages for Chrome and Firefox

New files:
- projects/common/src/lib/helpers/argon2-crypto.ts
- projects/common/src/lib/helpers/websocket-auth.ts
- projects/common/src/lib/helpers/nip05-validator.ts
- projects/common/src/lib/components/deriving-modal/
- projects/{chrome,firefox}/src/app/components/profile-edit/
- releases/plebeian-signer-{chrome,firefox}-v1.0.0.zip

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 12:30:10 +01:00

151 lines
3.9 KiB
TypeScript

import { Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import {
Identity_DECRYPTED,
NostrHelper,
ProfileMetadata,
ProfileMetadataService,
PubkeyComponent,
StorageService,
ToastComponent,
VisualNip05Pipe,
validateNip05,
} from '@common';
@Component({
selector: 'app-identity',
imports: [PubkeyComponent, VisualNip05Pipe, ToastComponent],
templateUrl: './identity.component.html',
styleUrl: './identity.component.scss',
})
export class IdentityComponent implements OnInit {
selectedIdentity: Identity_DECRYPTED | undefined;
selectedIdentityNpub: string | undefined;
profile: ProfileMetadata | null = null;
nip05isValidated: boolean | undefined;
validating = false;
loading = true;
readonly #storage = inject(StorageService);
readonly #router = inject(Router);
readonly #profileMetadata = inject(ProfileMetadataService);
ngOnInit(): void {
this.#loadData();
}
get displayName(): string | undefined {
return this.#profileMetadata.getDisplayName(this.profile);
}
get username(): string | undefined {
return this.#profileMetadata.getUsername(this.profile);
}
get avatarUrl(): string | undefined {
return this.profile?.picture;
}
get bannerUrl(): string | undefined {
return this.profile?.banner;
}
copyToClipboard(pubkey: string | undefined) {
if (!pubkey) {
return;
}
navigator.clipboard.writeText(pubkey);
}
onClickShowDetails() {
if (!this.selectedIdentity) {
return;
}
this.#router.navigateByUrl(
`/edit-identity/${this.selectedIdentity.id}/home`
);
}
onClickEditProfile() {
if (!this.selectedIdentity) {
return;
}
this.#router.navigateByUrl('/profile-edit');
}
async #loadData() {
try {
const selectedIdentityId =
this.#storage.getBrowserSessionHandler().browserSessionData
?.selectedIdentityId ?? null;
const identity = this.#storage
.getBrowserSessionHandler()
.browserSessionData?.identities.find(
(x) => x.id === selectedIdentityId
);
if (!identity) {
this.loading = false;
return;
}
this.selectedIdentity = identity;
const pubkey = NostrHelper.pubkeyFromPrivkey(identity.privkey);
this.selectedIdentityNpub = NostrHelper.pubkey2npub(pubkey);
// Initialize the profile metadata service (loads cache from storage)
await this.#profileMetadata.initialize();
// Check if we have cached profile data
const cachedProfile = this.#profileMetadata.getCachedProfile(pubkey);
if (cachedProfile) {
this.profile = cachedProfile;
this.loading = false;
// Validate NIP-05 if present (in background)
if (cachedProfile.nip05) {
this.#validateNip05(pubkey, cachedProfile.nip05);
}
return; // Use cached data, don't fetch again
}
// No cached data, fetch from relays
this.loading = true;
const fetchedProfile = await this.#profileMetadata.fetchProfile(pubkey);
if (fetchedProfile) {
this.profile = fetchedProfile;
// Validate NIP-05 if present
if (fetchedProfile.nip05) {
this.#validateNip05(pubkey, fetchedProfile.nip05);
}
}
this.loading = false;
} catch (error) {
console.error(error);
this.loading = false;
}
}
async #validateNip05(pubkey: string, nip05: string) {
try {
this.validating = true;
// Direct NIP-05 validation - fetches .well-known/nostr.json directly
const result = await validateNip05(nip05, pubkey);
this.nip05isValidated = result.valid;
if (!result.valid) {
console.log('NIP-05 validation failed:', result.error);
}
this.validating = false;
} catch (error) {
console.error('NIP-05 validation failed:', error);
this.nip05isValidated = false;
this.validating = false;
}
}
}