ns-auth-sdk 1.8.0 → 1.9.1

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/README.md CHANGED
@@ -12,12 +12,15 @@ Open‑source, client‑side, decentralized single‑sign‑on (SSO) like NSAuth
12
12
  ## Technology
13
13
 
14
14
  ### Choose your Backend
15
- NSAuth is designed as a frontend SSO where data can be synced in a trust minimized way. The basics are there for extensions to interoperate with Farcaster, Nostr, Ethereum, Solana or even regular webservers.
15
+ NSAuth is designed as a frontend SSO where data can be synced in a trust minimized way. The basics are there for extensions to interoperate with public or private DLT networks or even regular webservers.
16
16
 
17
17
  ### Two Approaches
18
18
  #### PRF Direct Method
19
19
  Derive the private key directly from the PRF value produced by a passkey.
20
20
 
21
+ #### Password Fallback Method
22
+ If PRF is not supported (e.g., on older browsers or devices without WebAuthn), the SDK automatically falls back to a password-protected key. The user's password encrypts the private key using AES-256-GCM, and the encrypted bundle is stored locally. The password is never stored - it's only used to derive the encryption key on-demand.
23
+
21
24
  #### Encryption Method
22
25
  Encrypt an existing private key with a key derived from the passkey’s PRF output. WebAuthn PRF Extension The PRF (Pseudo‑Random Function) extension, part of WebAuthn Level 3, yields deterministic 32‑byte high‑entropy values from an authenticator’s internal private key and a supplied salt. The same credential ID and salt always generate the same PRF output, which never leaves the device except during authentication.
23
26
 
@@ -46,7 +49,7 @@ yarn add ns-auth-sdk
46
49
 
47
50
  ```typescript
48
51
  import { AuthService, RelayService } from 'ns-auth-sdk';
49
- import { EventStore } from 'applesauce-core';
52
+ import { EventStore } from 'ns-auth-sdk';
50
53
 
51
54
  // Initialize auth service
52
55
  const authService = new AuthService({
@@ -66,107 +69,56 @@ const eventStore = new EventStore(/* config */);
66
69
  relayService.initialize(eventStore);
67
70
  ```
68
71
 
69
- ### 2. Set Up Auth Store
72
+ ### 2. Create a Passkey
70
73
 
71
74
  ```typescript
72
- import { useAuthStore } from 'ns-auth-sdk';
73
-
74
- function App() {
75
- const publicKey = useAuthStore((state) => state.publicKey);
76
- const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
77
- const setAuthenticated = useAuthStore((state) => state.setAuthenticated);
78
-
79
- // Initialize auth on mount
80
- useAuthInit(authService, setAuthenticated);
81
-
82
- // ... rest of your app
83
- }
84
- ```
85
-
86
- ### 3. Use Components
75
+ // Create a passkey (triggers biometric)
76
+ const credentialId = await authService.createPasskey('user@example.com');
87
77
 
88
- #### Registration Flow
78
+ // Create Nostr key - password required only if PRF unavailable
79
+ const prfSupported = await authService.checkPRFSupport();
80
+ const keyInfo = prfSupported
81
+ ? await authService.createNostrKey(credentialId)
82
+ : await authService.createNostrKey(credentialId, userPassword);
89
83
 
90
- ```typescript
91
- import { RegistrationFlow } from 'ns-auth-sdk';
92
- import { useRouter } from 'next/navigation'; // or your router
93
-
94
- function RegisterPage() {
95
- const router = useRouter();
96
-
97
- return (
98
- <RegistrationFlow
99
- authService={authService}
100
- setAuthenticated={useAuthStore((state) => state.setAuthenticated)}
101
- onSuccess={() => router.push('/profile')}
102
- />
103
- );
104
- }
84
+ // Store keyInfo for later use
85
+ authService.setCurrentKeyInfo(keyInfo);
105
86
  ```
106
87
 
107
- #### Login Button
88
+ ### 3. Sign Events
108
89
 
109
90
  ```typescript
110
- import { LoginButton } from 'ns-auth-sdk';
111
-
112
- function LoginPage() {
113
- return (
114
- <LoginButton
115
- authService={authService}
116
- setAuthenticated={useAuthStore((state) => state.setAuthenticated)}
117
- setLoginError={useAuthStore((state) => state.setLoginError)}
118
- onSuccess={() => router.push('/dashboard')}
119
- />
120
- );
121
- }
91
+ const event = {
92
+ kind: 1,
93
+ content: 'Hello Nostr!',
94
+ tags: [],
95
+ created_at: Math.floor(Date.now() / 1000),
96
+ };
97
+
98
+ // Sign - no password needed if key was cached at creation
99
+ const signed = await authService.signEvent(event);
122
100
  ```
123
101
 
124
- #### Profile Page
102
+ ### Password Fallback
103
+
104
+ When PRF is not supported by the browser or device, the SDK automatically uses password encryption. The password is required only at key creation time - subsequent signs use the cached key:
125
105
 
126
106
  ```typescript
127
- import { ProfilePage } from 'ns-auth-sdk';
128
-
129
- function ProfilePageComponent() {
130
- const publicKey = useAuthStore((state) => state.publicKey);
131
-
132
- return (
133
- <ProfilePage
134
- authService={authService}
135
- relayService={relayService}
136
- publicKey={publicKey}
137
- onUnauthenticated={() => router.push('/login')}
138
- onSuccess={() => router.push('/membership')}
139
- onRoleSuggestion={async (about) => {
140
- // Optional: Provide role suggestion callback
141
- const response = await fetch('/api/suggest-role', {
142
- method: 'POST',
143
- body: JSON.stringify({ about }),
144
- });
145
- const data = await response.json();
146
- return data.role || null;
147
- }}
148
- />
149
- );
150
- }
151
- ```
107
+ import { AuthService, checkPRFSupport } from 'ns-auth-sdk';
152
108
 
153
- #### Membership Management
109
+ // Check if password fallback is needed
110
+ const prfSupported = await checkPRFSupport();
154
111
 
155
- ```typescript
156
- import { MembershipPage } from 'ns-auth-sdk';
157
-
158
- function MembershipPageComponent() {
159
- const publicKey = useAuthStore((state) => state.publicKey);
160
-
161
- return (
162
- <MembershipPage
163
- authService={authService}
164
- relayService={relayService}
165
- publicKey={publicKey}
166
- onUnauthenticated={() => router.push('/login')}
167
- />
168
- );
112
+ if (!prfSupported) {
113
+ const password = await promptUserForPassword();
169
114
  }
115
+
116
+ // Create key - password required only when PRF unavailable
117
+ const keyInfo = await authService.createNostrKey(credentialId, password);
118
+
119
+ // First sign: password used to decrypt and cache key
120
+ // Subsequent signs: uses cached key (no password needed)
121
+ const signedEvent = await authService.signEvent(event);
170
122
  ```
171
123
 
172
124
  ## API Reference
@@ -174,19 +126,47 @@ function MembershipPageComponent() {
174
126
  #### Methods
175
127
 
176
128
  - `createPasskey(username?: string): Promise<Uint8Array>` - Create a new passkey
177
- - `createNostrKey(credentialId?: Uint8Array): Promise<NostrKeyInfo>` - Create Nostr key from passkey
129
+ - `createKey(credentialId?: Uint8Array, password?: string): Promise<KeyInfo>` - Create key from passkey (auto-detects PRF support, uses password fallback if needed)
178
130
  - `getPublicKey(): Promise<string>` - Get current public key
179
- - `signEvent(event: NostrEvent): Promise<NostrEvent>` - Sign a Nostr event
180
- - `getCurrentKeyInfo(): NostrKeyInfo | null` - Get current key info
181
- - `setCurrentKeyInfo(keyInfo: NostrKeyInfo): void` - Set current key info
131
+ - `signEvent(event: Event, options?: SignOptions): Promise<Event>` - Sign an event (password optional - only needed first time before key is cached)
132
+ - `getCurrentKeyInfo(): KeyInfo | null` - Get current key info
133
+ - `setCurrentKeyInfo(keyInfo: KeyInfo): void` - Set current key info
182
134
  - `hasKeyInfo(): boolean` - Check if key info exists
183
135
  - `clearStoredKeyInfo(): void` - Clear stored key info
184
- - `isPrfSupported(): Promise<boolean>` - Check if PRF is supported
136
+ - `checkPRFSupport(): Promise<boolean>` - Check if PRF is supported (returns false if password fallback needed)
137
+
138
+ #### Types
139
+
140
+ ```typescript
141
+ // Password-protected key bundle (used when PRF unavailable)
142
+ interface PasswordProtectedBundle {
143
+ publicKeySpkiBase64: string;
144
+ wrappedPrivateKeyBase64: string;
145
+ saltBase64: string;
146
+ ivBase64: string;
147
+ }
148
+
149
+ // KeyInfo with optional password fallback
150
+ interface KeyInfo {
151
+ credentialId: string;
152
+ pubkey: string;
153
+ salt: string;
154
+ username?: string;
155
+ passwordProtectedBundle?: PasswordProtectedBundle;
156
+ }
157
+
158
+ // Sign options with password support
159
+ interface SignOptions {
160
+ clearMemory?: boolean;
161
+ tags?: string[][];
162
+ password?: string;
163
+ }
164
+ ```
185
165
 
186
166
  #### Configuration Options
187
167
 
188
168
  ```typescript
189
- interface NosskeyManagerOptions {
169
+ interface KeyManagerOptions {
190
170
  cacheOptions?: {
191
171
  enabled: boolean;
192
172
  timeoutMs?: number;
@@ -208,43 +188,31 @@ interface NosskeyManagerOptions {
208
188
  **Cache Options:**
209
189
  - `enabled`: Enable/disable key caching
210
190
  - `timeoutMs`: Cache timeout in milliseconds (default: 30 minutes)
211
- - `cacheOnCreation`: When `true`, caches the key immediately after `createNostrKey()` to reduce biometric prompts from 2-3 to 1-2. This is enabled by default for better user experience.
191
+ - `cacheOnCreation`: When `true`, caches the key immediately after `createKey()` to reduce biometric prompts from 2-3 to 1-2. This is enabled by default for better user experience.
212
192
 
213
193
  ### RelayService
214
194
 
215
- Service for communicating with Nostr relays using applesauce-core.
195
+ Service for communicating with relays.
216
196
 
217
197
  #### Methods
218
198
 
219
199
  - `initialize(eventStore: EventStore): void` - Initialize with EventStore
220
200
  - `getRelays(): string[]` - Get current relay URLs
221
201
  - `setRelays(urls: string[]): void` - Set relay URLs
222
- - `publishEvent(event: NostrEvent, timeoutMs?: number): Promise<boolean>` - Publish event
202
+ - `publishEvent(event: Event, timeoutMs?: number): Promise<boolean>` - Publish event
223
203
  - `fetchProfile(pubkey: string): Promise<ProfileMetadata | null>` - Fetch profile
224
204
  - `fetchProfileRoleTag(pubkey: string): Promise<string | null>` - Fetch role tag
225
205
  - `fetchFollowList(pubkey: string): Promise<FollowEntry[]>` - Fetch follow list
226
206
  - `fetchMultipleProfiles(pubkeys: string[]): Promise<Map<string, ProfileMetadata>>` - Fetch multiple profiles
227
207
  - `queryProfiles(pubkeys?: string[], limit?: number): Promise<Map<string, ProfileMetadata>>` - Query profiles
228
- - `publishFollowList(pubkey: string, followList: FollowEntry[], signEvent: (event: NostrEvent) => Promise<NostrEvent>): Promise<boolean>` - Publish follow list
229
-
230
- ### Components
231
-
232
- All components are framework-agnostic React components that accept callback props for navigation.
233
-
234
- #### Common Props
235
-
236
- - `authService: AuthService` - Auth service instance
237
- - `relayService?: RelayService` - Relay service instance (required for profile/membership)
238
- - `publicKey?: string | null` - Current user's public key
239
- - `onSuccess?: () => void` - Success callback
240
- - `onUnauthenticated?: () => void` - Unauthenticated callback
208
+ - `publishFollowList(pubkey: string, followList: FollowEntry[], signEvent: (event: Event) => Promise<Event>): Promise<boolean>` - Publish follow list
241
209
 
242
210
  ## Integration with Applesauce
243
211
 
244
- This library is designed to work seamlessly with applesauce-core. The `RelayService` uses applesauce's `EventStore` for all relay operations.
212
+ This library is designed to work seamlessly with applesauce-core. The `RelayService` uses applesauce's `EventStore` for all relay operations. All applesauce-core exports are re-exported from `ns-auth-sdk` for convenience.
245
213
 
246
214
  ```typescript
247
- import { EventStore } from 'applesauce-core';
215
+ import { EventStore } from 'ns-auth-sdk';
248
216
  import { RelayService } from 'ns-auth-sdk';
249
217
 
250
218
  const eventStore = new EventStore({
@@ -255,6 +223,31 @@ const relayService = new RelayService();
255
223
  relayService.initialize(eventStore);
256
224
  ```
257
225
 
226
+ ### Event Support
227
+
228
+ The SDK helpers for building events:
229
+
230
+ ```typescript
231
+ import {
232
+ Helpers,
233
+ finalizeEvent,
234
+ getPublicKey,
235
+ generateSecretKey
236
+ } from 'ns-auth-sdk';
237
+
238
+ // Generate a new key pair
239
+ const sk = generateSecretKey();
240
+ const pubkey = getPublicKey(sk);
241
+
242
+ // Create and sign an event
243
+ const event = finalizeEvent({
244
+ kind: 0,
245
+ content: 'My Event',
246
+ created_at: Math.floor(Date.now() / 1000),
247
+ tags: [],
248
+ }, sk);
249
+ ```
250
+
258
251
  ## Security Guidance
259
252
 
260
253
  - Configure a strict Content Security Policy (CSP) in the host app to restrict script and image sources.