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 +105 -112
- package/dist/index.cjs +782 -896
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +381 -99
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +381 -99
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +768 -865
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/index.css +0 -772
- package/dist/index.css.map +0 -1
- package/dist/utils-CD_Ibg98.cjs +0 -3
- package/dist/utils-Cu7K7sft.cjs +0 -539
- package/dist/utils-Cu7K7sft.cjs.map +0 -1
- package/dist/utils-DoinrauJ.mjs +0 -503
- package/dist/utils-DoinrauJ.mjs.map +0 -1
- package/dist/utils-SydXBP7f.mjs +0 -3
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
|
|
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 '
|
|
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.
|
|
72
|
+
### 2. Create a Passkey
|
|
70
73
|
|
|
71
74
|
```typescript
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
88
|
+
### 3. Sign Events
|
|
108
89
|
|
|
109
90
|
```typescript
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
109
|
+
// Check if password fallback is needed
|
|
110
|
+
const prfSupported = await checkPRFSupport();
|
|
154
111
|
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
- `
|
|
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:
|
|
180
|
-
- `getCurrentKeyInfo():
|
|
181
|
-
- `setCurrentKeyInfo(keyInfo:
|
|
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
|
-
- `
|
|
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
|
|
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 `
|
|
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
|
|
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:
|
|
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:
|
|
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 '
|
|
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.
|