@le-space/orbitdb-storage-bridge 0.10.0
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.
- package/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/components/StorachaAuth.svelte +801 -0
- package/dist/components/StorachaIntegration.svelte +965 -0
- package/dist/components/WebAuthnDIDProvider.js +562 -0
- package/dist/components/storacha-backup.js +325 -0
- package/dist/components/theme.js +79 -0
- package/lib/backends/aleph-pin.js +161 -0
- package/lib/backends/aleph.js +197 -0
- package/lib/backends/lighthouse.js +325 -0
- package/lib/backends/memory.js +140 -0
- package/lib/backends/pinata.js +330 -0
- package/lib/backends/resolve.js +72 -0
- package/lib/backends/storacha.js +178 -0
- package/lib/backends/types.js +187 -0
- package/lib/backup-car.js +1286 -0
- package/lib/backup-helpers.js +87 -0
- package/lib/backup-metadata.js +40 -0
- package/lib/backup.js +249 -0
- package/lib/block-bytes.js +39 -0
- package/lib/car-storage.js +342 -0
- package/lib/courier-sync.js +886 -0
- package/lib/dehydrate.js +146 -0
- package/lib/extract-blocks.js +258 -0
- package/lib/gateway-fetch.js +116 -0
- package/lib/ipns-helpers.js +324 -0
- package/lib/logger.js +128 -0
- package/lib/memory-courier.js +105 -0
- package/lib/orbitdb-storacha-bridge.js +2789 -0
- package/lib/pointer-ipns.js +234 -0
- package/lib/restore-cid.js +302 -0
- package/lib/ucan-bridge.js +921 -0
- package/lib/utils.js +316 -0
- package/package.json +172 -0
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createEventDispatcher, onMount } from "svelte";
|
|
3
|
+
// Import Carbon components and icons
|
|
4
|
+
import {
|
|
5
|
+
Tile,
|
|
6
|
+
Tabs,
|
|
7
|
+
Tab,
|
|
8
|
+
TabContent,
|
|
9
|
+
Button,
|
|
10
|
+
TextInput,
|
|
11
|
+
TextArea,
|
|
12
|
+
PasswordInput,
|
|
13
|
+
InlineNotification,
|
|
14
|
+
Loading,
|
|
15
|
+
Tag,
|
|
16
|
+
} from "carbon-components-svelte";
|
|
17
|
+
import {
|
|
18
|
+
Cloud,
|
|
19
|
+
Key,
|
|
20
|
+
Email,
|
|
21
|
+
Shuffle,
|
|
22
|
+
CheckmarkFilled,
|
|
23
|
+
Logout,
|
|
24
|
+
Reset,
|
|
25
|
+
} from "carbon-icons-svelte";
|
|
26
|
+
import {
|
|
27
|
+
initializeStorachaClient,
|
|
28
|
+
initializeStorachaClientWithUCAN,
|
|
29
|
+
initializeStorachaClientWithSeed,
|
|
30
|
+
createStorachaAccount,
|
|
31
|
+
listSpaces,
|
|
32
|
+
createSpace,
|
|
33
|
+
generateSeedPhrase,
|
|
34
|
+
validateSeedPhrase,
|
|
35
|
+
} from "./storacha-backup.js";
|
|
36
|
+
import { logger } from "../lib/logger.js";
|
|
37
|
+
|
|
38
|
+
const dispatch = createEventDispatcher();
|
|
39
|
+
|
|
40
|
+
// Component props
|
|
41
|
+
export let autoLogin = true;
|
|
42
|
+
export let showTitle = true;
|
|
43
|
+
export let compact = false;
|
|
44
|
+
export let enableSeedAuth = true;
|
|
45
|
+
export let enableEmailAuth = true;
|
|
46
|
+
|
|
47
|
+
// Authentication state
|
|
48
|
+
let isAuthenticated = false;
|
|
49
|
+
let client = null;
|
|
50
|
+
let currentSpace = null;
|
|
51
|
+
let spaces = [];
|
|
52
|
+
let authMethod = null; // 'credentials', 'ucan', 'seed'
|
|
53
|
+
|
|
54
|
+
// UI state
|
|
55
|
+
let isLoading = false;
|
|
56
|
+
let status = "";
|
|
57
|
+
let error = null;
|
|
58
|
+
let success = null;
|
|
59
|
+
let activeTab = 0; // 0: credentials, 1: ucan, 2: seed, 3: email
|
|
60
|
+
|
|
61
|
+
// Form data
|
|
62
|
+
let email = "";
|
|
63
|
+
let storachaKey = "";
|
|
64
|
+
let storachaProof = "";
|
|
65
|
+
let ucanToken = "";
|
|
66
|
+
let recipientKey = "";
|
|
67
|
+
let seedPhrase = "";
|
|
68
|
+
let seedPassword = "password";
|
|
69
|
+
let delegationToken = "";
|
|
70
|
+
let newSpaceName = "";
|
|
71
|
+
|
|
72
|
+
// LocalStorage keys
|
|
73
|
+
const STORAGE_KEYS = {
|
|
74
|
+
STORACHA_KEY: "storacha_key",
|
|
75
|
+
STORACHA_PROOF: "storacha_proof",
|
|
76
|
+
UCAN_TOKEN: "storacha_ucan_token",
|
|
77
|
+
RECIPIENT_KEY: "storacha_recipient_key",
|
|
78
|
+
SEED_PHRASE: "storacha_seed_phrase",
|
|
79
|
+
SEED_PASSWORD: "storacha_seed_password",
|
|
80
|
+
DELEGATION_TOKEN: "storacha_delegation_token",
|
|
81
|
+
AUTH_METHOD: "storacha_auth_method",
|
|
82
|
+
AUTO_LOGIN: "storacha_auto_login",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Auto-hide messages
|
|
86
|
+
function showMessage(message, type = "info", duration = 5000) {
|
|
87
|
+
if (type === "error") {
|
|
88
|
+
error = message;
|
|
89
|
+
success = null;
|
|
90
|
+
} else {
|
|
91
|
+
success = message;
|
|
92
|
+
error = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
error = null;
|
|
97
|
+
success = null;
|
|
98
|
+
}, duration);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Save credentials to localStorage
|
|
102
|
+
function saveCredentials(method, data) {
|
|
103
|
+
try {
|
|
104
|
+
localStorage.setItem(STORAGE_KEYS.AUTH_METHOD, method);
|
|
105
|
+
localStorage.setItem(STORAGE_KEYS.AUTO_LOGIN, "true");
|
|
106
|
+
|
|
107
|
+
switch (method) {
|
|
108
|
+
case "credentials":
|
|
109
|
+
localStorage.setItem(STORAGE_KEYS.STORACHA_KEY, data.key);
|
|
110
|
+
localStorage.setItem(STORAGE_KEYS.STORACHA_PROOF, data.proof);
|
|
111
|
+
break;
|
|
112
|
+
case "ucan":
|
|
113
|
+
localStorage.setItem(STORAGE_KEYS.UCAN_TOKEN, data.ucanToken);
|
|
114
|
+
localStorage.setItem(STORAGE_KEYS.RECIPIENT_KEY, data.recipientKey);
|
|
115
|
+
break;
|
|
116
|
+
case "seed":
|
|
117
|
+
localStorage.setItem(STORAGE_KEYS.SEED_PHRASE, data.seedPhrase);
|
|
118
|
+
localStorage.setItem(STORAGE_KEYS.SEED_PASSWORD, data.seedPassword);
|
|
119
|
+
if (data.delegationToken) {
|
|
120
|
+
localStorage.setItem(
|
|
121
|
+
STORAGE_KEYS.DELEGATION_TOKEN,
|
|
122
|
+
data.delegationToken,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
logger.warn({ error: err.message }, "Failed to save credentials");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Load credentials from localStorage
|
|
133
|
+
function loadStoredCredentials() {
|
|
134
|
+
try {
|
|
135
|
+
const method = localStorage.getItem(STORAGE_KEYS.AUTH_METHOD);
|
|
136
|
+
const autoLoginEnabled =
|
|
137
|
+
localStorage.getItem(STORAGE_KEYS.AUTO_LOGIN) === "true";
|
|
138
|
+
|
|
139
|
+
if (!method || !autoLoginEnabled) return null;
|
|
140
|
+
|
|
141
|
+
switch (method) {
|
|
142
|
+
case "credentials": {
|
|
143
|
+
const key = localStorage.getItem(STORAGE_KEYS.STORACHA_KEY);
|
|
144
|
+
const proof = localStorage.getItem(STORAGE_KEYS.STORACHA_PROOF);
|
|
145
|
+
if (key && proof) return { method, key, proof };
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "ucan": {
|
|
149
|
+
const ucanToken = localStorage.getItem(STORAGE_KEYS.UCAN_TOKEN);
|
|
150
|
+
const recipientKey = localStorage.getItem(STORAGE_KEYS.RECIPIENT_KEY);
|
|
151
|
+
if (ucanToken && recipientKey)
|
|
152
|
+
return { method, ucanToken, recipientKey };
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "seed": {
|
|
156
|
+
const seedPhrase = localStorage.getItem(STORAGE_KEYS.SEED_PHRASE);
|
|
157
|
+
const seedPassword = localStorage.getItem(STORAGE_KEYS.SEED_PASSWORD);
|
|
158
|
+
const delegationToken = localStorage.getItem(
|
|
159
|
+
STORAGE_KEYS.DELEGATION_TOKEN,
|
|
160
|
+
);
|
|
161
|
+
if (seedPhrase && seedPassword)
|
|
162
|
+
return { method, seedPhrase, seedPassword, delegationToken };
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
logger.warn({ error: err.message }, "Failed to load credentials");
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Clear stored credentials
|
|
173
|
+
function clearStoredCredentials() {
|
|
174
|
+
try {
|
|
175
|
+
Object.values(STORAGE_KEYS).forEach((key) => {
|
|
176
|
+
localStorage.removeItem(key);
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
logger.warn({ error: err.message }, "Failed to clear credentials");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Authentication methods
|
|
184
|
+
async function authenticateWithCredentials() {
|
|
185
|
+
if (!storachaKey.trim() || !storachaProof.trim()) {
|
|
186
|
+
showMessage("Please provide both Storacha key and proof", "error");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
isLoading = true;
|
|
191
|
+
status = "Authenticating with credentials...";
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
client = await initializeStorachaClient(
|
|
195
|
+
storachaKey.trim(),
|
|
196
|
+
storachaProof.trim(),
|
|
197
|
+
);
|
|
198
|
+
authMethod = "credentials";
|
|
199
|
+
|
|
200
|
+
await loadSpaces();
|
|
201
|
+
|
|
202
|
+
saveCredentials("credentials", {
|
|
203
|
+
key: storachaKey.trim(),
|
|
204
|
+
proof: storachaProof.trim(),
|
|
205
|
+
});
|
|
206
|
+
isAuthenticated = true;
|
|
207
|
+
|
|
208
|
+
showMessage("Successfully authenticated with Storacha credentials!");
|
|
209
|
+
dispatch("authenticated", { client, method: "credentials", spaces });
|
|
210
|
+
} catch (err) {
|
|
211
|
+
showMessage(`Authentication failed: ${err.message}`, "error");
|
|
212
|
+
} finally {
|
|
213
|
+
isLoading = false;
|
|
214
|
+
status = "";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function authenticateWithUCAN() {
|
|
219
|
+
if (!ucanToken.trim() || !recipientKey.trim()) {
|
|
220
|
+
showMessage("Please provide both UCAN token and recipient key", "error");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
isLoading = true;
|
|
225
|
+
status = "Authenticating with UCAN...";
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
client = await initializeStorachaClientWithUCAN(
|
|
229
|
+
ucanToken.trim(),
|
|
230
|
+
recipientKey.trim(),
|
|
231
|
+
);
|
|
232
|
+
authMethod = "ucan";
|
|
233
|
+
|
|
234
|
+
await loadSpaces();
|
|
235
|
+
|
|
236
|
+
saveCredentials("ucan", {
|
|
237
|
+
ucanToken: ucanToken.trim(),
|
|
238
|
+
recipientKey: recipientKey.trim(),
|
|
239
|
+
});
|
|
240
|
+
isAuthenticated = true;
|
|
241
|
+
|
|
242
|
+
showMessage("Successfully authenticated with UCAN delegation!");
|
|
243
|
+
dispatch("authenticated", { client, method: "ucan", spaces });
|
|
244
|
+
} catch (err) {
|
|
245
|
+
showMessage(`UCAN authentication failed: ${err.message}`, "error");
|
|
246
|
+
} finally {
|
|
247
|
+
isLoading = false;
|
|
248
|
+
status = "";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function authenticateWithSeed() {
|
|
253
|
+
if (!seedPhrase.trim()) {
|
|
254
|
+
showMessage("Please provide a seed phrase", "error");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!validateSeedPhrase(seedPhrase.trim())) {
|
|
259
|
+
showMessage("Invalid seed phrase format", "error");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
isLoading = true;
|
|
264
|
+
status = "Authenticating with seed phrase...";
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const result = await initializeStorachaClientWithSeed(
|
|
268
|
+
seedPhrase.trim(),
|
|
269
|
+
seedPassword.trim() || "password",
|
|
270
|
+
delegationToken.trim() || null,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
client = result.client;
|
|
274
|
+
authMethod = "seed";
|
|
275
|
+
|
|
276
|
+
await loadSpaces();
|
|
277
|
+
|
|
278
|
+
saveCredentials("seed", {
|
|
279
|
+
seedPhrase: seedPhrase.trim(),
|
|
280
|
+
seedPassword: seedPassword.trim() || "password",
|
|
281
|
+
delegationToken: delegationToken.trim(),
|
|
282
|
+
});
|
|
283
|
+
isAuthenticated = true;
|
|
284
|
+
|
|
285
|
+
const message = delegationToken.trim()
|
|
286
|
+
? "Successfully authenticated with seed phrase and delegation!"
|
|
287
|
+
: "Authenticated with seed phrase (limited functionality without delegation)";
|
|
288
|
+
showMessage(message);
|
|
289
|
+
dispatch("authenticated", {
|
|
290
|
+
client,
|
|
291
|
+
method: "seed",
|
|
292
|
+
identity: result.identity,
|
|
293
|
+
spaces,
|
|
294
|
+
});
|
|
295
|
+
} catch (err) {
|
|
296
|
+
showMessage(`Seed authentication failed: ${err.message}`, "error");
|
|
297
|
+
} finally {
|
|
298
|
+
isLoading = false;
|
|
299
|
+
status = "";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function createAccount() {
|
|
304
|
+
if (!email.trim()) {
|
|
305
|
+
showMessage("Please enter your email address", "error");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
isLoading = true;
|
|
310
|
+
status = "Creating account...";
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const result = await createStorachaAccount(email.trim());
|
|
314
|
+
if (result.success) {
|
|
315
|
+
showMessage(result.message);
|
|
316
|
+
} else {
|
|
317
|
+
showMessage(result.error, "error");
|
|
318
|
+
}
|
|
319
|
+
} catch (err) {
|
|
320
|
+
showMessage(`Account creation failed: ${err.message}`, "error");
|
|
321
|
+
} finally {
|
|
322
|
+
isLoading = false;
|
|
323
|
+
status = "";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Space management
|
|
328
|
+
async function loadSpaces() {
|
|
329
|
+
if (!client) return;
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
spaces = await listSpaces(client);
|
|
333
|
+
currentSpace = client.currentSpace();
|
|
334
|
+
} catch (err) {
|
|
335
|
+
logger.warn({ error: err.message }, "Failed to load spaces");
|
|
336
|
+
spaces = [];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function createNewSpace() {
|
|
341
|
+
if (!newSpaceName.trim()) {
|
|
342
|
+
showMessage("Please enter a space name", "error");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
isLoading = true;
|
|
347
|
+
status = "Creating space...";
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const result = await createSpace(client, newSpaceName.trim());
|
|
351
|
+
if (result.success) {
|
|
352
|
+
showMessage(`Space "${newSpaceName}" created successfully!`);
|
|
353
|
+
newSpaceName = "";
|
|
354
|
+
await loadSpaces();
|
|
355
|
+
} else {
|
|
356
|
+
showMessage(result.error, "error");
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
showMessage(`Failed to create space: ${err.message}`, "error");
|
|
360
|
+
} finally {
|
|
361
|
+
isLoading = false;
|
|
362
|
+
status = "";
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function selectSpace(space) {
|
|
367
|
+
try {
|
|
368
|
+
await client.setCurrentSpace(space.did);
|
|
369
|
+
currentSpace = space;
|
|
370
|
+
showMessage(`Switched to space: ${space.name}`);
|
|
371
|
+
dispatch("spaceChanged", { space });
|
|
372
|
+
} catch (err) {
|
|
373
|
+
showMessage(`Failed to switch space: ${err.message}`, "error");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Logout
|
|
378
|
+
function logout() {
|
|
379
|
+
isAuthenticated = false;
|
|
380
|
+
client = null;
|
|
381
|
+
currentSpace = null;
|
|
382
|
+
spaces = [];
|
|
383
|
+
authMethod = null;
|
|
384
|
+
clearStoredCredentials();
|
|
385
|
+
clearForms();
|
|
386
|
+
showMessage("Logged out successfully");
|
|
387
|
+
dispatch("logout");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Clear forms
|
|
391
|
+
function clearForms() {
|
|
392
|
+
email = "";
|
|
393
|
+
storachaKey = "";
|
|
394
|
+
storachaProof = "";
|
|
395
|
+
ucanToken = "";
|
|
396
|
+
recipientKey = "";
|
|
397
|
+
seedPhrase = "";
|
|
398
|
+
seedPassword = "password";
|
|
399
|
+
delegationToken = "";
|
|
400
|
+
newSpaceName = "";
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Helper function to get correct tab index based on enabled auth methods
|
|
404
|
+
function getTabIndex(method) {
|
|
405
|
+
const tabs = ['credentials', 'ucan'];
|
|
406
|
+
if (enableSeedAuth) tabs.push('seed');
|
|
407
|
+
if (enableEmailAuth) tabs.push('email');
|
|
408
|
+
return tabs.indexOf(method);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Utility functions
|
|
412
|
+
function generateNewSeedPhrase() {
|
|
413
|
+
seedPhrase = generateSeedPhrase();
|
|
414
|
+
showMessage("New seed phrase generated!");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Auto-login on mount
|
|
418
|
+
onMount(async () => {
|
|
419
|
+
if (!autoLogin) return;
|
|
420
|
+
|
|
421
|
+
const stored = loadStoredCredentials();
|
|
422
|
+
if (!stored) return;
|
|
423
|
+
|
|
424
|
+
logger.info({ method: stored.method }, `🔄 Auto-login with ${stored.method}...`);
|
|
425
|
+
|
|
426
|
+
// Check if the stored method is enabled
|
|
427
|
+
const tabIndex = getTabIndex(stored.method);
|
|
428
|
+
if (tabIndex === -1) {
|
|
429
|
+
logger.warn({ method: stored.method }, `Stored auth method '${stored.method}' is disabled`);
|
|
430
|
+
clearStoredCredentials();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Set form values
|
|
435
|
+
switch (stored.method) {
|
|
436
|
+
case "credentials":
|
|
437
|
+
storachaKey = stored.key;
|
|
438
|
+
storachaProof = stored.proof;
|
|
439
|
+
activeTab = tabIndex;
|
|
440
|
+
await authenticateWithCredentials();
|
|
441
|
+
break;
|
|
442
|
+
case "ucan":
|
|
443
|
+
ucanToken = stored.ucanToken;
|
|
444
|
+
recipientKey = stored.recipientKey;
|
|
445
|
+
activeTab = tabIndex;
|
|
446
|
+
await authenticateWithUCAN();
|
|
447
|
+
break;
|
|
448
|
+
case "seed":
|
|
449
|
+
if (!enableSeedAuth) {
|
|
450
|
+
logger.warn("Seed auth is disabled");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
seedPhrase = stored.seedPhrase;
|
|
454
|
+
seedPassword = stored.seedPassword;
|
|
455
|
+
delegationToken = stored.delegationToken || "";
|
|
456
|
+
activeTab = tabIndex;
|
|
457
|
+
await authenticateWithSeed();
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Export authentication status for parent components
|
|
463
|
+
export { isAuthenticated, client, currentSpace, spaces, authMethod };
|
|
464
|
+
</script>
|
|
465
|
+
|
|
466
|
+
<Tile>
|
|
467
|
+
<!-- Header -->
|
|
468
|
+
{#if showTitle}
|
|
469
|
+
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:1rem;">
|
|
470
|
+
<Cloud size={20} />
|
|
471
|
+
<h3 style="font-size:1.125rem;font-weight:600;margin:0;">
|
|
472
|
+
Storacha Authentication
|
|
473
|
+
</h3>
|
|
474
|
+
</div>
|
|
475
|
+
{/if}
|
|
476
|
+
|
|
477
|
+
<!-- Status Messages -->
|
|
478
|
+
{#if error}
|
|
479
|
+
<InlineNotification
|
|
480
|
+
kind="error"
|
|
481
|
+
title="Error"
|
|
482
|
+
subtitle={error}
|
|
483
|
+
style="margin-bottom:1rem;"
|
|
484
|
+
/>
|
|
485
|
+
{/if}
|
|
486
|
+
|
|
487
|
+
{#if success}
|
|
488
|
+
<InlineNotification
|
|
489
|
+
kind="success"
|
|
490
|
+
title="Success"
|
|
491
|
+
subtitle={success}
|
|
492
|
+
style="margin-bottom:1rem;"
|
|
493
|
+
/>
|
|
494
|
+
{/if}
|
|
495
|
+
|
|
496
|
+
{#if isLoading && status}
|
|
497
|
+
<InlineNotification
|
|
498
|
+
kind="info"
|
|
499
|
+
title="Processing"
|
|
500
|
+
subtitle={status}
|
|
501
|
+
style="margin-bottom:1rem;"
|
|
502
|
+
>
|
|
503
|
+
<Loading withOverlay={false} small />
|
|
504
|
+
</InlineNotification>
|
|
505
|
+
{/if}
|
|
506
|
+
|
|
507
|
+
{#if !isAuthenticated}
|
|
508
|
+
<!-- Authentication Tabs -->
|
|
509
|
+
<Tabs bind:selected={activeTab}>
|
|
510
|
+
<Tab>
|
|
511
|
+
<div style="display:flex;align-items:center;gap:0.25rem;">
|
|
512
|
+
<Key size={16} />
|
|
513
|
+
Credentials
|
|
514
|
+
</div>
|
|
515
|
+
</Tab>
|
|
516
|
+
<Tab>
|
|
517
|
+
<div style="display:flex;align-items:center;gap:0.25rem;">🎫 UCAN</div>
|
|
518
|
+
</Tab>
|
|
519
|
+
{#if enableSeedAuth}
|
|
520
|
+
<Tab>
|
|
521
|
+
<div style="display:flex;align-items:center;gap:0.25rem;">
|
|
522
|
+
<Shuffle size={16} />
|
|
523
|
+
Seed
|
|
524
|
+
</div>
|
|
525
|
+
</Tab>
|
|
526
|
+
{/if}
|
|
527
|
+
{#if enableEmailAuth}
|
|
528
|
+
<Tab>
|
|
529
|
+
<div style="display:flex;align-items:center;gap:0.25rem;">
|
|
530
|
+
<Email size={16} />
|
|
531
|
+
Email
|
|
532
|
+
</div>
|
|
533
|
+
</Tab>
|
|
534
|
+
{/if}
|
|
535
|
+
|
|
536
|
+
<svelte:fragment slot="content">
|
|
537
|
+
<TabContent>
|
|
538
|
+
<!-- Credentials Tab -->
|
|
539
|
+
<div style="margin-top:1rem;">
|
|
540
|
+
<h4 style="font-weight:500;margin-bottom:1rem;">
|
|
541
|
+
Storacha Key & Proof
|
|
542
|
+
</h4>
|
|
543
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
544
|
+
<PasswordInput
|
|
545
|
+
labelText="Private Key"
|
|
546
|
+
placeholder="MgCZ9...your-storacha-private-key"
|
|
547
|
+
bind:value={storachaKey}
|
|
548
|
+
disabled={isLoading}
|
|
549
|
+
tooltipAlignment="center"
|
|
550
|
+
tooltipPosition="bottom"
|
|
551
|
+
/>
|
|
552
|
+
<TextArea
|
|
553
|
+
labelText="Proof (Delegation)"
|
|
554
|
+
placeholder="uCAIS...your-storacha-proof"
|
|
555
|
+
rows={3}
|
|
556
|
+
bind:value={storachaProof}
|
|
557
|
+
disabled={isLoading}
|
|
558
|
+
/>
|
|
559
|
+
<Button
|
|
560
|
+
on:click={authenticateWithCredentials}
|
|
561
|
+
disabled={isLoading ||
|
|
562
|
+
!storachaKey.trim() ||
|
|
563
|
+
!storachaProof.trim()}
|
|
564
|
+
style="width:100%;"
|
|
565
|
+
>
|
|
566
|
+
{isLoading ? "Authenticating..." : "Login with Credentials"}
|
|
567
|
+
</Button>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
</TabContent>
|
|
571
|
+
|
|
572
|
+
<TabContent>
|
|
573
|
+
<!-- UCAN Tab -->
|
|
574
|
+
<div style="margin-top:1rem;">
|
|
575
|
+
<h4 style="font-weight:500;margin-bottom:1rem;">UCAN Delegation</h4>
|
|
576
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
577
|
+
<TextArea
|
|
578
|
+
labelText="UCAN Token (Base64)"
|
|
579
|
+
placeholder="eyJh...base64-encoded-ucan-token"
|
|
580
|
+
rows={3}
|
|
581
|
+
bind:value={ucanToken}
|
|
582
|
+
disabled={isLoading}
|
|
583
|
+
/>
|
|
584
|
+
<TextArea
|
|
585
|
+
labelText="Recipient Key (JSON)"
|
|
586
|
+
placeholder="did key as json"
|
|
587
|
+
rows={3}
|
|
588
|
+
bind:value={recipientKey}
|
|
589
|
+
disabled={isLoading}
|
|
590
|
+
/>
|
|
591
|
+
<Button
|
|
592
|
+
kind="secondary"
|
|
593
|
+
on:click={authenticateWithUCAN}
|
|
594
|
+
disabled={isLoading ||
|
|
595
|
+
!ucanToken.trim() ||
|
|
596
|
+
!recipientKey.trim()}
|
|
597
|
+
style="width:100%;"
|
|
598
|
+
>
|
|
599
|
+
{isLoading ? "Authenticating..." : "Login with UCAN"}
|
|
600
|
+
</Button>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
</TabContent>
|
|
604
|
+
|
|
605
|
+
{#if enableSeedAuth}
|
|
606
|
+
<TabContent>
|
|
607
|
+
<!-- Seed Tab -->
|
|
608
|
+
<div style="margin-top:1rem;">
|
|
609
|
+
<h4 style="font-weight:500;margin-bottom:1rem;">Seed Phrase</h4>
|
|
610
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
611
|
+
<div style="display:flex;flex-direction:column;gap:0.5rem;">
|
|
612
|
+
<div
|
|
613
|
+
style="display:flex;justify-content:space-between;align-items:center;"
|
|
614
|
+
>
|
|
615
|
+
<span style="font-size:0.875rem;font-weight:500;"
|
|
616
|
+
>Seed Phrase (12-24 words)</span
|
|
617
|
+
>
|
|
618
|
+
<Button
|
|
619
|
+
kind="ghost"
|
|
620
|
+
size="sm"
|
|
621
|
+
icon={Reset}
|
|
622
|
+
on:click={generateNewSeedPhrase}
|
|
623
|
+
disabled={isLoading}
|
|
624
|
+
>
|
|
625
|
+
Generate
|
|
626
|
+
</Button>
|
|
627
|
+
</div>
|
|
628
|
+
<TextArea
|
|
629
|
+
placeholder="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art"
|
|
630
|
+
rows={2}
|
|
631
|
+
bind:value={seedPhrase}
|
|
632
|
+
disabled={isLoading}
|
|
633
|
+
/>
|
|
634
|
+
</div>
|
|
635
|
+
<PasswordInput
|
|
636
|
+
labelText="Password (optional)"
|
|
637
|
+
placeholder="password"
|
|
638
|
+
bind:value={seedPassword}
|
|
639
|
+
disabled={isLoading}
|
|
640
|
+
/>
|
|
641
|
+
<div style="display:flex;flex-direction:column;gap:0.5rem;">
|
|
642
|
+
<TextArea
|
|
643
|
+
labelText="Delegation Token (optional)"
|
|
644
|
+
placeholder="eyJh...base64-encoded-delegation-token"
|
|
645
|
+
rows={2}
|
|
646
|
+
bind:value={delegationToken}
|
|
647
|
+
disabled={isLoading}
|
|
648
|
+
/>
|
|
649
|
+
<p
|
|
650
|
+
style="font-size:0.75rem;color:var(--cds-text-secondary);margin:0;"
|
|
651
|
+
>
|
|
652
|
+
Without delegation, you can only create identities but not
|
|
653
|
+
upload to Storacha
|
|
654
|
+
</p>
|
|
655
|
+
</div>
|
|
656
|
+
<Button
|
|
657
|
+
kind="tertiary"
|
|
658
|
+
on:click={authenticateWithSeed}
|
|
659
|
+
disabled={isLoading || !seedPhrase.trim()}
|
|
660
|
+
style="width:100%;"
|
|
661
|
+
>
|
|
662
|
+
{isLoading ? "Authenticating..." : "Login with Seed"}
|
|
663
|
+
</Button>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
</TabContent>
|
|
667
|
+
{/if}
|
|
668
|
+
|
|
669
|
+
{#if enableEmailAuth}
|
|
670
|
+
<TabContent>
|
|
671
|
+
<!-- Email Tab -->
|
|
672
|
+
<div style="margin-top:1rem;">
|
|
673
|
+
<h4 style="font-weight:500;margin-bottom:1rem;">Create Account</h4>
|
|
674
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
675
|
+
<TextInput
|
|
676
|
+
labelText="Email Address"
|
|
677
|
+
placeholder="Enter your email address"
|
|
678
|
+
bind:value={email}
|
|
679
|
+
disabled={isLoading}
|
|
680
|
+
/>
|
|
681
|
+
<Button
|
|
682
|
+
kind="primary"
|
|
683
|
+
on:click={createAccount}
|
|
684
|
+
disabled={isLoading || !email.trim()}
|
|
685
|
+
style="width:100%;"
|
|
686
|
+
>
|
|
687
|
+
{isLoading ? "Creating..." : "Create Account"}
|
|
688
|
+
</Button>
|
|
689
|
+
<p
|
|
690
|
+
style="font-size:0.75rem;color:var(--cds-text-secondary);margin:0;"
|
|
691
|
+
>
|
|
692
|
+
This will guide you through manual account creation at
|
|
693
|
+
web3.storage
|
|
694
|
+
</p>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
</TabContent>
|
|
698
|
+
{/if}
|
|
699
|
+
</svelte:fragment>
|
|
700
|
+
</Tabs>
|
|
701
|
+
{:else}
|
|
702
|
+
<!-- Authenticated State -->
|
|
703
|
+
<div
|
|
704
|
+
style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;"
|
|
705
|
+
>
|
|
706
|
+
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
707
|
+
<Tag type="green">
|
|
708
|
+
<CheckmarkFilled size={16} slot="icon" />
|
|
709
|
+
Connected via {authMethod}
|
|
710
|
+
</Tag>
|
|
711
|
+
{#if currentSpace}
|
|
712
|
+
<Tag type="outline">
|
|
713
|
+
Space: {currentSpace.name ||
|
|
714
|
+
currentSpace.did?.slice(-8) ||
|
|
715
|
+
"Unknown"}
|
|
716
|
+
</Tag>
|
|
717
|
+
{/if}
|
|
718
|
+
</div>
|
|
719
|
+
|
|
720
|
+
<Button
|
|
721
|
+
kind="ghost"
|
|
722
|
+
iconDescription="Logout"
|
|
723
|
+
icon={Logout}
|
|
724
|
+
on:click={logout}
|
|
725
|
+
size="sm"
|
|
726
|
+
>
|
|
727
|
+
Logout
|
|
728
|
+
</Button>
|
|
729
|
+
</div>
|
|
730
|
+
|
|
731
|
+
<!-- Space Management -->
|
|
732
|
+
{#if !compact}
|
|
733
|
+
<div
|
|
734
|
+
style="border-top:1px solid var(--cds-border-subtle);padding-top:1rem;"
|
|
735
|
+
>
|
|
736
|
+
<h4 style="margin:0 0 0.75rem 0;font-size:0.875rem;font-weight:600;">
|
|
737
|
+
Spaces ({spaces.length})
|
|
738
|
+
</h4>
|
|
739
|
+
|
|
740
|
+
<!-- Create New Space -->
|
|
741
|
+
<div
|
|
742
|
+
style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.75rem;"
|
|
743
|
+
>
|
|
744
|
+
<TextInput
|
|
745
|
+
labelText="New space name"
|
|
746
|
+
placeholder="New space name"
|
|
747
|
+
bind:value={newSpaceName}
|
|
748
|
+
style="flex:1;"
|
|
749
|
+
/>
|
|
750
|
+
<Button
|
|
751
|
+
kind="primary"
|
|
752
|
+
on:click={createNewSpace}
|
|
753
|
+
disabled={isLoading || !newSpaceName.trim()}
|
|
754
|
+
size="sm"
|
|
755
|
+
>
|
|
756
|
+
Add
|
|
757
|
+
</Button>
|
|
758
|
+
</div>
|
|
759
|
+
|
|
760
|
+
<!-- Spaces List -->
|
|
761
|
+
{#if spaces.length === 0}
|
|
762
|
+
<InlineNotification
|
|
763
|
+
kind="info"
|
|
764
|
+
title="No spaces"
|
|
765
|
+
subtitle="No spaces found"
|
|
766
|
+
/>
|
|
767
|
+
{:else}
|
|
768
|
+
<div
|
|
769
|
+
style="max-height:10rem;overflow:auto;display:flex;flex-direction:column;gap:0.5rem;"
|
|
770
|
+
>
|
|
771
|
+
{#each spaces as space (space.did)}
|
|
772
|
+
<div
|
|
773
|
+
style="display:flex;align-items:center;justify-content:space-between;border:1px solid var(--cds-border-subtle);padding:0.5rem;border-radius:0.25rem;background:var(--cds-layer);"
|
|
774
|
+
>
|
|
775
|
+
<div>
|
|
776
|
+
<div style="font-size:0.9rem;font-weight:500;">
|
|
777
|
+
{space.name || "Unnamed Space"}
|
|
778
|
+
</div>
|
|
779
|
+
<div
|
|
780
|
+
style="font-family:monospace;font-size:0.75rem;color:var(--cds-text-secondary);"
|
|
781
|
+
>
|
|
782
|
+
{space.did.slice(0, 20)}...
|
|
783
|
+
</div>
|
|
784
|
+
</div>
|
|
785
|
+
{#if currentSpace?.did !== space.did}
|
|
786
|
+
<Button
|
|
787
|
+
kind="ghost"
|
|
788
|
+
size="sm"
|
|
789
|
+
on:click={() => selectSpace(space)}>Select</Button
|
|
790
|
+
>
|
|
791
|
+
{:else}
|
|
792
|
+
<Tag type="green">Current</Tag>
|
|
793
|
+
{/if}
|
|
794
|
+
</div>
|
|
795
|
+
{/each}
|
|
796
|
+
</div>
|
|
797
|
+
{/if}
|
|
798
|
+
</div>
|
|
799
|
+
{/if}
|
|
800
|
+
{/if}
|
|
801
|
+
</Tile>
|