identity.ts raw
1 import {
2 CryptoHelper,
3 Identity_DECRYPTED,
4 Identity_ENCRYPTED,
5 NostrHelper,
6 StorageService,
7 } from '@common';
8
9 export const addIdentity = async function (
10 this: StorageService,
11 data: {
12 nick: string;
13 privkeyString: string;
14 }
15 ): Promise<void> {
16 this.assureIsInitialized();
17
18 const privkey = NostrHelper.getNostrPrivkeyObject(
19 data.privkeyString.toLowerCase()
20 ).hex;
21
22 // Check if an identity with the same privkey already exists.
23 const existingIdentity = (
24 this.getBrowserSessionHandler().browserSessionData?.identities ?? []
25 ).find((x) => x.privkey === privkey);
26 if (existingIdentity) {
27 throw new Error(
28 `An identity with the same private key already exists: ${existingIdentity.nick}`
29 );
30 }
31
32 const browserSessionData = this.getBrowserSessionHandler().browserSessionData;
33 if (!browserSessionData) {
34 throw new Error('Browser session data is undefined.');
35 }
36
37 const decryptedIdentity: Identity_DECRYPTED = {
38 id: CryptoHelper.v4(),
39 nick: data.nick,
40 privkey,
41 createdAt: new Date().toISOString(),
42 };
43
44 // Add the new identity to the session data.
45 browserSessionData.identities.push(decryptedIdentity);
46 let isFirstIdentity = false;
47 if (browserSessionData.identities.length === 1) {
48 isFirstIdentity = true;
49 browserSessionData.selectedIdentityId = decryptedIdentity.id;
50 }
51 this.getBrowserSessionHandler().saveFullData(browserSessionData);
52
53 // Encrypt the new identity and add it to the sync data.
54 const encryptedIdentity = await encryptIdentity.call(this, decryptedIdentity);
55 const encryptedIdentities = [
56 ...(this.getBrowserSyncHandler().browserSyncData?.identities ?? []),
57 encryptedIdentity,
58 ];
59
60 await this.getBrowserSyncHandler().saveAndSetPartialData_Identities({
61 identities: encryptedIdentities,
62 });
63
64 if (isFirstIdentity) {
65 await this.getBrowserSyncHandler().saveAndSetPartialData_SelectedIdentityId(
66 {
67 selectedIdentityId: encryptedIdentity.id,
68 }
69 );
70 }
71 };
72
73 export const deleteIdentity = async function (
74 this: StorageService,
75 identityId: string | undefined
76 ): Promise<void> {
77 this.assureIsInitialized();
78
79 if (!identityId) {
80 return;
81 }
82
83 const browserSessionData = this.getBrowserSessionHandler().browserSessionData;
84 const browserSyncData = this.getBrowserSyncHandler().browserSyncData;
85 if (!browserSessionData || !browserSyncData) {
86 throw new Error('Browser session or sync data is undefined.');
87 }
88
89 browserSessionData.identities = browserSessionData.identities.filter(
90 (x) => x.id !== identityId
91 );
92 browserSessionData.permissions = browserSessionData.permissions.filter(
93 (x) => x.identityId !== identityId
94 );
95 browserSessionData.relays = browserSessionData.relays.filter(
96 (x) => x.identityId !== identityId
97 );
98 if (browserSessionData.selectedIdentityId === identityId) {
99 // Choose another identity to be selected or null if there is none.
100 browserSessionData.selectedIdentityId =
101 browserSessionData.identities.length > 0
102 ? browserSessionData.identities[0].id
103 : null;
104 }
105 await this.getBrowserSessionHandler().saveFullData(browserSessionData);
106
107 // Handle Sync data.
108 const encryptedIdentityId = await this.encrypt(identityId);
109 await this.getBrowserSyncHandler().saveAndSetPartialData_Identities({
110 identities: browserSyncData.identities.filter(
111 (x) => x.id !== encryptedIdentityId
112 ),
113 });
114 await this.getBrowserSyncHandler().saveAndSetPartialData_Permissions({
115 permissions: browserSyncData.permissions.filter(
116 (x) => x.identityId !== encryptedIdentityId
117 ),
118 });
119 await this.getBrowserSyncHandler().saveAndSetPartialData_Relays({
120 relays: browserSyncData.relays.filter(
121 (x) => x.identityId !== encryptedIdentityId
122 ),
123 });
124 await this.getBrowserSyncHandler().saveAndSetPartialData_SelectedIdentityId({
125 selectedIdentityId:
126 browserSessionData.selectedIdentityId === null
127 ? null
128 : await this.encrypt(browserSessionData.selectedIdentityId),
129 });
130 };
131
132 export const switchIdentity = async function (
133 this: StorageService,
134 identityId: string | null
135 ): Promise<void> {
136 this.assureIsInitialized();
137
138 // Check, if the identity really exists.
139 const browserSessionData = this.getBrowserSessionHandler().browserSessionData;
140
141 if (!browserSessionData?.identities.find((x) => x.id === identityId)) {
142 return;
143 }
144
145 browserSessionData.selectedIdentityId = identityId;
146 await this.getBrowserSessionHandler().saveFullData(browserSessionData);
147
148 const encryptedIdentityId =
149 identityId === null ? null : await this.encrypt(identityId);
150 await this.getBrowserSyncHandler().saveAndSetPartialData_SelectedIdentityId({
151 selectedIdentityId: encryptedIdentityId,
152 });
153 };
154
155 export const encryptIdentity = async function (
156 this: StorageService,
157 identity: Identity_DECRYPTED
158 ): Promise<Identity_ENCRYPTED> {
159 const encryptedIdentity: Identity_ENCRYPTED = {
160 id: await this.encrypt(identity.id),
161 nick: await this.encrypt(identity.nick),
162 createdAt: await this.encrypt(identity.createdAt),
163 privkey: await this.encrypt(identity.privkey),
164 };
165
166 return encryptedIdentity;
167 };
168
169 /**
170 * Locked vault context for decryption during unlock
171 * - v1 vaults use password (PBKDF2)
172 * - v2 vaults use keyBase64 (pre-derived Argon2id key)
173 */
174 export type LockedVaultContext =
175 | { iv: string; password: string; keyBase64?: undefined }
176 | { iv: string; keyBase64: string; password?: undefined };
177
178 export const decryptIdentities = async function (
179 this: StorageService,
180 identities: Identity_ENCRYPTED[],
181 withLockedVault: LockedVaultContext | undefined = undefined
182 ): Promise<Identity_DECRYPTED[]> {
183 const decryptedIdentities: Identity_DECRYPTED[] = [];
184
185 for (const identity of identities) {
186 const decryptedIdentity = await decryptIdentity.call(
187 this,
188 identity,
189 withLockedVault
190 );
191 decryptedIdentities.push(decryptedIdentity);
192 }
193
194 return decryptedIdentities;
195 };
196
197 export const decryptIdentity = async function (
198 this: StorageService,
199 identity: Identity_ENCRYPTED,
200 withLockedVault: LockedVaultContext | undefined = undefined
201 ): Promise<Identity_DECRYPTED> {
202 if (typeof withLockedVault === 'undefined') {
203 const decryptedIdentity: Identity_DECRYPTED = {
204 id: await this.decrypt(identity.id, 'string'),
205 nick: await this.decrypt(identity.nick, 'string'),
206 createdAt: await this.decrypt(identity.createdAt, 'string'),
207 privkey: await this.decrypt(identity.privkey, 'string'),
208 };
209
210 return decryptedIdentity;
211 }
212
213 // v2: Use pre-derived key
214 if (withLockedVault.keyBase64) {
215 const decryptedIdentity: Identity_DECRYPTED = {
216 id: await this.decryptWithLockedVaultV2(
217 identity.id,
218 'string',
219 withLockedVault.iv,
220 withLockedVault.keyBase64
221 ),
222 nick: await this.decryptWithLockedVaultV2(
223 identity.nick,
224 'string',
225 withLockedVault.iv,
226 withLockedVault.keyBase64
227 ),
228 createdAt: await this.decryptWithLockedVaultV2(
229 identity.createdAt,
230 'string',
231 withLockedVault.iv,
232 withLockedVault.keyBase64
233 ),
234 privkey: await this.decryptWithLockedVaultV2(
235 identity.privkey,
236 'string',
237 withLockedVault.iv,
238 withLockedVault.keyBase64
239 ),
240 };
241 return decryptedIdentity;
242 }
243
244 // v1: Use password (PBKDF2)
245 const decryptedIdentity: Identity_DECRYPTED = {
246 id: await this.decryptWithLockedVault(
247 identity.id,
248 'string',
249 withLockedVault.iv,
250 withLockedVault.password!
251 ),
252 nick: await this.decryptWithLockedVault(
253 identity.nick,
254 'string',
255 withLockedVault.iv,
256 withLockedVault.password!
257 ),
258 createdAt: await this.decryptWithLockedVault(
259 identity.createdAt,
260 'string',
261 withLockedVault.iv,
262 withLockedVault.password!
263 ),
264 privkey: await this.decryptWithLockedVault(
265 identity.privkey,
266 'string',
267 withLockedVault.iv,
268 withLockedVault.password!
269 ),
270 };
271
272 return decryptedIdentity;
273 };
274