@bagelink/auth 1.7.72 → 1.7.74
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 +283 -248
- package/dist/index.cjs +208 -62
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +208 -62
- package/dist/redirect.d.ts +21 -0
- package/dist/router.d.ts +22 -0
- package/dist/types/redirect.d.ts +70 -0
- package/dist/useAuth.d.ts +23 -0
- package/package.json +1 -1
- package/src/index.ts +10 -2
- package/src/redirect.ts +95 -0
- package/src/router.ts +86 -0
- package/src/types/redirect.ts +96 -0
- package/src/useAuth.ts +72 -1
package/README.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# @bagelink/auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern authentication library for Bagelink applications with built-in SSO support, automatic redirect handling, and Vue components.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Complete auth flow** - Login, signup, password reset, email verification
|
|
8
|
+
- 🚀 **SSO ready** - Google, GitHub, Microsoft, Apple, Okta, Facebook
|
|
9
|
+
- 🔄 **Auto-redirect** - Handles post-login navigation automatically
|
|
10
|
+
- 🛡️ **Security built-in** - Protection against open redirect attacks
|
|
11
|
+
- 🎨 **Vue components** - Pre-built forms and pages
|
|
12
|
+
- 📱 **Type-safe** - Full TypeScript support
|
|
13
|
+
- 🧩 **Router integration** - Smart guards for protected routes
|
|
4
14
|
|
|
5
15
|
## Installation
|
|
6
16
|
|
|
@@ -13,381 +23,406 @@ npm install @bagelink/auth
|
|
|
13
23
|
### 1. Initialize Auth
|
|
14
24
|
|
|
15
25
|
```typescript
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
createAuth
|
|
19
|
-
|
|
26
|
+
// main.ts
|
|
27
|
+
import { createApp } from 'vue'
|
|
28
|
+
import { createAuth, setAuthRouter, authGuard } from '@bagelink/auth'
|
|
29
|
+
import router from './router'
|
|
30
|
+
|
|
31
|
+
const auth = createAuth({
|
|
32
|
+
baseURL: 'https://api.your-app.com',
|
|
33
|
+
redirect: {
|
|
34
|
+
loginRoute: 'Login',
|
|
35
|
+
fallback: '/dashboard',
|
|
36
|
+
autoRedirect: true, // Automatic post-login redirect
|
|
37
|
+
}
|
|
20
38
|
})
|
|
39
|
+
|
|
40
|
+
// Connect router for auto-redirect
|
|
41
|
+
setAuthRouter(router)
|
|
42
|
+
|
|
43
|
+
// Install auth guard
|
|
44
|
+
router.beforeEach(authGuard())
|
|
45
|
+
|
|
46
|
+
const app = createApp(App)
|
|
47
|
+
app.use(router)
|
|
48
|
+
app.use(auth) // Optional: adds $auth globally
|
|
49
|
+
app.mount('#app')
|
|
21
50
|
```
|
|
22
51
|
|
|
23
|
-
### 2.
|
|
52
|
+
### 2. Define Routes
|
|
24
53
|
|
|
25
54
|
```typescript
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
const
|
|
55
|
+
// router/index.ts
|
|
56
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
57
|
+
|
|
58
|
+
const routes = [
|
|
59
|
+
{
|
|
60
|
+
path: '/login',
|
|
61
|
+
name: 'Login',
|
|
62
|
+
component: () => import('@/views/Login.vue'),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
path: '/dashboard',
|
|
66
|
+
name: 'Dashboard',
|
|
67
|
+
component: () => import('@/views/Dashboard.vue'),
|
|
68
|
+
meta: { auth: true }, // Protected route
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export default createRouter({
|
|
30
73
|
history: createWebHistory(),
|
|
31
|
-
routes
|
|
32
|
-
// Add auth routes
|
|
33
|
-
...createAuthRoutes({
|
|
34
|
-
basePath: '/auth',
|
|
35
|
-
redirectTo: '/dashboard'
|
|
36
|
-
}),
|
|
37
|
-
|
|
38
|
-
// Your app routes
|
|
39
|
-
{
|
|
40
|
-
path: '/dashboard',
|
|
41
|
-
component: Dashboard,
|
|
42
|
-
meta: { requiresAuth: true }
|
|
43
|
-
}
|
|
44
|
-
]
|
|
74
|
+
routes,
|
|
45
75
|
})
|
|
46
|
-
|
|
47
|
-
// Add auth guard to redirect authenticated users
|
|
48
|
-
router.beforeEach(createAuthGuard({ redirectTo: '/dashboard' }))
|
|
49
|
-
|
|
50
|
-
export default router
|
|
51
76
|
```
|
|
52
77
|
|
|
53
78
|
### 3. Use in Components
|
|
54
79
|
|
|
55
80
|
```vue
|
|
56
|
-
<script setup>
|
|
81
|
+
<script setup lang="ts">
|
|
57
82
|
import { useAuth } from '@bagelink/auth'
|
|
58
83
|
|
|
59
|
-
const
|
|
84
|
+
const auth = useAuth()
|
|
85
|
+
|
|
86
|
+
async function handleLogin() {
|
|
87
|
+
await auth.login({
|
|
88
|
+
email: 'user@example.com',
|
|
89
|
+
password: 'password'
|
|
90
|
+
})
|
|
91
|
+
// Auto-redirect happens automatically! 🎉
|
|
92
|
+
}
|
|
60
93
|
</script>
|
|
61
94
|
|
|
62
95
|
<template>
|
|
63
|
-
<div v-if="
|
|
64
|
-
<p>Welcome, {{ user.name }}
|
|
65
|
-
<button @click="logout">Logout</button>
|
|
96
|
+
<div v-if="auth.user.value">
|
|
97
|
+
<p>Welcome, {{ auth.user.value.name }}!</p>
|
|
98
|
+
<button @click="auth.logout()">Logout</button>
|
|
66
99
|
</div>
|
|
67
100
|
</template>
|
|
68
101
|
```
|
|
69
102
|
|
|
70
|
-
##
|
|
103
|
+
## Documentation
|
|
71
104
|
|
|
72
|
-
|
|
105
|
+
- **[REDIRECT_GUIDE.md](./REDIRECT_GUIDE.md)** - Complete redirect configuration guide
|
|
106
|
+
- **[COMPLETE_EXAMPLE.md](./COMPLETE_EXAMPLE.md)** - Full working application example
|
|
107
|
+
- **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** - Migration guide for breaking changes
|
|
73
108
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
109
|
+
## Core API
|
|
110
|
+
|
|
111
|
+
### `createAuth(config)`
|
|
78
112
|
|
|
79
|
-
|
|
113
|
+
Initialize the auth module with redirect configuration:
|
|
80
114
|
|
|
81
115
|
```typescript
|
|
82
|
-
|
|
83
|
-
|
|
116
|
+
createAuth({
|
|
117
|
+
baseURL: string, // Required: Your API base URL
|
|
118
|
+
redirect?: {
|
|
119
|
+
loginRoute?: string, // Login route name (default: 'Login')
|
|
120
|
+
fallback?: string, // Default redirect after login (default: '/')
|
|
121
|
+
queryKey?: string, // Query param for redirect URL (default: 'redirect')
|
|
122
|
+
autoRedirect?: boolean, // Auto-redirect after login (default: true)
|
|
123
|
+
preserveRedirect?: boolean, // Preserve original URL (default: true)
|
|
124
|
+
noAuthRoutes?: string[], // Routes requiring no auth (default: ['Login', 'Signup', ...])
|
|
125
|
+
allowedPaths?: RegExp[], // Optional: Restrict allowed redirects for security
|
|
126
|
+
}
|
|
84
127
|
})
|
|
85
|
-
// Creates routes: /auth/login, /auth/signup, etc.
|
|
86
128
|
```
|
|
87
129
|
|
|
88
|
-
###
|
|
130
|
+
### `useAuth()`
|
|
131
|
+
|
|
132
|
+
Returns auth composable with:
|
|
89
133
|
|
|
90
134
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
135
|
+
{
|
|
136
|
+
// State
|
|
137
|
+
user: Ref<User | null>
|
|
138
|
+
accountInfo: Ref<AccountInfo | null>
|
|
139
|
+
|
|
140
|
+
// Authentication
|
|
141
|
+
login(credentials): Promise<AuthenticationResponse>
|
|
142
|
+
signup(data): Promise<AuthenticationResponse>
|
|
143
|
+
logout(): Promise<void>
|
|
144
|
+
checkAuth(): Promise<boolean>
|
|
145
|
+
|
|
146
|
+
// SSO
|
|
147
|
+
sso: {
|
|
148
|
+
google: { redirect(), callback() }
|
|
149
|
+
github: { redirect(), callback() }
|
|
150
|
+
microsoft: { redirect(), callback() }
|
|
151
|
+
// ... more providers
|
|
96
152
|
}
|
|
97
|
-
|
|
153
|
+
|
|
154
|
+
// Password Management
|
|
155
|
+
forgotPassword(email): Promise<void>
|
|
156
|
+
resetPassword(token, newPassword): Promise<void>
|
|
157
|
+
changePassword(current, new): Promise<void>
|
|
158
|
+
|
|
159
|
+
// Profile
|
|
160
|
+
updateProfile(updates): Promise<void>
|
|
161
|
+
|
|
162
|
+
// Getters
|
|
163
|
+
getFullName(): string
|
|
164
|
+
getIsLoggedIn(): boolean
|
|
165
|
+
getEmail(): string
|
|
166
|
+
getRoles(): string[]
|
|
167
|
+
}
|
|
98
168
|
```
|
|
99
169
|
|
|
100
|
-
###
|
|
170
|
+
### `authGuard()`
|
|
171
|
+
|
|
172
|
+
Navigation guard for protected routes:
|
|
101
173
|
|
|
102
174
|
```typescript
|
|
103
|
-
|
|
175
|
+
router.beforeEach(authGuard()) // No parameters needed!
|
|
176
|
+
```
|
|
104
177
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
178
|
+
Protects routes with `meta: { auth: true }` and handles redirects automatically.
|
|
179
|
+
|
|
180
|
+
### `setAuthRouter(router)`
|
|
181
|
+
|
|
182
|
+
Connect router for auto-redirect:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
setAuthRouter(router) // Call after creating router
|
|
108
186
|
```
|
|
109
187
|
|
|
110
188
|
## Components
|
|
111
189
|
|
|
112
|
-
###
|
|
113
|
-
|
|
114
|
-
Use these if you want to build custom pages:
|
|
190
|
+
### Pre-built Pages
|
|
115
191
|
|
|
116
192
|
```vue
|
|
117
193
|
<script setup>
|
|
118
|
-
import {
|
|
194
|
+
import { LoginPage, SignupPage, ForgotPasswordPage } from '@bagelink/auth'
|
|
119
195
|
</script>
|
|
120
196
|
|
|
121
197
|
<template>
|
|
122
|
-
<
|
|
198
|
+
<LoginPage
|
|
123
199
|
:texts="{ title: 'Welcome Back' }"
|
|
124
|
-
|
|
125
|
-
:show-google="true"
|
|
126
|
-
@switch-form="handleFormSwitch"
|
|
200
|
+
card-width="400px"
|
|
127
201
|
/>
|
|
128
202
|
</template>
|
|
129
203
|
```
|
|
130
204
|
|
|
131
|
-
###
|
|
205
|
+
### Form Components
|
|
132
206
|
|
|
133
|
-
|
|
207
|
+
For custom layouts:
|
|
134
208
|
|
|
135
209
|
```vue
|
|
136
210
|
<script setup>
|
|
137
|
-
import {
|
|
211
|
+
import { LoginForm, SignupForm } from '@bagelink/auth'
|
|
138
212
|
</script>
|
|
139
213
|
|
|
140
214
|
<template>
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
215
|
+
<div class="custom-layout">
|
|
216
|
+
<LoginForm
|
|
217
|
+
:texts="{ title: 'Sign In' }"
|
|
218
|
+
:show-google="true"
|
|
219
|
+
:show-github="true"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
145
222
|
</template>
|
|
146
223
|
```
|
|
147
224
|
|
|
148
|
-
## SSO
|
|
149
|
-
|
|
150
|
-
### Available Providers
|
|
151
|
-
|
|
152
|
-
- GitHub
|
|
153
|
-
- Google
|
|
154
|
-
- Microsoft
|
|
155
|
-
- Apple
|
|
156
|
-
- Okta
|
|
157
|
-
- Facebook
|
|
225
|
+
## SSO Integration
|
|
158
226
|
|
|
159
|
-
###
|
|
227
|
+
### Quick SSO Login
|
|
160
228
|
|
|
161
229
|
```typescript
|
|
162
230
|
const { sso } = useAuth()
|
|
163
231
|
|
|
164
|
-
//
|
|
165
|
-
await sso.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
await sso.handleCallback()
|
|
169
|
-
|
|
170
|
-
// Link additional account
|
|
171
|
-
await sso.linkAccount('google')
|
|
232
|
+
// Redirect to Google OAuth
|
|
233
|
+
await sso.google.redirect({
|
|
234
|
+
redirect_uri: `${window.location.origin}/auth/callback`
|
|
235
|
+
})
|
|
172
236
|
```
|
|
173
237
|
|
|
174
|
-
###
|
|
238
|
+
### SSO Callback Route
|
|
175
239
|
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
:
|
|
180
|
-
:
|
|
181
|
-
:
|
|
182
|
-
|
|
183
|
-
:sso-brand-background="true"
|
|
184
|
-
/>
|
|
240
|
+
```typescript
|
|
241
|
+
// router.ts
|
|
242
|
+
{
|
|
243
|
+
path: '/auth/callback',
|
|
244
|
+
name: 'Callback',
|
|
245
|
+
component: () => import('@bagelink/auth').then(m => m.Callback),
|
|
246
|
+
}
|
|
185
247
|
```
|
|
186
248
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
### `useAuth()`
|
|
190
|
-
|
|
191
|
-
Returns the authentication composable with:
|
|
249
|
+
### Available Providers
|
|
192
250
|
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
- `forgotPassword(email)` - Request password reset
|
|
200
|
-
- `resetPassword(token, password)` - Reset password with token
|
|
201
|
-
- `sso` - SSO methods object
|
|
251
|
+
- Google
|
|
252
|
+
- GitHub
|
|
253
|
+
- Microsoft
|
|
254
|
+
- Apple
|
|
255
|
+
- Okta
|
|
256
|
+
- Facebook
|
|
202
257
|
|
|
203
|
-
|
|
258
|
+
## Advanced Configuration
|
|
204
259
|
|
|
205
|
-
|
|
260
|
+
### Security: Restrict Redirect Paths
|
|
206
261
|
|
|
207
262
|
```typescript
|
|
208
263
|
createAuth({
|
|
209
|
-
baseURL:
|
|
264
|
+
baseURL: 'https://api.example.com',
|
|
265
|
+
redirect: {
|
|
266
|
+
allowedPaths: [
|
|
267
|
+
/^\/dashboard/,
|
|
268
|
+
/^\/profile/,
|
|
269
|
+
/^\/settings/,
|
|
270
|
+
],
|
|
271
|
+
// Only these paths can be redirect targets
|
|
272
|
+
}
|
|
210
273
|
})
|
|
211
274
|
```
|
|
212
275
|
|
|
213
|
-
###
|
|
276
|
+
### Disable Auto-Redirect
|
|
214
277
|
|
|
215
|
-
|
|
278
|
+
For custom logic:
|
|
216
279
|
|
|
217
280
|
```typescript
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
routeNames?: { // Custom route names
|
|
222
|
-
login?: string
|
|
223
|
-
signup?: string
|
|
224
|
-
forgotPassword?: string
|
|
225
|
-
resetPassword?: string
|
|
226
|
-
callback?: string
|
|
281
|
+
createAuth({
|
|
282
|
+
redirect: {
|
|
283
|
+
autoRedirect: false, // Manual control
|
|
227
284
|
}
|
|
228
|
-
redirectTo?: string // Redirect after auth (default: '/')
|
|
229
|
-
layout?: Component // Wrapper layout component
|
|
230
285
|
})
|
|
231
|
-
```
|
|
232
286
|
|
|
233
|
-
|
|
287
|
+
// Then in login handler:
|
|
288
|
+
import { performRedirect, getRedirectConfig } from '@bagelink/auth'
|
|
234
289
|
|
|
235
|
-
|
|
290
|
+
await auth.login(credentials)
|
|
291
|
+
await performRedirect(router, getRedirectConfig())
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Custom Route Meta Key
|
|
236
295
|
|
|
237
296
|
```typescript
|
|
238
|
-
|
|
239
|
-
|
|
297
|
+
createAuth({
|
|
298
|
+
redirect: {
|
|
299
|
+
authMetaKey: 'requiresAuth', // Default: 'auth'
|
|
300
|
+
}
|
|
240
301
|
})
|
|
302
|
+
|
|
303
|
+
// Then in routes:
|
|
304
|
+
{
|
|
305
|
+
path: '/dashboard',
|
|
306
|
+
meta: { requiresAuth: true } // Custom meta key
|
|
307
|
+
}
|
|
241
308
|
```
|
|
242
309
|
|
|
243
|
-
##
|
|
310
|
+
## Event System
|
|
244
311
|
|
|
245
|
-
|
|
312
|
+
Listen to auth events:
|
|
246
313
|
|
|
247
|
-
|
|
314
|
+
```typescript
|
|
315
|
+
import { createAuth, AuthState } from '@bagelink/auth'
|
|
248
316
|
|
|
249
|
-
|
|
250
|
-
<LoginForm
|
|
251
|
-
:texts="{
|
|
252
|
-
title: 'Sign In',
|
|
253
|
-
emailLabel: 'Email Address',
|
|
254
|
-
passwordLabel: 'Password',
|
|
255
|
-
loginButton: 'Continue',
|
|
256
|
-
githubButton: 'Continue with GitHub'
|
|
257
|
-
}"
|
|
258
|
-
/>
|
|
259
|
-
```
|
|
317
|
+
const auth = createAuth({ ... })
|
|
260
318
|
|
|
261
|
-
|
|
319
|
+
auth.on(AuthState.LOGIN, () => {
|
|
320
|
+
console.log('User logged in')
|
|
321
|
+
})
|
|
262
322
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
import { ref, computed } from 'vue'
|
|
266
|
-
import { LoginForm } from '@bagelink/auth'
|
|
267
|
-
|
|
268
|
-
const locale = ref('en')
|
|
269
|
-
|
|
270
|
-
const texts = computed(() => {
|
|
271
|
-
if (locale.value === 'he') {
|
|
272
|
-
return {
|
|
273
|
-
title: 'התחברות',
|
|
274
|
-
emailLabel: 'אימייל',
|
|
275
|
-
passwordLabel: 'סיסמה',
|
|
276
|
-
loginButton: 'התחברות'
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return {
|
|
280
|
-
title: 'Login',
|
|
281
|
-
emailLabel: 'Email',
|
|
282
|
-
passwordLabel: 'Password',
|
|
283
|
-
loginButton: 'Login'
|
|
284
|
-
}
|
|
323
|
+
auth.on(AuthState.LOGOUT, () => {
|
|
324
|
+
console.log('User logged out')
|
|
285
325
|
})
|
|
286
|
-
</script>
|
|
287
326
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
327
|
+
auth.on(AuthState.PROFILE_UPDATE, () => {
|
|
328
|
+
console.log('Profile updated')
|
|
329
|
+
})
|
|
291
330
|
```
|
|
292
331
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
332
|
+
Available events:
|
|
333
|
+
- `LOGIN`
|
|
334
|
+
- `LOGOUT`
|
|
335
|
+
- `SIGNUP`
|
|
336
|
+
- `PASSWORD_RESET`
|
|
337
|
+
- `PASSWORD_CHANGE`
|
|
338
|
+
- `PROFILE_UPDATE`
|
|
339
|
+
- `AUTH_CHECK`
|
|
340
|
+
- `EMAIL_VERIFIED`
|
|
341
|
+
- `SESSION_REFRESH`
|
|
296
342
|
|
|
297
|
-
|
|
298
|
-
<!-- AuthLayout.vue -->
|
|
299
|
-
<template>
|
|
300
|
-
<div class="auth-layout">
|
|
301
|
-
<header>
|
|
302
|
-
<img src="/logo.png" alt="Logo">
|
|
303
|
-
</header>
|
|
304
|
-
<main>
|
|
305
|
-
<router-view />
|
|
306
|
-
</main>
|
|
307
|
-
<footer>
|
|
308
|
-
© 2024 Your Company
|
|
309
|
-
</footer>
|
|
310
|
-
</div>
|
|
311
|
-
</template>
|
|
312
|
-
```
|
|
343
|
+
## TypeScript Support
|
|
313
344
|
|
|
314
|
-
|
|
345
|
+
Full type definitions included:
|
|
315
346
|
|
|
316
347
|
```typescript
|
|
317
|
-
import
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
348
|
+
import type {
|
|
349
|
+
User,
|
|
350
|
+
AccountInfo,
|
|
351
|
+
AuthState,
|
|
352
|
+
SSOProvider,
|
|
353
|
+
RedirectConfig,
|
|
354
|
+
LoginTexts,
|
|
355
|
+
SignupTexts,
|
|
356
|
+
} from '@bagelink/auth'
|
|
322
357
|
```
|
|
323
358
|
|
|
324
|
-
##
|
|
359
|
+
## Examples
|
|
325
360
|
|
|
326
|
-
### Protected
|
|
361
|
+
### Protected Route Flow
|
|
327
362
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (to.meta.requiresAuth && !isAuthenticated.value) {
|
|
333
|
-
next('/login')
|
|
334
|
-
} else {
|
|
335
|
-
next()
|
|
336
|
-
}
|
|
337
|
-
})
|
|
338
|
-
```
|
|
363
|
+
1. User visits `/dashboard` (protected)
|
|
364
|
+
2. Not authenticated → redirected to `/login?redirect=/dashboard`
|
|
365
|
+
3. User logs in
|
|
366
|
+
4. Auto-redirected to `/dashboard`
|
|
339
367
|
|
|
340
|
-
###
|
|
368
|
+
### Custom Login Page
|
|
341
369
|
|
|
342
|
-
```
|
|
343
|
-
|
|
370
|
+
```vue
|
|
371
|
+
<script setup lang="ts">
|
|
372
|
+
import { ref } from 'vue'
|
|
344
373
|
import { useAuth } from '@bagelink/auth'
|
|
345
374
|
|
|
346
|
-
const
|
|
347
|
-
const
|
|
375
|
+
const auth = useAuth()
|
|
376
|
+
const email = ref('')
|
|
377
|
+
const password = ref('')
|
|
378
|
+
const error = ref('')
|
|
348
379
|
|
|
349
380
|
async function handleLogin() {
|
|
350
381
|
try {
|
|
351
|
-
await login(email, password)
|
|
352
|
-
|
|
353
|
-
} catch (
|
|
354
|
-
|
|
382
|
+
await auth.login({ email: email.value, password: password.value })
|
|
383
|
+
// Auto-redirect happens automatically!
|
|
384
|
+
} catch (err: any) {
|
|
385
|
+
error.value = err.response?.data?.message || 'Login failed'
|
|
355
386
|
}
|
|
356
387
|
}
|
|
388
|
+
</script>
|
|
389
|
+
|
|
390
|
+
<template>
|
|
391
|
+
<form @submit.prevent="handleLogin">
|
|
392
|
+
<input v-model="email" type="email" placeholder="Email" />
|
|
393
|
+
<input v-model="password" type="password" placeholder="Password" />
|
|
394
|
+
<button type="submit">Login</button>
|
|
395
|
+
<p v-if="error" class="error">{{ error }}</p>
|
|
396
|
+
</form>
|
|
397
|
+
</template>
|
|
357
398
|
```
|
|
358
399
|
|
|
359
|
-
###
|
|
400
|
+
### Role-Based Access
|
|
360
401
|
|
|
361
402
|
```typescript
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
await login(email, password)
|
|
366
|
-
} catch (error) {
|
|
367
|
-
if (error.code === 'INVALID_CREDENTIALS') {
|
|
368
|
-
// Handle invalid credentials
|
|
369
|
-
} else if (error.code === 'NETWORK_ERROR') {
|
|
370
|
-
// Handle network error
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
```
|
|
403
|
+
import { useAuth } from '@bagelink/auth'
|
|
374
404
|
|
|
375
|
-
|
|
405
|
+
const auth = useAuth()
|
|
376
406
|
|
|
377
|
-
|
|
407
|
+
// Check if user has admin role
|
|
408
|
+
if (auth.getRoles().includes('admin')) {
|
|
409
|
+
// Allow access
|
|
410
|
+
}
|
|
378
411
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
AuthRouteConfig
|
|
388
|
-
} from '@bagelink/auth'
|
|
412
|
+
// Or in route guard
|
|
413
|
+
router.beforeEach((to, from, next) => {
|
|
414
|
+
if (to.meta.requiresAdmin && !auth.getRoles().includes('admin')) {
|
|
415
|
+
next('/unauthorized')
|
|
416
|
+
} else {
|
|
417
|
+
next()
|
|
418
|
+
}
|
|
419
|
+
})
|
|
389
420
|
```
|
|
390
421
|
|
|
422
|
+
## Migration
|
|
423
|
+
|
|
424
|
+
See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) for migration guide from previous versions.
|
|
425
|
+
|
|
391
426
|
## License
|
|
392
427
|
|
|
393
428
|
MIT
|