shogun-core 3.0.4 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/browser/shogun-core.js +92134 -0
  2. package/dist/browser/shogun-core.js.map +1 -0
  3. package/dist/config/simplified-config.js +230 -0
  4. package/dist/core.js +338 -0
  5. package/dist/gundb/crypto.js +268 -0
  6. package/dist/gundb/db.js +1833 -0
  7. package/dist/gundb/derive.js +229 -0
  8. package/dist/gundb/errors.js +66 -0
  9. package/dist/gundb/index.js +6 -0
  10. package/dist/gundb/restricted-put.js +81 -0
  11. package/dist/gundb/rxjs.js +445 -0
  12. package/dist/gundb/simple-api.js +438 -0
  13. package/dist/gundb/types.js +4 -0
  14. package/dist/index.js +16 -0
  15. package/dist/interfaces/common.js +1 -0
  16. package/dist/interfaces/events.js +36 -0
  17. package/dist/interfaces/plugin.js +1 -0
  18. package/dist/interfaces/shogun.js +34 -0
  19. package/dist/managers/AuthManager.js +225 -0
  20. package/dist/managers/CoreInitializer.js +240 -0
  21. package/dist/managers/EventManager.js +67 -0
  22. package/dist/managers/PluginManager.js +296 -0
  23. package/dist/migration-test.js +91 -0
  24. package/dist/plugins/base.js +47 -0
  25. package/dist/plugins/index.js +15 -0
  26. package/dist/plugins/nostr/index.js +4 -0
  27. package/dist/plugins/nostr/nostrConnector.js +413 -0
  28. package/dist/plugins/nostr/nostrConnectorPlugin.js +446 -0
  29. package/dist/plugins/nostr/nostrSigner.js +313 -0
  30. package/dist/plugins/nostr/types.js +1 -0
  31. package/dist/plugins/oauth/index.js +3 -0
  32. package/dist/plugins/oauth/oauthConnector.js +753 -0
  33. package/dist/plugins/oauth/oauthPlugin.js +396 -0
  34. package/dist/plugins/oauth/types.js +1 -0
  35. package/dist/plugins/web3/index.js +4 -0
  36. package/dist/plugins/web3/types.js +1 -0
  37. package/dist/plugins/web3/web3Connector.js +528 -0
  38. package/dist/plugins/web3/web3ConnectorPlugin.js +448 -0
  39. package/dist/plugins/web3/web3Signer.js +308 -0
  40. package/dist/plugins/webauthn/index.js +3 -0
  41. package/dist/plugins/webauthn/types.js +11 -0
  42. package/dist/plugins/webauthn/webauthn.js +478 -0
  43. package/dist/plugins/webauthn/webauthnPlugin.js +398 -0
  44. package/dist/plugins/webauthn/webauthnSigner.js +304 -0
  45. package/dist/storage/storage.js +147 -0
  46. package/dist/types/config/simplified-config.d.ts +114 -0
  47. package/dist/types/core.d.ts +305 -0
  48. package/dist/types/gundb/crypto.d.ts +95 -0
  49. package/dist/types/gundb/db.d.ts +404 -0
  50. package/dist/types/gundb/derive.d.ts +21 -0
  51. package/dist/types/gundb/errors.d.ts +42 -0
  52. package/dist/types/gundb/index.d.ts +3 -0
  53. package/dist/types/gundb/restricted-put.d.ts +15 -0
  54. package/dist/types/gundb/rxjs.d.ts +110 -0
  55. package/dist/types/gundb/simple-api.d.ts +90 -0
  56. package/dist/types/gundb/types.d.ts +264 -0
  57. package/dist/types/index.d.ts +14 -0
  58. package/dist/types/interfaces/common.d.ts +85 -0
  59. package/dist/types/interfaces/events.d.ts +131 -0
  60. package/dist/types/interfaces/plugin.d.ts +162 -0
  61. package/dist/types/interfaces/shogun.d.ts +215 -0
  62. package/dist/types/managers/AuthManager.d.ts +72 -0
  63. package/dist/types/managers/CoreInitializer.d.ts +40 -0
  64. package/dist/types/managers/EventManager.d.ts +49 -0
  65. package/dist/types/managers/PluginManager.d.ts +145 -0
  66. package/dist/types/migration-test.d.ts +16 -0
  67. package/dist/types/plugins/base.d.ts +35 -0
  68. package/dist/types/plugins/index.d.ts +14 -0
  69. package/dist/types/plugins/nostr/index.d.ts +4 -0
  70. package/dist/types/plugins/nostr/nostrConnector.d.ts +119 -0
  71. package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +163 -0
  72. package/dist/types/plugins/nostr/nostrSigner.d.ts +105 -0
  73. package/dist/types/plugins/nostr/types.d.ts +122 -0
  74. package/dist/types/plugins/oauth/index.d.ts +3 -0
  75. package/dist/types/plugins/oauth/oauthConnector.d.ts +110 -0
  76. package/dist/types/plugins/oauth/oauthPlugin.d.ts +91 -0
  77. package/dist/types/plugins/oauth/types.d.ts +114 -0
  78. package/dist/types/plugins/web3/index.d.ts +4 -0
  79. package/dist/types/plugins/web3/types.d.ts +107 -0
  80. package/dist/types/plugins/web3/web3Connector.d.ts +129 -0
  81. package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +160 -0
  82. package/dist/types/plugins/web3/web3Signer.d.ts +114 -0
  83. package/dist/types/plugins/webauthn/index.d.ts +3 -0
  84. package/dist/types/plugins/webauthn/types.d.ts +162 -0
  85. package/dist/types/plugins/webauthn/webauthn.d.ts +129 -0
  86. package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +158 -0
  87. package/dist/types/plugins/webauthn/webauthnSigner.d.ts +91 -0
  88. package/dist/types/storage/storage.d.ts +50 -0
  89. package/dist/types/utils/errorHandler.d.ts +119 -0
  90. package/dist/types/utils/eventEmitter.d.ts +39 -0
  91. package/dist/types/utils/validation.d.ts +27 -0
  92. package/dist/utils/errorHandler.js +241 -0
  93. package/dist/utils/eventEmitter.js +76 -0
  94. package/dist/utils/validation.js +72 -0
  95. package/package.json +1 -1
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Constants for WebAuthn configuration
3
+ */
4
+ const MIN_USERNAME_LENGTH = 3;
5
+ const MAX_USERNAME_LENGTH = 64;
6
+ import { ethers } from "ethers";
7
+ import { ErrorHandler, ErrorType } from "../../utils/errorHandler";
8
+ import { EventEmitter } from "../../utils/eventEmitter";
9
+ import { WebAuthnEventType, } from "./types";
10
+ import derive from "../../gundb/derive";
11
+ /**
12
+ * Constants for WebAuthn configuration
13
+ */
14
+ const DEFAULT_CONFIG = {
15
+ rpName: "Shogun Wallet",
16
+ timeout: 60000,
17
+ userVerification: "preferred",
18
+ attestation: "none",
19
+ authenticatorAttachment: "platform",
20
+ requireResidentKey: false,
21
+ };
22
+ /**
23
+ * Main WebAuthn class for authentication management
24
+ */
25
+ export class Webauthn extends EventEmitter {
26
+ config;
27
+ gunInstance;
28
+ credential;
29
+ abortController = null;
30
+ /**
31
+ * Creates a new WebAuthn instance
32
+ */
33
+ constructor(gunInstance, config) {
34
+ super();
35
+ this.gunInstance = gunInstance;
36
+ this.credential = null;
37
+ // Merge default config with provided config
38
+ this.config = {
39
+ ...DEFAULT_CONFIG,
40
+ ...config,
41
+ rpId: config?.rpId ??
42
+ (typeof window !== "undefined" &&
43
+ window.location &&
44
+ window.location.hostname
45
+ ? window.location.hostname.split(":")[0]
46
+ : "localhost"),
47
+ };
48
+ }
49
+ /**
50
+ * Validates a username
51
+ */
52
+ validateUsername(username) {
53
+ if (!username || typeof username !== "string") {
54
+ throw new Error("Username must be a non-empty string");
55
+ }
56
+ if (username.length < MIN_USERNAME_LENGTH ||
57
+ username.length > MAX_USERNAME_LENGTH) {
58
+ throw new Error(`Username must be between ${MIN_USERNAME_LENGTH} and ${MAX_USERNAME_LENGTH} characters`);
59
+ }
60
+ if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
61
+ throw new Error("Username can only contain letters, numbers, underscores and hyphens");
62
+ }
63
+ }
64
+ /**
65
+ * Creates a new WebAuthn account with retry logic
66
+ */
67
+ async createAccount(username, credentials, isNewDevice = false) {
68
+ try {
69
+ this.validateUsername(username);
70
+ const maxRetries = 3;
71
+ let lastError = null;
72
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
73
+ try {
74
+ const result = await this.generateCredentials(username, credentials, isNewDevice);
75
+ if (result.success) {
76
+ this.emit(WebAuthnEventType.DEVICE_REGISTERED, {
77
+ type: WebAuthnEventType.DEVICE_REGISTERED,
78
+ data: { username },
79
+ timestamp: Date.now(),
80
+ });
81
+ return result;
82
+ }
83
+ lastError = new Error(result.error ?? "Unknown error");
84
+ }
85
+ catch (error) {
86
+ lastError = error;
87
+ if (attempt < maxRetries) {
88
+ await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
89
+ continue;
90
+ }
91
+ }
92
+ }
93
+ throw lastError || new Error("Failed to create account after retries");
94
+ }
95
+ catch (error) {
96
+ this.emit(WebAuthnEventType.ERROR, {
97
+ type: WebAuthnEventType.ERROR,
98
+ data: { error: error.message },
99
+ timestamp: Date.now(),
100
+ });
101
+ throw error;
102
+ }
103
+ }
104
+ /**
105
+ * Authenticates a user with timeout and abort handling
106
+ */
107
+ async authenticateUser(username, salt, options = {}) {
108
+ try {
109
+ this.validateUsername(username);
110
+ if (!salt) {
111
+ const error = new Error("No WebAuthn credentials found for this username");
112
+ ErrorHandler.handle(ErrorType.WEBAUTHN, "NO_CREDENTIALS", error.message, error);
113
+ return { success: false, error: error.message };
114
+ }
115
+ // Cancel any existing authentication attempt
116
+ this.abortAuthentication();
117
+ // Create new abort controller
118
+ this.abortController = new AbortController();
119
+ const timeout = options.timeout || this.config.timeout;
120
+ const timeoutId = setTimeout(() => this.abortController?.abort(), timeout);
121
+ try {
122
+ const challenge = this.generateChallenge(username);
123
+ const assertionOptions = {
124
+ challenge,
125
+ allowCredentials: [],
126
+ timeout,
127
+ userVerification: options.userVerification || this.config.userVerification,
128
+ rpId: this.config.rpId,
129
+ };
130
+ const assertion = (await navigator.credentials.get({
131
+ publicKey: assertionOptions,
132
+ signal: this.abortController.signal,
133
+ }));
134
+ if (!assertion) {
135
+ throw new Error("WebAuthn verification failed");
136
+ }
137
+ const { password } = this.generateCredentialsFromSalt(username, salt);
138
+ const deviceInfo = this.getDeviceInfo(assertion.id);
139
+ const result = {
140
+ success: true,
141
+ username,
142
+ password,
143
+ credentialId: this.bufferToBase64(assertion.rawId),
144
+ deviceInfo,
145
+ };
146
+ this.emit(WebAuthnEventType.AUTHENTICATION_SUCCESS, {
147
+ type: WebAuthnEventType.AUTHENTICATION_SUCCESS,
148
+ data: { username, deviceInfo },
149
+ timestamp: Date.now(),
150
+ });
151
+ return result;
152
+ }
153
+ finally {
154
+ clearTimeout(timeoutId);
155
+ this.abortController = null;
156
+ }
157
+ }
158
+ catch (error) {
159
+ const errorMessage = error instanceof Error ? error.message : "Unknown WebAuthn error";
160
+ this.emit(WebAuthnEventType.AUTHENTICATION_FAILED, {
161
+ type: WebAuthnEventType.AUTHENTICATION_FAILED,
162
+ data: { username, error: errorMessage },
163
+ timestamp: Date.now(),
164
+ });
165
+ ErrorHandler.handle(ErrorType.WEBAUTHN, "AUTH_ERROR", errorMessage, error);
166
+ return { success: false, error: errorMessage };
167
+ }
168
+ }
169
+ /**
170
+ * Aborts current authentication attempt
171
+ */
172
+ abortAuthentication() {
173
+ if (this.abortController) {
174
+ this.abortController.abort();
175
+ this.abortController = null;
176
+ }
177
+ }
178
+ /**
179
+ * Gets device information
180
+ */
181
+ getDeviceInfo(credentialId) {
182
+ const platformInfo = this.getPlatformInfo();
183
+ return {
184
+ deviceId: credentialId,
185
+ timestamp: Date.now(),
186
+ name: platformInfo.name,
187
+ platform: platformInfo.platform,
188
+ lastUsed: Date.now(),
189
+ };
190
+ }
191
+ /**
192
+ * Gets platform information
193
+ */
194
+ getPlatformInfo() {
195
+ if (typeof navigator === "undefined") {
196
+ return { name: "unknown", platform: "unknown" };
197
+ }
198
+ const platform = navigator.platform;
199
+ const userAgent = navigator.userAgent;
200
+ if (/iPhone|iPad|iPod/.test(platform)) {
201
+ return { name: "iOS Device", platform };
202
+ }
203
+ if (/Android/.test(userAgent)) {
204
+ return { name: "Android Device", platform };
205
+ }
206
+ if (/Win/.test(platform)) {
207
+ return { name: "Windows Device", platform };
208
+ }
209
+ if (/Mac/.test(platform)) {
210
+ return { name: "Mac Device", platform };
211
+ }
212
+ if (/Linux/.test(platform)) {
213
+ return { name: "Linux Device", platform };
214
+ }
215
+ return { name: "Unknown Device", platform };
216
+ }
217
+ /**
218
+ * Generates a challenge for WebAuthn operations
219
+ */
220
+ generateChallenge(username) {
221
+ const timestamp = Date.now().toString();
222
+ const randomBytes = this.getRandomBytes(32);
223
+ const challengeData = `${username}-${timestamp}-${this.uint8ArrayToHex(randomBytes)}`;
224
+ return new TextEncoder().encode(challengeData);
225
+ }
226
+ /**
227
+ * Gets cryptographically secure random bytes
228
+ */
229
+ getRandomBytes(length) {
230
+ if (typeof window !== "undefined" && window.crypto) {
231
+ return window.crypto.getRandomValues(new Uint8Array(length));
232
+ }
233
+ throw new Error("No cryptographic implementation available");
234
+ }
235
+ /**
236
+ * Converts Uint8Array to hexadecimal string
237
+ */
238
+ uint8ArrayToHex(arr) {
239
+ return Array.from(arr)
240
+ .map((b) => b.toString(16).padStart(2, "0"))
241
+ .join("");
242
+ }
243
+ /**
244
+ * Converts ArrayBuffer to URL-safe base64 string
245
+ */
246
+ bufferToBase64(buffer) {
247
+ const bytes = new Uint8Array(buffer);
248
+ const binary = bytes.reduce((str, byte) => str + String.fromCharCode(byte), "");
249
+ return btoa(binary)
250
+ .replace(/\+/g, "-")
251
+ .replace(/\//g, "_")
252
+ .replace(/=/g, "");
253
+ }
254
+ /**
255
+ * Generates credentials from username and salt
256
+ */
257
+ generateCredentialsFromSalt(username, salt) {
258
+ const data = ethers.toUtf8Bytes(username + salt);
259
+ return {
260
+ password: ethers.sha256(data),
261
+ };
262
+ }
263
+ /**
264
+ * Checks if WebAuthn is supported
265
+ */
266
+ isSupported() {
267
+ return (typeof window !== "undefined" && window.PublicKeyCredential !== undefined);
268
+ }
269
+ /**
270
+ * Creates a WebAuthn credential for registration
271
+ */
272
+ async createCredential(username) {
273
+ try {
274
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
275
+ const userId = new TextEncoder().encode(username);
276
+ const publicKeyCredentialCreationOptions = {
277
+ challenge,
278
+ rp: {
279
+ name: "Shogun Wallet",
280
+ ...(this.config.rpId !== "localhost" && { id: this.config.rpId }),
281
+ },
282
+ user: {
283
+ id: userId,
284
+ name: username,
285
+ displayName: username,
286
+ },
287
+ pubKeyCredParams: [{ type: "public-key", alg: -7 }],
288
+ timeout: this.config.timeout,
289
+ attestation: this.config.attestation,
290
+ authenticatorSelection: {
291
+ authenticatorAttachment: this.config.authenticatorAttachment,
292
+ userVerification: this.config.userVerification,
293
+ requireResidentKey: this.config.requireResidentKey,
294
+ },
295
+ };
296
+ const credential = await navigator.credentials.create({
297
+ publicKey: publicKeyCredentialCreationOptions,
298
+ });
299
+ if (!credential) {
300
+ throw new Error("Credential creation failed");
301
+ }
302
+ const webAuthnCredential = credential;
303
+ // Convert to WebAuthnCredentialData
304
+ const credentialData = {
305
+ id: webAuthnCredential.id,
306
+ rawId: webAuthnCredential.rawId,
307
+ type: webAuthnCredential.type,
308
+ response: {
309
+ clientDataJSON: webAuthnCredential.response.clientDataJSON,
310
+ },
311
+ getClientExtensionResults: webAuthnCredential.getClientExtensionResults,
312
+ };
313
+ // Add additional response properties if available
314
+ if ("attestationObject" in webAuthnCredential.response) {
315
+ credentialData.response.attestationObject = webAuthnCredential.response.attestationObject;
316
+ }
317
+ this.credential = credentialData;
318
+ return credentialData;
319
+ }
320
+ catch (error) {
321
+ console.error("Detailed error in credential creation:", error);
322
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
323
+ throw new Error(`Error creating credentials: ${errorMessage}`);
324
+ }
325
+ }
326
+ /**
327
+ * Generates WebAuthn credentials (uniforme con altri plugin)
328
+ */
329
+ async generateCredentials(username, existingCredential, isLogin = false) {
330
+ try {
331
+ if (isLogin) {
332
+ const verificationResult = await this.verifyCredential(username);
333
+ if (!verificationResult.success || !verificationResult.credentialId) {
334
+ return {
335
+ success: false,
336
+ username,
337
+ key: undefined,
338
+ credentialId: "",
339
+ error: verificationResult.error,
340
+ publicKey: null,
341
+ };
342
+ }
343
+ // Deriva la chiave GunDB
344
+ const key = await deriveWebauthnKeys(username, verificationResult.credentialId);
345
+ return {
346
+ success: true,
347
+ username,
348
+ key,
349
+ credentialId: verificationResult.credentialId,
350
+ publicKey: null,
351
+ };
352
+ }
353
+ else {
354
+ const credential = await this.createCredential(username);
355
+ const credentialId = credential.id;
356
+ let publicKey = null;
357
+ if (credential?.response?.getPublicKey) {
358
+ publicKey = credential.response.getPublicKey();
359
+ }
360
+ // Deriva la chiave GunDB
361
+ const key = await deriveWebauthnKeys(username, credentialId);
362
+ return {
363
+ success: true,
364
+ username,
365
+ key,
366
+ credentialId,
367
+ publicKey,
368
+ };
369
+ }
370
+ }
371
+ catch (error) {
372
+ console.error("Error in generateCredentials:", error);
373
+ const errorMessage = error instanceof Error
374
+ ? error.message
375
+ : "Unknown error during WebAuthn operation";
376
+ return {
377
+ success: false,
378
+ username,
379
+ key: undefined,
380
+ credentialId: "",
381
+ error: errorMessage,
382
+ publicKey: null,
383
+ };
384
+ }
385
+ }
386
+ /**
387
+ * Verifies a credential
388
+ */
389
+ async verifyCredential(username) {
390
+ try {
391
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
392
+ const options = {
393
+ challenge,
394
+ timeout: this.config.timeout,
395
+ userVerification: this.config.userVerification,
396
+ ...(this.config.rpId !== "localhost" && { rpId: this.config.rpId }),
397
+ };
398
+ if (this.credential?.rawId) {
399
+ options.allowCredentials = [
400
+ {
401
+ id: this.credential.rawId,
402
+ type: "public-key",
403
+ },
404
+ ];
405
+ }
406
+ const assertion = await navigator.credentials.get({
407
+ publicKey: options,
408
+ });
409
+ if (!assertion) {
410
+ return {
411
+ success: false,
412
+ error: "Credential verification failed",
413
+ };
414
+ }
415
+ return {
416
+ success: true,
417
+ credentialId: assertion.id,
418
+ username,
419
+ };
420
+ }
421
+ catch (error) {
422
+ console.error("Error verifying credentials:", error);
423
+ const errorMessage = error instanceof Error
424
+ ? error.message
425
+ : "Unknown error verifying credentials";
426
+ return {
427
+ success: false,
428
+ error: errorMessage,
429
+ };
430
+ }
431
+ }
432
+ /**
433
+ * Removes device credentials
434
+ */
435
+ async removeDevice(username, credentialId, credentials) {
436
+ if (!credentials ||
437
+ !credentials.credentials ||
438
+ !credentials.credentials[credentialId]) {
439
+ return { success: false };
440
+ }
441
+ const updatedCreds = { ...credentials };
442
+ // Make sure credentials exists before modifying it
443
+ if (updatedCreds.credentials) {
444
+ delete updatedCreds.credentials[credentialId];
445
+ }
446
+ return {
447
+ success: true,
448
+ updatedCredentials: updatedCreds,
449
+ };
450
+ }
451
+ /**
452
+ * Signs data with the credential
453
+ */
454
+ async sign(data) {
455
+ const signature = await navigator.credentials.get({
456
+ publicKey: {
457
+ challenge: new Uint8Array(16),
458
+ rpId: this.config.rpId,
459
+ },
460
+ });
461
+ return signature;
462
+ }
463
+ }
464
+ // Add to global scope if available
465
+ if (typeof window !== "undefined") {
466
+ window.Webauthn = Webauthn;
467
+ }
468
+ else if (typeof global !== "undefined") {
469
+ global.Webauthn = Webauthn;
470
+ }
471
+ // Funzione helper per derivare chiavi WebAuthn (come per Web3)
472
+ export async function deriveWebauthnKeys(username, credentialId) {
473
+ const hashedCredentialId = ethers.keccak256(ethers.toUtf8Bytes(credentialId));
474
+ const salt = `${username}_${credentialId}`;
475
+ return await derive(hashedCredentialId, salt, {
476
+ includeP256: true,
477
+ });
478
+ }