@authon/vue 0.2.1 → 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.ko.md ADDED
@@ -0,0 +1,170 @@
1
+ [English](./README.md) | **한국어**
2
+
3
+ # @authon/vue
4
+
5
+ [Authon](https://authon.dev)용 Vue 3 SDK — 플러그인, composable, 컴포넌트를 제공합니다.
6
+
7
+ ## 설치
8
+
9
+ ```bash
10
+ npm install @authon/vue
11
+ # 또는
12
+ pnpm add @authon/vue
13
+ ```
14
+
15
+ `vue >= 3.3.0`이 필요합니다.
16
+
17
+ ## 빠른 시작
18
+
19
+ ### 1. 플러그인 설치
20
+
21
+ ```ts
22
+ // main.ts
23
+ import { createApp } from 'vue';
24
+ import { AuthonPlugin } from '@authon/vue';
25
+ import App from './App.vue';
26
+
27
+ const app = createApp(App);
28
+ app.use(AuthonPlugin, {
29
+ publishableKey: 'pk_live_...',
30
+ });
31
+ app.mount('#app');
32
+ ```
33
+
34
+ ### 2. Composable 사용
35
+
36
+ ```vue
37
+ <script setup lang="ts">
38
+ import { useAuthon, useUser } from '@authon/vue';
39
+
40
+ const { isSignedIn, openSignIn, signOut } = useAuthon();
41
+ const { user, isLoading } = useUser();
42
+ </script>
43
+
44
+ <template>
45
+ <div v-if="isLoading">Loading...</div>
46
+ <div v-else-if="isSignedIn">
47
+ <p>Welcome, {{ user?.displayName }}</p>
48
+ <button @click="signOut()">Sign Out</button>
49
+ </div>
50
+ <div v-else>
51
+ <button @click="openSignIn()">Sign In</button>
52
+ </div>
53
+ </template>
54
+ ```
55
+
56
+ ### 3. 컴포넌트 사용
57
+
58
+ ```vue
59
+ <template>
60
+ <SignedIn>
61
+ <UserButton />
62
+ </SignedIn>
63
+ <SignedOut>
64
+ <button @click="openSignIn()">Sign In</button>
65
+ </SignedOut>
66
+ </template>
67
+
68
+ <script setup lang="ts">
69
+ import { SignedIn, SignedOut, UserButton, useAuthon } from '@authon/vue';
70
+
71
+ const { openSignIn } = useAuthon();
72
+ </script>
73
+ ```
74
+
75
+ ## API 레퍼런스
76
+
77
+ ### 플러그인
78
+
79
+ ```ts
80
+ app.use(AuthonPlugin, {
81
+ publishableKey: string;
82
+ apiUrl?: string;
83
+ theme?: 'light' | 'dark' | 'auto';
84
+ locale?: string;
85
+ appearance?: Partial<BrandingConfig>;
86
+ });
87
+ ```
88
+
89
+ ### Composable
90
+
91
+ #### `useAuthon()`
92
+
93
+ ```ts
94
+ const {
95
+ isSignedIn, // Ref<boolean>
96
+ isLoading, // Ref<boolean>
97
+ user, // Ref<AuthonUser | null>
98
+ signOut, // () => Promise<void>
99
+ openSignIn, // () => Promise<void>
100
+ openSignUp, // () => Promise<void>
101
+ getToken, // () => string | null
102
+ client, // Authon 인스턴스
103
+ } = useAuthon();
104
+ ```
105
+
106
+ #### `useUser()`
107
+
108
+ ```ts
109
+ const { user, isLoading } = useUser();
110
+ ```
111
+
112
+ ### 컴포넌트
113
+
114
+ | 컴포넌트 | 설명 |
115
+ |----------|------|
116
+ | `<SignedIn>` | 로그인 상태일 때만 슬롯을 렌더링 |
117
+ | `<SignedOut>` | 로그아웃 상태일 때만 슬롯을 렌더링 |
118
+ | `<UserButton>` | 로그아웃 기능이 포함된 아바타 드롭다운 |
119
+ | `<Protect>` | fallback 슬롯을 지원하는 조건부 렌더링 |
120
+
121
+ ## Multi-Factor Authentication (MFA)
122
+
123
+ `useAuthon()`의 `client` 인스턴스를 통해 MFA 메서드에 접근합니다.
124
+
125
+ ```vue
126
+ <script setup lang="ts">
127
+ import { ref } from 'vue';
128
+ import { useAuthon } from '@authon/vue';
129
+ import { AuthonMfaRequiredError } from '@authon/js';
130
+
131
+ const { client } = useAuthon();
132
+ const qrSvg = ref('');
133
+ const mfaToken = ref('');
134
+
135
+ // MFA 설정
136
+ async function enableMfa() {
137
+ const setup = await client.value!.setupMfa();
138
+ qrSvg.value = setup.qrCodeSvg; // 인증 앱에 표시할 QR 코드
139
+ }
140
+
141
+ async function verifySetup(code: string) {
142
+ await client.value!.verifyMfaSetup(code);
143
+ }
144
+
145
+ // MFA를 사용한 로그인
146
+ async function signIn(email: string, password: string) {
147
+ try {
148
+ await client.value!.signInWithEmail(email, password);
149
+ } catch (err) {
150
+ if (err instanceof AuthonMfaRequiredError) {
151
+ mfaToken.value = err.mfaToken; // TOTP 입력 화면 표시
152
+ }
153
+ }
154
+ }
155
+
156
+ async function verifyMfa(code: string) {
157
+ await client.value!.verifyMfa(mfaToken.value, code);
158
+ }
159
+ </script>
160
+ ```
161
+
162
+ 전체 API 레퍼런스는 [`@authon/js` MFA 문서](../js/README.md#multi-factor-authentication-mfa)를 참고하세요.
163
+
164
+ ## 문서
165
+
166
+ [authon.dev/docs](https://authon.dev/docs)
167
+
168
+ ## 라이선스
169
+
170
+ [MIT](../../LICENSE)
package/README.md CHANGED
@@ -1,120 +1,465 @@
1
+ **English** | [한국어](./README.ko.md)
2
+
1
3
  # @authon/vue
2
4
 
3
- Vue 3 SDK for [Authon](https://authon.dev) — plugin, composables, and components.
5
+ Vue 3 Composition API integration for [Authon](https://authon.dev) — plugin setup, composables, and pre-built components.
4
6
 
5
7
  ## Install
6
8
 
7
9
  ```bash
8
- npm install @authon/vue
9
- # or
10
- pnpm add @authon/vue
10
+ npm install @authon/vue @authon/js
11
11
  ```
12
12
 
13
13
  Requires `vue >= 3.3.0`.
14
14
 
15
- ## Quick Start
15
+ ## Setup
16
16
 
17
- ### 1. Install the Plugin
17
+ Register the plugin in `main.ts`:
18
18
 
19
19
  ```ts
20
- // main.ts
21
- import { createApp } from 'vue';
22
- import { AuthonPlugin } from '@authon/vue';
23
- import App from './App.vue';
20
+ import { createApp } from 'vue'
21
+ import { createAuthon } from '@authon/vue'
22
+ import App from './App.vue'
23
+
24
+ const app = createApp(App)
24
25
 
25
- const app = createApp(App);
26
- app.use(AuthonPlugin, {
26
+ app.use(createAuthon({
27
27
  publishableKey: 'pk_live_...',
28
- });
29
- app.mount('#app');
28
+ config: {
29
+ theme: 'auto',
30
+ locale: 'en',
31
+ },
32
+ }))
33
+
34
+ app.mount('#app')
35
+ ```
36
+
37
+ ## Composables
38
+
39
+ ### `useAuthon()`
40
+
41
+ Returns the full auth state and helper methods. The `client` property exposes the underlying `@authon/js` `Authon` instance for all advanced operations.
42
+
43
+ ```ts
44
+ import { useAuthon } from '@authon/vue'
45
+
46
+ const {
47
+ isSignedIn, // boolean — reactive
48
+ isLoading, // boolean — reactive
49
+ user, // AuthonUser | null — reactive
50
+ client, // Authon instance from @authon/js
51
+ signOut, // () => Promise<void>
52
+ openSignIn, // () => Promise<void>
53
+ openSignUp, // () => Promise<void>
54
+ getToken, // () => string | null
55
+ } = useAuthon()
30
56
  ```
31
57
 
32
- ### 2. Use Composables
58
+ ### `useUser()`
59
+
60
+ Returns only the user and loading state as computed refs.
61
+
62
+ ```ts
63
+ import { useUser } from '@authon/vue'
64
+
65
+ const { user, isLoading } = useUser()
66
+ // user: ComputedRef<AuthonUser | null>
67
+ // isLoading: ComputedRef<boolean>
68
+ ```
69
+
70
+ ## Components
71
+
72
+ ```ts
73
+ import {
74
+ AuthonSignIn,
75
+ AuthonSignUp,
76
+ AuthonUserButton,
77
+ AuthonSignedIn,
78
+ AuthonSignedOut,
79
+ AuthonSocialButton,
80
+ AuthonSocialButtons,
81
+ } from '@authon/vue'
82
+ ```
83
+
84
+ | Component | Description |
85
+ |---|---|
86
+ | `<AuthonSignIn mode="popup" />` | Opens sign-in UI. `mode`: `'popup'` (default) or `'embedded'` |
87
+ | `<AuthonSignUp mode="popup" />` | Opens sign-up UI. `mode`: `'popup'` (default) or `'embedded'` |
88
+ | `<AuthonUserButton />` | Avatar button with dropdown — shows user info and sign-out |
89
+ | `<AuthonSignedIn>` | Renders default slot only when the user is authenticated |
90
+ | `<AuthonSignedOut>` | Renders default slot only when the user is not authenticated |
91
+ | `<AuthonSocialButton provider="google" />` | Single branded OAuth provider button |
92
+ | `<AuthonSocialButtons />` | Renders all configured OAuth provider buttons automatically |
93
+
94
+ ## Examples
95
+
96
+ ### Basic auth state in a navbar
33
97
 
34
98
  ```vue
99
+ <template>
100
+ <nav>
101
+ <AuthonSignedIn>
102
+ <span>Hello, {{ user?.displayName }}</span>
103
+ <button @click="signOut">Sign out</button>
104
+ </AuthonSignedIn>
105
+ <AuthonSignedOut>
106
+ <AuthonUserButton />
107
+ </AuthonSignedOut>
108
+ </nav>
109
+ </template>
110
+
35
111
  <script setup lang="ts">
36
- import { useAuthon, useUser } from '@authon/vue';
112
+ import { useAuthon, AuthonSignedIn, AuthonSignedOut, AuthonUserButton } from '@authon/vue'
37
113
 
38
- const { isSignedIn, openSignIn, signOut } = useAuthon();
39
- const { user, isLoading } = useUser();
114
+ const { user, signOut } = useAuthon()
40
115
  </script>
116
+ ```
117
+
118
+ ### Email + password sign-in
41
119
 
120
+ ```vue
42
121
  <template>
43
- <div v-if="isLoading">Loading...</div>
44
- <div v-else-if="isSignedIn">
45
- <p>Welcome, {{ user?.displayName }}</p>
46
- <button @click="signOut()">Sign Out</button>
47
- </div>
48
- <div v-else>
49
- <button @click="openSignIn()">Sign In</button>
50
- </div>
122
+ <form @submit.prevent="handleSignIn">
123
+ <input v-model="email" type="email" placeholder="Email" />
124
+ <input v-model="password" type="password" placeholder="Password" />
125
+ <button type="submit" :disabled="loading">Sign in</button>
126
+ <p v-if="error">{{ error }}</p>
127
+ </form>
128
+ </template>
129
+
130
+ <script setup lang="ts">
131
+ import { ref } from 'vue'
132
+ import { useAuthon } from '@authon/vue'
133
+ import { useRouter } from 'vue-router'
134
+
135
+ const { client } = useAuthon()
136
+ const router = useRouter()
137
+ const email = ref('')
138
+ const password = ref('')
139
+ const loading = ref(false)
140
+ const error = ref('')
141
+
142
+ async function handleSignIn() {
143
+ loading.value = true
144
+ error.value = ''
145
+ try {
146
+ await client!.signInWithEmail(email.value, password.value)
147
+ router.push('/dashboard')
148
+ } catch (e: any) {
149
+ error.value = e.message
150
+ } finally {
151
+ loading.value = false
152
+ }
153
+ }
154
+ </script>
155
+ ```
156
+
157
+ ### OAuth sign-in
158
+
159
+ ```vue
160
+ <script setup lang="ts">
161
+ import { useAuthon, AuthonSocialButtons } from '@authon/vue'
162
+
163
+ const { client } = useAuthon()
164
+
165
+ // Trigger a specific provider directly
166
+ async function signInWithGoogle() {
167
+ await client!.signInWithOAuth('google')
168
+ }
169
+ </script>
170
+
171
+ <template>
172
+ <!-- Or render all configured providers automatically -->
173
+ <AuthonSocialButtons
174
+ :compact="false"
175
+ :labels="{ google: 'Continue with Google' }"
176
+ @success="() => router.push('/dashboard')"
177
+ @error="(e) => console.error(e)"
178
+ />
51
179
  </template>
52
180
  ```
53
181
 
54
- ### 3. Use Components
182
+ ### MFA setup
55
183
 
56
184
  ```vue
57
185
  <template>
58
- <SignedIn>
59
- <UserButton />
60
- </SignedIn>
61
- <SignedOut>
62
- <button @click="openSignIn()">Sign In</button>
63
- </SignedOut>
186
+ <div>
187
+ <button @click="initMfaSetup">Enable MFA</button>
188
+ <div v-if="qrCodeSvg" v-html="qrCodeSvg" />
189
+ <p v-if="secret">Secret: {{ secret }}</p>
190
+ <input v-model="verifyCode" placeholder="6-digit code" />
191
+ <button @click="confirmSetup">Verify</button>
192
+ <ul v-if="backupCodes.length">
193
+ <li v-for="c in backupCodes" :key="c">{{ c }}</li>
194
+ </ul>
195
+ </div>
64
196
  </template>
65
197
 
66
198
  <script setup lang="ts">
67
- import { SignedIn, SignedOut, UserButton, useAuthon } from '@authon/vue';
199
+ import { ref } from 'vue'
200
+ import { useAuthon } from '@authon/vue'
201
+
202
+ const { client } = useAuthon()
203
+ const qrCodeSvg = ref('')
204
+ const secret = ref('')
205
+ const backupCodes = ref<string[]>([])
206
+ const verifyCode = ref('')
207
+
208
+ async function initMfaSetup() {
209
+ const res = await client!.setupMfa()
210
+ qrCodeSvg.value = res.qrCodeSvg // inline SVG for authenticator app
211
+ secret.value = res.secret
212
+ backupCodes.value = res.backupCodes
213
+ }
68
214
 
69
- const { openSignIn } = useAuthon();
215
+ async function confirmSetup() {
216
+ await client!.verifyMfaSetup(verifyCode.value)
217
+ alert('MFA enabled successfully')
218
+ }
70
219
  </script>
71
220
  ```
72
221
 
73
- ## API Reference
222
+ ### MFA verification on sign-in
74
223
 
75
- ### Plugin
224
+ ```vue
225
+ <script setup lang="ts">
226
+ import { ref } from 'vue'
227
+ import { useAuthon } from '@authon/vue'
228
+ import { AuthonMfaRequiredError } from '@authon/js'
76
229
 
77
- ```ts
78
- app.use(AuthonPlugin, {
79
- publishableKey: string;
80
- apiUrl?: string;
81
- theme?: 'light' | 'dark' | 'auto';
82
- locale?: string;
83
- appearance?: Partial<BrandingConfig>;
84
- });
230
+ const { client } = useAuthon()
231
+ const mfaToken = ref('')
232
+ const totpCode = ref('')
233
+
234
+ async function signIn(email: string, password: string) {
235
+ try {
236
+ await client!.signInWithEmail(email, password)
237
+ } catch (e) {
238
+ if (e instanceof AuthonMfaRequiredError) {
239
+ mfaToken.value = e.mfaToken // show TOTP input
240
+ }
241
+ }
242
+ }
243
+
244
+ async function verifyMfa() {
245
+ await client!.verifyMfa(mfaToken.value, totpCode.value)
246
+ }
247
+ </script>
85
248
  ```
86
249
 
87
- ### Composables
250
+ ### Passwordless — magic link
88
251
 
89
- #### `useAuthon()`
252
+ ```vue
253
+ <script setup lang="ts">
254
+ import { ref } from 'vue'
255
+ import { useAuthon } from '@authon/vue'
90
256
 
91
- ```ts
92
- const {
93
- isSignedIn, // Ref<boolean>
94
- isLoading, // Ref<boolean>
95
- user, // Ref<AuthonUser | null>
96
- signOut, // () => Promise<void>
97
- openSignIn, // () => Promise<void>
98
- openSignUp, // () => Promise<void>
99
- getToken, // () => string | null
100
- client, // Authon instance
101
- } = useAuthon();
257
+ const { client } = useAuthon()
258
+ const email = ref('')
259
+ const sent = ref(false)
260
+
261
+ async function sendMagicLink() {
262
+ await client!.sendMagicLink(email.value)
263
+ sent.value = true
264
+ }
265
+ </script>
102
266
  ```
103
267
 
104
- #### `useUser()`
268
+ ### Passwordless — email OTP
105
269
 
106
- ```ts
107
- const { user, isLoading } = useUser();
270
+ ```vue
271
+ <script setup lang="ts">
272
+ import { ref } from 'vue'
273
+ import { useAuthon } from '@authon/vue'
274
+
275
+ const { client } = useAuthon()
276
+ const email = ref('')
277
+ const otp = ref('')
278
+ const step = ref<'email' | 'verify'>('email')
279
+
280
+ async function sendOtp() {
281
+ await client!.sendEmailOtp(email.value)
282
+ step.value = 'verify'
283
+ }
284
+
285
+ async function verifyOtp() {
286
+ const user = await client!.verifyPasswordless({ email: email.value, code: otp.value })
287
+ console.log('Signed in as:', user.email)
288
+ }
289
+ </script>
108
290
  ```
109
291
 
110
- ### Components
292
+ ### Passkeys
111
293
 
112
- | Component | Description |
113
- |-----------|-------------|
114
- | `<SignedIn>` | Renders slot only when signed in |
115
- | `<SignedOut>` | Renders slot only when signed out |
116
- | `<UserButton>` | Avatar dropdown with sign-out |
117
- | `<Protect>` | Conditional rendering with fallback slot |
294
+ ```vue
295
+ <script setup lang="ts">
296
+ import { useAuthon } from '@authon/vue'
297
+
298
+ const { client } = useAuthon()
299
+
300
+ // Register a new passkey (user must be signed in)
301
+ async function registerPasskey() {
302
+ const credential = await client!.registerPasskey('My MacBook')
303
+ console.log('Registered passkey:', credential.id)
304
+ }
305
+
306
+ // Authenticate with an existing passkey
307
+ async function loginWithPasskey() {
308
+ const user = await client!.authenticateWithPasskey()
309
+ console.log('Signed in as:', user.email)
310
+ }
311
+
312
+ // List all registered passkeys
313
+ async function listPasskeys() {
314
+ const keys = await client!.listPasskeys()
315
+ console.log(keys)
316
+ }
317
+ </script>
318
+ ```
319
+
320
+ ### Web3 wallet authentication
321
+
322
+ ```vue
323
+ <script setup lang="ts">
324
+ import { useAuthon } from '@authon/vue'
325
+
326
+ const { client } = useAuthon()
327
+
328
+ async function signInWithWallet() {
329
+ const address = '0xYourWalletAddress'
330
+
331
+ // 1. Get a nonce + signable message from Authon
332
+ const { nonce, message } = await client!.web3GetNonce(address, 'evm', 'metamask')
333
+
334
+ // 2. Sign the message with the wallet
335
+ const signature = await window.ethereum.request({
336
+ method: 'personal_sign',
337
+ params: [message, address],
338
+ })
339
+
340
+ // 3. Verify the signature and sign in
341
+ const user = await client!.web3Verify(message, signature, address, 'evm', 'metamask')
342
+ console.log('Signed in as:', user.email)
343
+ }
344
+
345
+ async function listLinkedWallets() {
346
+ const wallets = await client!.listWallets()
347
+ console.log(wallets)
348
+ }
349
+ </script>
350
+ ```
351
+
352
+ ### Profile update
353
+
354
+ ```vue
355
+ <script setup lang="ts">
356
+ import { useAuthon } from '@authon/vue'
357
+
358
+ const { client } = useAuthon()
359
+
360
+ async function saveProfile() {
361
+ const updated = await client!.updateProfile({
362
+ displayName: 'Jane Doe',
363
+ avatarUrl: 'https://example.com/avatar.png',
364
+ phone: '+1234567890',
365
+ publicMetadata: { role: 'admin' },
366
+ })
367
+ console.log('Updated user:', updated)
368
+ }
369
+ </script>
370
+ ```
371
+
372
+ ### Session management
373
+
374
+ ```vue
375
+ <template>
376
+ <ul>
377
+ <li v-for="session in sessions" :key="session.id">
378
+ {{ session.userAgent }} — {{ session.createdAt }}
379
+ <button @click="revoke(session.id)">Revoke</button>
380
+ </li>
381
+ </ul>
382
+ </template>
383
+
384
+ <script setup lang="ts">
385
+ import { ref, onMounted } from 'vue'
386
+ import { useAuthon } from '@authon/vue'
387
+ import type { SessionInfo } from '@authon/shared'
388
+
389
+ const { client } = useAuthon()
390
+ const sessions = ref<SessionInfo[]>([])
391
+
392
+ onMounted(async () => {
393
+ sessions.value = await client!.listSessions()
394
+ })
395
+
396
+ async function revoke(sessionId: string) {
397
+ await client!.revokeSession(sessionId)
398
+ sessions.value = sessions.value.filter(s => s.id !== sessionId)
399
+ }
400
+ </script>
401
+ ```
402
+
403
+ ## Social Buttons
404
+
405
+ `<AuthonSocialButtons>` fetches the enabled OAuth providers from your Authon dashboard and renders branded buttons automatically.
406
+
407
+ **Props:**
408
+
409
+ | Prop | Type | Default | Description |
410
+ |---|---|---|---|
411
+ | `compact` | `boolean` | `false` | Icon-only square buttons in a row |
412
+ | `gap` | `number` | `10` / `12` | Gap between buttons in px |
413
+ | `labels` | `Record<provider, string>` | — | Override button labels per provider |
414
+ | `onSuccess` | `() => void` | — | Called after successful OAuth sign-in |
415
+ | `onError` | `(error: Error) => void` | — | Called on OAuth error |
416
+
417
+ For a single provider button:
418
+
419
+ ```vue
420
+ <template>
421
+ <AuthonSocialButton
422
+ provider="github"
423
+ :onClick="handleClick"
424
+ :loading="isLoading"
425
+ :compact="false"
426
+ />
427
+ </template>
428
+ ```
429
+
430
+ **`AuthonSocialButton` props:**
431
+
432
+ | Prop | Type | Default |
433
+ |---|---|---|
434
+ | `provider` | `OAuthProviderType` | required |
435
+ | `onClick` | `(provider) => void` | required |
436
+ | `loading` | `boolean` | `false` |
437
+ | `disabled` | `boolean` | `false` |
438
+ | `label` | `string` | `'Continue with {Name}'` |
439
+ | `compact` | `boolean` | `false` |
440
+ | `borderRadius` | `number` | `10` |
441
+ | `height` | `number` | `48` |
442
+ | `size` | `number` | `48` (compact mode) |
443
+
444
+ ## Plugin options
445
+
446
+ | Option | Type | Default | Description |
447
+ |---|---|---|---|
448
+ | `publishableKey` | `string` | required | Your Authon publishable key |
449
+ | `config.theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | UI theme |
450
+ | `config.locale` | `string` | `'en'` | Language code |
451
+ | `config.apiUrl` | `string` | `'https://api.authon.dev'` | Custom API base URL |
452
+ | `config.mode` | `'popup' \| 'embedded'` | `'popup'` | Modal display mode |
453
+ | `config.appearance` | `Partial<BrandingConfig>` | — | Override branding colors and logo |
454
+
455
+ ## TypeScript
456
+
457
+ Types are re-exported from `@authon/shared`:
458
+
459
+ ```ts
460
+ import type { AuthonUser, SessionInfo, PasskeyCredential, Web3Wallet } from '@authon/shared'
461
+ import type { AuthonState, AuthonPluginOptions } from '@authon/vue'
462
+ ```
118
463
 
119
464
  ## Documentation
120
465