@authon/js 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,8 +1,15 @@
1
+ **English** | [한국어](./README.ko.md)
2
+
1
3
  # @authon/js
2
4
 
3
- Core browser SDK for [Authon](https://authon.dev) — ShadowDOM login modal, OAuth flows, and session management.
5
+ [![npm version](https://img.shields.io/npm/v/@authon/js?color=6d28d9)](https://www.npmjs.com/package/@authon/js)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue)](../../LICENSE)
7
+
8
+ Core JavaScript SDK for [Authon](https://authon.dev). Works in any browser environment — no framework required.
9
+
10
+ Includes a built-in ShadowDOM sign-in modal, OAuth popup/redirect flows, passwordless auth, passkeys (WebAuthn), Web3 wallet login, TOTP-based MFA, and session management.
4
11
 
5
- ## Install
12
+ ## Installation
6
13
 
7
14
  ```bash
8
15
  npm install @authon/js
@@ -10,93 +17,443 @@ npm install @authon/js
10
17
  pnpm add @authon/js
11
18
  ```
12
19
 
13
- ## Quick Start
20
+ ## Initialization
14
21
 
15
22
  ```ts
16
23
  import { Authon } from '@authon/js';
17
24
 
18
25
  const authon = new Authon('pk_live_...');
26
+ ```
19
27
 
20
- // Open the sign-in modal
21
- await authon.openSignIn();
28
+ With options:
22
29
 
23
- // Listen for auth events
24
- authon.on('signedIn', (user) => {
25
- console.log('Signed in:', user.email);
30
+ ```ts
31
+ const authon = new Authon('pk_live_...', {
32
+ apiUrl: 'https://api.authon.dev', // default
33
+ mode: 'popup', // 'popup' | 'embedded'
34
+ theme: 'auto', // 'light' | 'dark' | 'auto'
35
+ locale: 'en',
36
+ containerId: 'auth-container', // element ID for embedded mode
37
+ appearance: { // override project branding
38
+ brandName: 'My App',
39
+ primaryColorStart: '#7c3aed',
40
+ primaryColorEnd: '#4f46e5',
41
+ borderRadius: 12,
42
+ },
26
43
  });
44
+ ```
27
45
 
28
- authon.on('signedOut', () => {
29
- console.log('Signed out');
46
+ ### Configuration Options
47
+
48
+ | Option | Type | Default | Description |
49
+ |--------|------|---------|-------------|
50
+ | `apiUrl` | `string` | `https://api.authon.dev` | Authon API base URL |
51
+ | `mode` | `'popup' \| 'embedded'` | `'popup'` | Modal display mode |
52
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | UI theme |
53
+ | `locale` | `string` | `'en'` | UI locale |
54
+ | `containerId` | `string` | — | Element ID for embedded mode |
55
+ | `appearance` | `Partial<BrandingConfig>` | — | Override branding from dashboard |
56
+
57
+ ## Built-in Modal
58
+
59
+ ```ts
60
+ // Open the Authon-hosted sign-in / sign-up modal
61
+ await authon.openSignIn();
62
+ await authon.openSignUp();
63
+ ```
64
+
65
+ The modal renders inside a ShadowRoot, preventing CSS conflicts with your app. Branding is fetched from your Authon project settings and can be overridden via `appearance`.
66
+
67
+ ## Email / Password
68
+
69
+ ```ts
70
+ // Sign up a new user
71
+ const user = await authon.signUpWithEmail('user@example.com', 'password', {
72
+ displayName: 'Alice',
30
73
  });
31
74
 
32
- // Email/password sign-in
75
+ // Sign in
33
76
  const user = await authon.signInWithEmail('user@example.com', 'password');
34
77
 
35
- // OAuth sign-in (uses dashboard default flow: auto | popup | redirect)
78
+ // Sign out
79
+ await authon.signOut();
80
+ ```
81
+
82
+ ## OAuth
83
+
84
+ ```ts
85
+ // Sign in with any supported provider
36
86
  await authon.signInWithOAuth('google');
87
+ await authon.signInWithOAuth('apple');
88
+ await authon.signInWithOAuth('github');
89
+ await authon.signInWithOAuth('discord');
90
+ await authon.signInWithOAuth('facebook');
91
+ await authon.signInWithOAuth('microsoft');
92
+ await authon.signInWithOAuth('kakao');
93
+ await authon.signInWithOAuth('naver');
94
+ await authon.signInWithOAuth('line');
95
+ await authon.signInWithOAuth('x');
37
96
 
38
- // Optional runtime override
97
+ // Override the flow mode per call
98
+ await authon.signInWithOAuth('google', { flowMode: 'popup' });
39
99
  await authon.signInWithOAuth('google', { flowMode: 'redirect' });
100
+ await authon.signInWithOAuth('google', { flowMode: 'auto' }); // default
101
+ ```
102
+
103
+ The `auto` mode opens a popup and automatically falls back to a redirect if the popup is blocked.
104
+
105
+ ## Passwordless
106
+
107
+ ```ts
108
+ // Magic link — sends a sign-in link to the user's email
109
+ await authon.sendMagicLink('user@example.com');
110
+
111
+ // Email OTP — sends a one-time code to the user's email
112
+ await authon.sendEmailOtp('user@example.com');
113
+
114
+ // Verify magic link (token comes from URL query param after user clicks link)
115
+ const user = await authon.verifyPasswordless({ token: 'token-from-url' });
116
+
117
+ // Verify email OTP
118
+ const user = await authon.verifyPasswordless({
119
+ email: 'user@example.com',
120
+ code: '123456',
121
+ });
122
+ ```
123
+
124
+ ## Passkeys (WebAuthn)
125
+
126
+ ```ts
127
+ // Register a new passkey — user must be signed in
128
+ const credential = await authon.registerPasskey('My MacBook');
129
+ // credential: { id, name, createdAt, lastUsedAt }
130
+
131
+ // Authenticate with a passkey (works before sign-in)
132
+ const user = await authon.authenticateWithPasskey();
133
+
134
+ // Restrict to passkeys registered for a specific email
135
+ const user = await authon.authenticateWithPasskey('user@example.com');
136
+
137
+ // List registered passkeys
138
+ const passkeys = await authon.listPasskeys();
139
+ // [{ id, name, createdAt, lastUsedAt }, ...]
140
+
141
+ // Rename a passkey
142
+ const updated = await authon.renamePasskey(passkeys[0].id, 'Work Laptop');
143
+
144
+ // Revoke (delete) a passkey
145
+ await authon.revokePasskey(passkeys[0].id);
146
+ ```
147
+
148
+ ## Web3
149
+
150
+ ```ts
151
+ // Step 1: Request a sign-in nonce for a wallet address
152
+ const { message, nonce } = await authon.web3GetNonce(
153
+ '0xAbc...123', // wallet address
154
+ 'evm', // 'evm' | 'solana'
155
+ 'metamask', // wallet type
156
+ 1, // chainId (optional, for EVM)
157
+ );
158
+
159
+ // Step 2: Sign the message with the wallet (MetaMask example)
160
+ const signature = await window.ethereum.request({
161
+ method: 'personal_sign',
162
+ params: [message, '0xAbc...123'],
163
+ });
164
+
165
+ // Step 3: Verify the signature to sign in
166
+ const user = await authon.web3Verify(
167
+ message,
168
+ signature,
169
+ '0xAbc...123',
170
+ 'evm',
171
+ 'metamask',
172
+ );
173
+
174
+ // Solana (Phantom) example
175
+ const { message } = await authon.web3GetNonce(
176
+ phantomPublicKey.toString(),
177
+ 'solana',
178
+ 'phantom',
179
+ );
180
+ const encodedMessage = new TextEncoder().encode(message);
181
+ const { signature } = await window.solana.signMessage(encodedMessage, 'utf8');
182
+ const user = await authon.web3Verify(
183
+ message,
184
+ bs58.encode(signature),
185
+ phantomPublicKey.toString(),
186
+ 'solana',
187
+ 'phantom',
188
+ );
189
+
190
+ // Link an additional wallet to the signed-in account
191
+ const wallet = await authon.linkWallet({
192
+ address: '0xDef...456',
193
+ chain: 'evm',
194
+ walletType: 'walletconnect',
195
+ chainId: 1,
196
+ message,
197
+ signature,
198
+ });
199
+ // wallet: { id, address, chain, walletType, chainId, createdAt }
200
+
201
+ // List linked wallets
202
+ const wallets = await authon.listWallets();
203
+
204
+ // Unlink a wallet
205
+ await authon.unlinkWallet(wallets[0].id);
206
+ ```
207
+
208
+ **Supported wallet types:** `'metamask'` | `'pexus'` | `'walletconnect'` | `'coinbase'` | `'phantom'` | `'trust'` | `'other'`
209
+
210
+ **Supported chains:** `'evm'` | `'solana'`
211
+
212
+ ## Multi-Factor Authentication (MFA)
213
+
214
+ Authon supports TOTP-based MFA compatible with Google Authenticator, Authy, and any other authenticator app.
215
+
216
+ ### Setup
217
+
218
+ ```ts
219
+ import { Authon } from '@authon/js';
220
+
221
+ // User must be signed in before calling setupMfa
222
+ const setup = await authon.setupMfa();
223
+ // setup.qrCodeSvg — inline SVG string, render with innerHTML or as <img src>
224
+ // setup.qrCodeUri — otpauth:// URI
225
+ // setup.secret — raw TOTP secret
226
+ // setup.backupCodes — one-time recovery codes (save these!)
227
+
228
+ // Render the QR code
229
+ document.querySelector('#qr-container').innerHTML = setup.qrCodeSvg;
230
+
231
+ // User scans QR code, then enters the 6-digit code to confirm setup
232
+ await authon.verifyMfaSetup('123456');
233
+ ```
40
234
 
41
- // Get current user and token
42
- const currentUser = authon.getUser();
235
+ ### Sign-In With MFA
236
+
237
+ When MFA is enabled, `signInWithEmail` throws `AuthonMfaRequiredError`:
238
+
239
+ ```ts
240
+ import { Authon, AuthonMfaRequiredError } from '@authon/js';
241
+
242
+ try {
243
+ await authon.signInWithEmail('user@example.com', 'password');
244
+ } catch (err) {
245
+ if (err instanceof AuthonMfaRequiredError) {
246
+ // Prompt the user for their TOTP code
247
+ const user = await authon.verifyMfa(err.mfaToken, '123456');
248
+ }
249
+ }
250
+ ```
251
+
252
+ Alternatively, use the event listener:
253
+
254
+ ```ts
255
+ authon.on('mfaRequired', async (mfaToken) => {
256
+ const code = prompt('Enter your authenticator code');
257
+ if (code) await authon.verifyMfa(mfaToken, code);
258
+ });
259
+ ```
260
+
261
+ ### MFA Management
262
+
263
+ ```ts
264
+ // Check current MFA status
265
+ const status = await authon.getMfaStatus();
266
+ // { enabled: true, backupCodesRemaining: 8 }
267
+
268
+ // Disable MFA (requires a valid TOTP code or backup code)
269
+ await authon.disableMfa('123456');
270
+
271
+ // Regenerate backup codes (requires a valid TOTP code)
272
+ const newCodes = await authon.regenerateBackupCodes('123456');
273
+ // ['XXXX-XXXX', 'XXXX-XXXX', ...]
274
+ ```
275
+
276
+ ## User Profile
277
+
278
+ ```ts
279
+ // Get the currently signed-in user (synchronous, no network request)
280
+ const user = authon.getUser();
281
+ // {
282
+ // id, projectId, email, displayName, avatarUrl, phone,
283
+ // emailVerified, phoneVerified, isBanned,
284
+ // publicMetadata, lastSignInAt, signInCount, createdAt, updatedAt
285
+ // }
286
+
287
+ // Get the current access token (synchronous)
43
288
  const token = authon.getToken();
44
289
 
45
- // Sign out
46
- await authon.signOut();
290
+ // Update profile fields
291
+ const updated = await authon.updateProfile({
292
+ displayName: 'Alice Smith',
293
+ avatarUrl: 'https://example.com/avatar.png',
294
+ phone: '+12025551234',
295
+ publicMetadata: { plan: 'pro', referralCode: 'ABC123' },
296
+ });
47
297
  ```
48
298
 
49
- ## Configuration
299
+ ## Session Management
50
300
 
51
301
  ```ts
52
- const authon = new Authon('pk_live_...', {
53
- apiUrl: 'https://api.authon.dev', // Custom API URL
54
- mode: 'popup', // 'popup' | 'embedded'
55
- theme: 'auto', // 'light' | 'dark' | 'auto'
56
- locale: 'en', // Locale for the modal UI
57
- containerId: 'auth-container', // Container element ID (embedded mode)
58
- appearance: { // Custom branding overrides
59
- primaryColorStart: '#7c3aed',
60
- primaryColorEnd: '#4f46e5',
61
- borderRadius: 12,
62
- brandName: 'My App',
63
- },
302
+ // List all active sessions for the signed-in user
303
+ const sessions = await authon.listSessions();
304
+ // [{ id, ipAddress, userAgent, createdAt, lastActiveAt }, ...]
305
+
306
+ // Revoke a specific session (e.g., sign out another device)
307
+ await authon.revokeSession(sessions[0].id);
308
+ ```
309
+
310
+ ## Events
311
+
312
+ `on()` returns an unsubscribe function.
313
+
314
+ ```ts
315
+ // User signed in (after any auth method)
316
+ const off = authon.on('signedIn', (user) => {
317
+ console.log('Signed in as', user.email);
318
+ });
319
+
320
+ // User signed out
321
+ authon.on('signedOut', () => {
322
+ console.log('Signed out');
323
+ });
324
+
325
+ // Access token was silently refreshed
326
+ authon.on('tokenRefreshed', (token) => {
327
+ // update token in your API client if needed
64
328
  });
329
+
330
+ // MFA step required after email/password sign-in
331
+ authon.on('mfaRequired', (mfaToken) => {
332
+ showMfaDialog(mfaToken);
333
+ });
334
+
335
+ // A new passkey was registered
336
+ authon.on('passkeyRegistered', (credential) => {
337
+ console.log('Passkey registered:', credential.name);
338
+ });
339
+
340
+ // A Web3 wallet was linked
341
+ authon.on('web3Connected', (wallet) => {
342
+ console.log('Wallet linked:', wallet.address, 'on', wallet.chain);
343
+ });
344
+
345
+ // An error occurred
346
+ authon.on('error', (error) => {
347
+ console.error('Authon error:', error.message);
348
+ });
349
+
350
+ // Unsubscribe
351
+ off();
65
352
  ```
66
353
 
67
- ## API Reference
354
+ ### Event Reference
355
+
356
+ | Event | Payload | Trigger |
357
+ |-------|---------|---------|
358
+ | `signedIn` | `AuthonUser` | Any successful sign-in |
359
+ | `signedOut` | — | `signOut()` called |
360
+ | `tokenRefreshed` | `string` | Access token silently refreshed |
361
+ | `mfaRequired` | `string` (mfaToken) | MFA required after password sign-in |
362
+ | `passkeyRegistered` | `PasskeyCredential` | `registerPasskey()` completed |
363
+ | `web3Connected` | `Web3Wallet` | `linkWallet()` completed |
364
+ | `error` | `Error` | Any error during auth flows |
68
365
 
69
- ### `Authon` class
366
+ ## Full API Reference
367
+
368
+ ### Auth Methods
70
369
 
71
370
  | Method | Returns | Description |
72
371
  |--------|---------|-------------|
73
- | `openSignIn()` | `Promise<void>` | Open the sign-in modal |
74
- | `openSignUp()` | `Promise<void>` | Open the sign-up modal |
75
- | `signInWithEmail(email, password)` | `Promise<AuthonUser>` | Sign in with email/password |
76
- | `signUpWithEmail(email, password, meta?)` | `Promise<AuthonUser>` | Register with email/password |
77
- | `signInWithOAuth(provider, options?)` | `Promise<void>` | Start OAuth flow (`auto`, `popup`, `redirect`) |
372
+ | `openSignIn()` | `Promise<void>` | Open the built-in sign-in modal |
373
+ | `openSignUp()` | `Promise<void>` | Open the built-in sign-up modal |
374
+ | `signInWithEmail(email, password)` | `Promise<AuthonUser>` | Sign in with email and password |
375
+ | `signUpWithEmail(email, password, meta?)` | `Promise<AuthonUser>` | Register a new account |
376
+ | `signInWithOAuth(provider, options?)` | `Promise<void>` | Start an OAuth flow |
78
377
  | `signOut()` | `Promise<void>` | Sign out and clear session |
79
- | `getUser()` | `AuthonUser \| null` | Get current user |
80
- | `getToken()` | `string \| null` | Get current access token |
81
- | `on(event, listener)` | `() => void` | Subscribe to events (returns unsubscribe fn) |
82
- | `destroy()` | `void` | Clean up resources |
378
+ | `sendMagicLink(email)` | `Promise<void>` | Send a magic link to email |
379
+ | `sendEmailOtp(email)` | `Promise<void>` | Send an OTP code to email |
380
+ | `verifyPasswordless(options)` | `Promise<AuthonUser>` | Verify magic link token or OTP code |
381
+
382
+ ### User & Session
383
+
384
+ | Method | Returns | Description |
385
+ |--------|---------|-------------|
386
+ | `getUser()` | `AuthonUser \| null` | Get current user (synchronous) |
387
+ | `getToken()` | `string \| null` | Get current access token (synchronous) |
388
+ | `updateProfile(data)` | `Promise<AuthonUser>` | Update displayName, avatarUrl, phone, publicMetadata |
389
+ | `listSessions()` | `Promise<SessionInfo[]>` | List all active sessions |
390
+ | `revokeSession(sessionId)` | `Promise<void>` | Revoke a session by ID |
391
+
392
+ ### MFA
393
+
394
+ | Method | Returns | Description |
395
+ |--------|---------|-------------|
396
+ | `setupMfa()` | `Promise<MfaSetupResponse & { qrCodeSvg: string }>` | Begin MFA setup |
397
+ | `verifyMfaSetup(code)` | `Promise<void>` | Confirm setup with TOTP code |
398
+ | `verifyMfa(mfaToken, code)` | `Promise<AuthonUser>` | Complete sign-in with TOTP code |
399
+ | `getMfaStatus()` | `Promise<MfaStatus>` | Get MFA enabled state and backup code count |
400
+ | `disableMfa(code)` | `Promise<void>` | Disable MFA |
401
+ | `regenerateBackupCodes(code)` | `Promise<string[]>` | Generate new backup codes |
83
402
 
84
- ### Events
403
+ ### Passkeys
85
404
 
86
- | Event | Payload | Description |
87
- |-------|---------|-------------|
88
- | `signedIn` | `AuthonUser` | User signed in |
89
- | `signedOut` | none | User signed out |
90
- | `tokenRefreshed` | `string` | Access token was refreshed |
91
- | `error` | `Error` | An error occurred |
405
+ | Method | Returns | Description |
406
+ |--------|---------|-------------|
407
+ | `registerPasskey(name?)` | `Promise<PasskeyCredential>` | Register a new passkey |
408
+ | `authenticateWithPasskey(email?)` | `Promise<AuthonUser>` | Sign in with a passkey |
409
+ | `listPasskeys()` | `Promise<PasskeyCredential[]>` | List registered passkeys |
410
+ | `renamePasskey(id, name)` | `Promise<PasskeyCredential>` | Rename a passkey |
411
+ | `revokePasskey(id)` | `Promise<void>` | Delete a passkey |
412
+
413
+ ### Web3
92
414
 
93
- ## ShadowDOM Modal
415
+ | Method | Returns | Description |
416
+ |--------|---------|-------------|
417
+ | `web3GetNonce(address, chain, walletType, chainId?)` | `Promise<Web3NonceResponse>` | Get sign-in message and nonce |
418
+ | `web3Verify(message, signature, address, chain, walletType)` | `Promise<AuthonUser>` | Verify wallet signature and sign in |
419
+ | `listWallets()` | `Promise<Web3Wallet[]>` | List linked wallets |
420
+ | `linkWallet(params)` | `Promise<Web3Wallet>` | Link an additional wallet |
421
+ | `unlinkWallet(walletId)` | `Promise<void>` | Remove a linked wallet |
94
422
 
95
- The login modal renders inside a ShadowRoot, preventing CSS conflicts with your application. Branding (colors, logo, border radius, custom CSS) is fetched from your Authon project settings and can be overridden via the `appearance` config.
423
+ ### Lifecycle
424
+
425
+ | Method | Returns | Description |
426
+ |--------|---------|-------------|
427
+ | `on(event, listener)` | `() => void` | Subscribe to an event, returns unsubscribe fn |
428
+ | `getProviders()` | `Promise<OAuthProviderType[]>` | List OAuth providers enabled for the project |
429
+ | `destroy()` | `void` | Clean up all listeners and resources |
430
+
431
+ ## TypeScript
432
+
433
+ The SDK ships with full type declarations. Key types are exported from `@authon/shared`:
434
+
435
+ ```ts
436
+ import type {
437
+ AuthonUser,
438
+ AuthTokens,
439
+ MfaSetupResponse,
440
+ MfaStatus,
441
+ PasskeyCredential,
442
+ Web3Wallet,
443
+ Web3NonceResponse,
444
+ SessionInfo,
445
+ Web3Chain,
446
+ Web3WalletType,
447
+ OAuthProviderType,
448
+ } from '@authon/shared';
449
+
450
+ import type { AuthonConfig, AuthonEventType } from '@authon/js';
451
+ import { Authon, AuthonMfaRequiredError } from '@authon/js';
452
+ ```
96
453
 
97
454
  ## Documentation
98
455
 
99
- [authon.dev/docs](https://authon.dev/docs)
456
+ Full documentation: [docs.authon.dev](https://docs.authon.dev)
100
457
 
101
458
  ## License
102
459