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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/index.cjs +560 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +396 -0
- package/dist/index.d.ts +396 -0
- package/dist/index.js +492 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +1056 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +400 -0
- package/dist/react.d.ts +400 -0
- package/dist/react.js +991 -0
- package/dist/react.js.map +1 -0
- package/package.json +72 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|