local-first-auth-import-export 0.1.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.
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Local First Auth Import/Export Types
3
+ * Compatible with the Local First Auth Specification
4
+ */
5
+ interface SocialLink {
6
+ platform: SocialPlatform;
7
+ handle: string;
8
+ }
9
+ type SocialPlatform = 'INSTAGRAM' | 'X' | 'BLUESKY' | 'LINKEDIN' | 'YOUTUBE' | 'SPOTIFY' | 'TIKTOK' | 'SNAPCHAT' | 'GITHUB' | 'FACEBOOK' | 'REDDIT' | 'DISCORD' | 'TWITCH' | 'TELEGRAM' | 'PINTEREST' | 'TUMBLR' | 'SOUNDCLOUD' | 'BANDCAMP' | 'PATREON' | 'KO_FI' | 'MASTODON' | 'WEBSITE' | 'EMAIL';
10
+ interface Profile {
11
+ did: string;
12
+ name: string;
13
+ socials?: SocialLink[];
14
+ avatar?: string | null;
15
+ }
16
+ interface ProfileKeys {
17
+ did: string;
18
+ privateKey: string;
19
+ publicKey: string;
20
+ }
21
+ interface StoredProfile {
22
+ did: string;
23
+ name: string;
24
+ socials?: SocialLink[];
25
+ avatar?: string | null;
26
+ }
27
+ /**
28
+ * The magic string identifying a Local First Auth export file.
29
+ * Present as the `type` field so a consumer can cheaply confirm the format.
30
+ */
31
+ declare const EXPORT_FILE_TYPE: "local-first-auth:export";
32
+ /**
33
+ * The current export file schema version.
34
+ */
35
+ declare const EXPORT_FILE_VERSION: 1;
36
+ /**
37
+ * A Local First Auth profile export.
38
+ *
39
+ * This is the exact shape of the exported `.json` file. It is self-identifying
40
+ * (`type` + `version`) and internally consistent: `did` and `publicKey` MUST be
41
+ * re-derivable from `privateKey`, which lets any consumer validate the file
42
+ * without trusting its contents.
43
+ *
44
+ * SECURITY: this contains a plaintext private key. Anyone who has it can act as
45
+ * the user. Never transmit or log it.
46
+ */
47
+ interface ExportedProfile {
48
+ /** Fixed magic string identifying this as a Local First Auth export */
49
+ type: typeof EXPORT_FILE_TYPE;
50
+ /** Schema version, for forward compatibility */
51
+ version: typeof EXPORT_FILE_VERSION;
52
+ /** The user's DID (did:key:z...) */
53
+ did: string;
54
+ /** base64-encoded 32-byte Ed25519 public key */
55
+ publicKey: string;
56
+ /** base64-encoded 64-byte Ed25519 secret key */
57
+ privateKey: string;
58
+ /** Display name */
59
+ name?: string;
60
+ /** Social links */
61
+ socials?: SocialLink[];
62
+ /** Avatar as a data: URI */
63
+ avatar?: string | null;
64
+ /** ISO 8601 timestamp of when the file was exported */
65
+ exportedAt?: string;
66
+ }
67
+ /**
68
+ * Result of validating an untrusted value against the export file format.
69
+ */
70
+ interface ValidationResult {
71
+ valid: boolean;
72
+ errors: string[];
73
+ profile?: ExportedProfile;
74
+ }
75
+ /**
76
+ * Options for importing a profile.
77
+ */
78
+ interface ImportOptions {
79
+ /**
80
+ * Replace an existing profile with a different DID.
81
+ * Defaults to false — importing over a different identity throws unless this
82
+ * is explicitly set, to guard against accidentally destroying the user's keys.
83
+ */
84
+ overwrite?: boolean;
85
+ }
86
+ interface LocalFirstAuth {
87
+ getProfileDetails(): Promise<string>;
88
+ getAvatar(): Promise<string | null>;
89
+ getAppDetails(): AppDetails;
90
+ requestPermission(permission: string): Promise<boolean>;
91
+ close(): void;
92
+ }
93
+ interface AppDetails {
94
+ name: string;
95
+ version: string;
96
+ platform: 'ios' | 'android' | 'browser';
97
+ supportedPermissions: string[];
98
+ }
99
+ interface JWTHeader {
100
+ alg: 'EdDSA';
101
+ typ: 'JWT';
102
+ }
103
+ interface JWTPayload {
104
+ iss: string;
105
+ aud: string;
106
+ iat: number;
107
+ exp: number;
108
+ type: string;
109
+ data?: any;
110
+ }
111
+ interface CustomStyles {
112
+ primaryColor?: string;
113
+ backgroundColor?: string;
114
+ textColor?: string;
115
+ borderRadius?: string;
116
+ fontFamily?: string;
117
+ buttonRadius?: string;
118
+ inputRadius?: string;
119
+ inputTextColor?: string;
120
+ inputBackgroundColor?: string;
121
+ /** Scale factor for button press animation on mobile (0-1). Default: 0.95 */
122
+ mobileButtonPressScale?: number;
123
+ /** Color for mobile tap highlight. Default: 'transparent' */
124
+ mobileTapHighlightColor?: string;
125
+ /** Enable safe area insets for iOS notch / Android nav bars. Default: true */
126
+ useSafeAreaInsets?: boolean;
127
+ }
128
+ interface ExportProfileProps {
129
+ customStyles?: CustomStyles;
130
+ /** Called after the user downloads their profile backup */
131
+ onExport?: (profile: ExportedProfile) => void;
132
+ /** Rendered when no profile exists. Defaults to a short message. */
133
+ emptyState?: React.ReactNode;
134
+ }
135
+ interface ImportProfileProps {
136
+ customStyles?: CustomStyles;
137
+ /** Called with the imported profile on success */
138
+ onComplete?: (profile: Profile) => void;
139
+ onBack?: () => void;
140
+ }
141
+ interface ImportExportProps {
142
+ customStyles?: CustomStyles;
143
+ onComplete?: (profile: Profile) => void;
144
+ onExport?: (profile: ExportedProfile) => void;
145
+ /** Which tab is shown first. Default: 'import' */
146
+ defaultTab?: 'import' | 'export';
147
+ /** Hide the import tab */
148
+ skipImport?: boolean;
149
+ /** Hide the export tab */
150
+ skipExport?: boolean;
151
+ }
152
+
153
+ /**
154
+ * Cryptographic utilities for DID derivation and JWT signing.
155
+ *
156
+ * Mirrors local-first-auth's crypto layer so that keys/DIDs/JWTs produced here
157
+ * are byte-for-byte compatible, and adds the primitives needed to reconstruct an
158
+ * identity from an existing private key (rather than generating a new one).
159
+ */
160
+
161
+ /** Ed25519 secret key size in bytes (32-byte seed + 32-byte public key) */
162
+ declare const SECRET_KEY_SIZE = 64;
163
+ /** Ed25519 public key size in bytes */
164
+ declare const PUBLIC_KEY_SIZE = 32;
165
+ /**
166
+ * Helper to encode/decode base64url (RFC 4648)
167
+ */
168
+ declare const base64url: {
169
+ encode: (input: Uint8Array) => string;
170
+ decode: (input: string) => Uint8Array;
171
+ };
172
+ /**
173
+ * Create a did:key DID from an Ed25519 public key
174
+ *
175
+ * @param publicKey - The Ed25519 public key as Uint8Array (32 bytes)
176
+ * @returns The did:key formatted DID string
177
+ */
178
+ declare function createDidFromPublicKey(publicKey: Uint8Array): string;
179
+ /**
180
+ * Validate that a string is a usable Ed25519 private key.
181
+ *
182
+ * A valid key is base64 that decodes to exactly 64 bytes (the Ed25519 secret
183
+ * key: a 32-byte seed followed by the 32-byte public key).
184
+ */
185
+ declare function validatePrivateKey(privateKey: unknown): {
186
+ valid: boolean;
187
+ error?: string;
188
+ };
189
+ /**
190
+ * Reconstruct a full identity (DID + public key) from an existing private key.
191
+ *
192
+ * This is the core of "import": an Ed25519 secret key already contains its
193
+ * public key in the final 32 bytes, so the private key alone is enough to
194
+ * recover the complete identity. No new keypair is generated.
195
+ *
196
+ * @param privateKey - base64-encoded 64-byte Ed25519 secret key
197
+ * @returns The derived { did, publicKey, privateKey }
198
+ * @throws If the private key is malformed
199
+ */
200
+ declare function deriveKeysFromPrivateKey(privateKey: string): ProfileKeys;
201
+ /**
202
+ * Decode a base64-encoded public key back into bytes (useful for verifyJWT)
203
+ */
204
+ declare function decodePublicKey(publicKey: string): Uint8Array;
205
+ /**
206
+ * Create and sign a JWT using Ed25519
207
+ *
208
+ * @param payload - JWT payload containing claims
209
+ * @param privateKey - base64-encoded 64-byte Ed25519 secret key
210
+ * @returns Signed JWT string
211
+ */
212
+ declare function createJWT(payload: JWTPayload, privateKey: string): Promise<string>;
213
+ /**
214
+ * Decode a JWT (for debugging - does NOT verify the signature)
215
+ */
216
+ declare function decodeJWT(jwt: string): {
217
+ header: JWTHeader;
218
+ payload: JWTPayload;
219
+ signature: string;
220
+ };
221
+ /**
222
+ * Verify a JWT signature against an Ed25519 public key
223
+ *
224
+ * @param jwt - The JWT string to verify
225
+ * @param publicKey - The Ed25519 public key (32 bytes)
226
+ */
227
+ declare function verifyJWT(jwt: string, publicKey: Uint8Array): boolean;
228
+
229
+ /**
230
+ * LocalStorage wrapper for profile storage.
231
+ *
232
+ * IMPORTANT: these storage keys are intentionally identical to the ones used by
233
+ * the `local-first-auth` package. That is what makes the two interoperable — a
234
+ * profile created by local-first-auth's <Onboarding> can be exported here, and a
235
+ * profile imported here is immediately usable by local-first-auth's hooks and
236
+ * window API. Do not namespace these differently.
237
+ */
238
+
239
+ declare const STORAGE_KEYS: {
240
+ readonly PROFILE: "local-first-auth:profile";
241
+ readonly PRIVATE_KEY: "local-first-auth:privateKey";
242
+ };
243
+ /**
244
+ * Save profile to LocalStorage
245
+ */
246
+ declare function saveProfile(profile: StoredProfile): void;
247
+ /**
248
+ * Get profile from LocalStorage
249
+ */
250
+ declare function getProfile(): StoredProfile | null;
251
+ /**
252
+ * Save private key to LocalStorage
253
+ */
254
+ declare function savePrivateKey(privateKey: string): void;
255
+ /**
256
+ * Get private key from LocalStorage
257
+ */
258
+ declare function getPrivateKey(): string | null;
259
+ /**
260
+ * Clear all stored profile data
261
+ */
262
+ declare function clearProfile(): void;
263
+ /**
264
+ * Check if a profile exists in storage
265
+ */
266
+ declare function hasProfile(): boolean;
267
+ /**
268
+ * Get the current profile (requires both the profile and its private key)
269
+ */
270
+ declare function getCurrentProfile(): Profile | null;
271
+
272
+ /**
273
+ * Export the current profile (including its keypair) as a portable JSON file.
274
+ *
275
+ * SECURITY: the exported file contains a plaintext private key. Anyone who has
276
+ * it can act as the user. Surface a clear warning in your UI, and never send the
277
+ * file to a server or log it.
278
+ */
279
+
280
+ /**
281
+ * Build the exportable representation of the current profile.
282
+ *
283
+ * The DID and public key are re-derived from the stored private key rather than
284
+ * read from storage, so the resulting file is always internally consistent.
285
+ *
286
+ * @returns The ExportedProfile, or null if there is no profile to export.
287
+ */
288
+ declare function exportProfile(): ExportedProfile | null;
289
+ /**
290
+ * Serialize the current profile to a JSON string.
291
+ *
292
+ * @param pretty - Pretty-print the JSON (default true)
293
+ * @returns The JSON string, or null if there is no profile to export.
294
+ */
295
+ declare function exportProfileToJSON(pretty?: boolean): string | null;
296
+ /**
297
+ * Build a default filename for a profile backup, e.g.
298
+ * `local-first-auth-profile-z6MkhaXg.json`
299
+ */
300
+ declare function defaultExportFilename(did: string): string;
301
+ /**
302
+ * Download the current profile as a .json file in the browser.
303
+ *
304
+ * @param filename - Optional filename. Defaults to `local-first-auth-profile-<key>.json`
305
+ * @returns The exported profile, or null if there was nothing to export.
306
+ */
307
+ declare function downloadProfile(filename?: string): ExportedProfile | null;
308
+
309
+ /**
310
+ * Import an existing profile (public + private keypair) from an export file.
311
+ *
312
+ * Importing adopts an identity the user already has — no new keypair is minted.
313
+ * The private key is the source of truth: the DID and public key are always
314
+ * re-derived from it, never trusted from the file.
315
+ */
316
+
317
+ /**
318
+ * Validate an untrusted value against the Local First Auth export file format.
319
+ *
320
+ * Checks, in order:
321
+ * 1. It parses as an object (a string is JSON.parse'd first)
322
+ * 2. `type` is the `local-first-auth:export` magic string
323
+ * 3. `version` is a supported schema version
324
+ * 4. `privateKey` is base64 that decodes to exactly 64 bytes
325
+ * 5. `did` / `publicKey` are re-derivable from `privateKey` (consistency check)
326
+ * 6. `socials`, if present, is a well-formed array
327
+ *
328
+ * This never throws — inspect `valid` and `errors`.
329
+ */
330
+ declare function validateExportedProfile(input: unknown): ValidationResult;
331
+ /**
332
+ * Normalize any supported import input into a valid ExportedProfile.
333
+ *
334
+ * Accepts:
335
+ * - an `ExportedProfile` object
336
+ * - a JSON string of an export file
337
+ * - a bare base64 private key (convenience — the envelope is synthesized)
338
+ *
339
+ * @throws Error with a human-readable message if the input is unusable.
340
+ */
341
+ declare function parseExportedProfile(input: string | ExportedProfile): ExportedProfile;
342
+ /**
343
+ * Import a profile, adopting its existing identity.
344
+ *
345
+ * Saves the profile and private key to storage (the same keys local-first-auth
346
+ * uses) and injects `window.localFirstAuth`, so the app is immediately
347
+ * authenticated as the imported user.
348
+ *
349
+ * If a profile with a *different* DID already exists, this throws unless
350
+ * `overwrite: true` is passed — importing would otherwise silently destroy the
351
+ * user's existing keys.
352
+ *
353
+ * @param input - An export file (object or JSON string), or a bare private key
354
+ * @param options - `{ overwrite }` to replace an existing, different identity
355
+ */
356
+ declare function importProfile(input: string | ExportedProfile, options?: ImportOptions): Promise<Profile>;
357
+ /**
358
+ * Import a profile from a File (e.g. from an <input type="file"> or a drop).
359
+ */
360
+ declare function importProfileFromFile(file: File, options?: ImportOptions): Promise<Profile>;
361
+
362
+ /**
363
+ * Local First Auth API implementation.
364
+ *
365
+ * Implements the LocalFirstAuth interface from the Local First Auth Specification
366
+ * and injects it at window.localFirstAuth. Mirrors local-first-auth's api.ts so an
367
+ * imported profile behaves exactly like one created through onboarding.
368
+ */
369
+
370
+ declare class LocalFirstAuthAPI implements LocalFirstAuth {
371
+ /**
372
+ * Get profile details as a signed JWT
373
+ */
374
+ getProfileDetails(): Promise<string>;
375
+ /**
376
+ * Get avatar as base64-encoded string in a signed JWT
377
+ */
378
+ getAvatar(): Promise<string | null>;
379
+ getAppDetails(): AppDetails;
380
+ requestPermission(permission: string): Promise<boolean>;
381
+ close(): void;
382
+ }
383
+ /**
384
+ * Inject the Local First Auth API into the window object
385
+ */
386
+ declare function injectLocalFirstAuthAPI(): void;
387
+ /**
388
+ * Remove the Local First Auth API from the window object
389
+ */
390
+ declare function removeLocalFirstAuthAPI(): void;
391
+ /**
392
+ * Check if the Local First Auth API is available
393
+ */
394
+ declare function hasLocalFirstAuthAPI(): boolean;
395
+
396
+ export { type AppDetails, type CustomStyles, EXPORT_FILE_TYPE, EXPORT_FILE_VERSION, type ExportProfileProps, type ExportedProfile, type ImportExportProps, type ImportOptions, type ImportProfileProps, type JWTHeader, type JWTPayload, type LocalFirstAuth, LocalFirstAuthAPI, PUBLIC_KEY_SIZE, type Profile, type ProfileKeys, SECRET_KEY_SIZE, STORAGE_KEYS, type SocialLink, type SocialPlatform, type StoredProfile, type ValidationResult, base64url, clearProfile, createDidFromPublicKey, createJWT, decodeJWT, decodePublicKey, defaultExportFilename, deriveKeysFromPrivateKey, downloadProfile, exportProfile, exportProfileToJSON, getCurrentProfile, getPrivateKey, getProfile, hasLocalFirstAuthAPI, hasProfile, importProfile, importProfileFromFile, injectLocalFirstAuthAPI, parseExportedProfile, removeLocalFirstAuthAPI, savePrivateKey, saveProfile, validateExportedProfile, validatePrivateKey, verifyJWT };