@bagelink/auth 1.7.72 → 1.7.76
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 +332 -244
- package/dist/index.cjs +307 -74
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +307 -74
- package/dist/redirect.d.ts +21 -0
- package/dist/router.d.ts +36 -0
- package/dist/types/redirect.d.ts +70 -0
- package/dist/useAuth.d.ts +57 -0
- package/package.json +1 -1
- package/src/index.ts +10 -2
- package/src/redirect.ts +95 -0
- package/src/router.ts +129 -0
- package/src/types/redirect.ts +96 -0
- package/src/useAuth.ts +128 -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,459 @@ npm install @bagelink/auth
|
|
|
13
23
|
### 1. Initialize Auth
|
|
14
24
|
|
|
15
25
|
```typescript
|
|
26
|
+
// main.ts
|
|
27
|
+
import { createApp } from 'vue'
|
|
16
28
|
import { createAuth } from '@bagelink/auth'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 (automatically sets up auth guard)
|
|
41
|
+
auth.use(router)
|
|
42
|
+
|
|
43
|
+
const app = createApp(App)
|
|
44
|
+
app.use(router)
|
|
45
|
+
app.use(auth) // Optional: adds $auth globally
|
|
46
|
+
app.mount('#app')
|
|
21
47
|
```
|
|
22
48
|
|
|
23
|
-
### 2.
|
|
49
|
+
### 2. Define Routes
|
|
24
50
|
|
|
25
51
|
```typescript
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
const
|
|
52
|
+
// router/index.ts
|
|
53
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
54
|
+
|
|
55
|
+
const routes = [
|
|
56
|
+
{
|
|
57
|
+
path: '/login',
|
|
58
|
+
name: 'Login',
|
|
59
|
+
component: () => import('@/views/Login.vue'),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
path: '/dashboard',
|
|
63
|
+
name: 'Dashboard',
|
|
64
|
+
component: () => import('@/views/Dashboard.vue'),
|
|
65
|
+
meta: { auth: true }, // Protected route
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
export default createRouter({
|
|
30
70
|
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
|
-
]
|
|
71
|
+
routes,
|
|
45
72
|
})
|
|
46
|
-
|
|
47
|
-
// Add auth guard to redirect authenticated users
|
|
48
|
-
router.beforeEach(createAuthGuard({ redirectTo: '/dashboard' }))
|
|
49
|
-
|
|
50
|
-
export default router
|
|
51
73
|
```
|
|
52
74
|
|
|
53
75
|
### 3. Use in Components
|
|
54
76
|
|
|
55
77
|
```vue
|
|
56
|
-
<script setup>
|
|
78
|
+
<script setup lang="ts">
|
|
57
79
|
import { useAuth } from '@bagelink/auth'
|
|
58
80
|
|
|
59
|
-
const
|
|
81
|
+
const auth = useAuth()
|
|
82
|
+
|
|
83
|
+
async function handleLogin() {
|
|
84
|
+
await auth.login({
|
|
85
|
+
email: 'user@example.com',
|
|
86
|
+
password: 'password'
|
|
87
|
+
})
|
|
88
|
+
// Auto-redirect happens automatically! 🎉
|
|
89
|
+
}
|
|
60
90
|
</script>
|
|
61
91
|
|
|
62
92
|
<template>
|
|
63
|
-
<div v-if="
|
|
64
|
-
<p>Welcome, {{ user.name }}
|
|
65
|
-
<button @click="logout">Logout</button>
|
|
93
|
+
<div v-if="auth.user.value">
|
|
94
|
+
<p>Welcome, {{ auth.user.value.name }}!</p>
|
|
95
|
+
<button @click="auth.logout()">Logout</button>
|
|
66
96
|
</div>
|
|
67
97
|
</template>
|
|
68
98
|
```
|
|
69
99
|
|
|
70
|
-
##
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
- **[REDIRECT_GUIDE.md](./REDIRECT_GUIDE.md)** - Complete redirect configuration guide
|
|
103
|
+
- **[COMPLETE_EXAMPLE.md](./COMPLETE_EXAMPLE.md)** - Full working application example
|
|
104
|
+
- **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** - Migration guide for breaking changes
|
|
71
105
|
|
|
72
|
-
|
|
106
|
+
## Core API
|
|
107
|
+
|
|
108
|
+
### `createAuth(config)`
|
|
109
|
+
|
|
110
|
+
Initialize the auth module with redirect configuration:
|
|
73
111
|
|
|
74
112
|
```typescript
|
|
75
|
-
|
|
76
|
-
//
|
|
113
|
+
createAuth({
|
|
114
|
+
baseURL: string, // Required: Your API base URL
|
|
115
|
+
redirect?: {
|
|
116
|
+
loginRoute?: string, // Login route name (default: 'Login')
|
|
117
|
+
fallback?: string, // Default redirect after login (default: '/')
|
|
118
|
+
queryKey?: string, // Query param for redirect URL (default: 'redirect')
|
|
119
|
+
autoRedirect?: boolean, // Auto-redirect after login (default: true)
|
|
120
|
+
preserveRedirect?: boolean, // Preserve original URL (default: true)
|
|
121
|
+
noAuthRoutes?: string[], // Routes requiring no auth (default: ['Login', 'Signup', ...])
|
|
122
|
+
allowedPaths?: RegExp[], // Optional: Restrict allowed redirects for security
|
|
123
|
+
}
|
|
124
|
+
})
|
|
77
125
|
```
|
|
78
126
|
|
|
79
|
-
###
|
|
127
|
+
### `useAuth()`
|
|
128
|
+
|
|
129
|
+
Returns auth composable with:
|
|
80
130
|
|
|
81
131
|
```typescript
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
132
|
+
{
|
|
133
|
+
// State
|
|
134
|
+
user: Ref<User | null>
|
|
135
|
+
accountInfo: Ref<AccountInfo | null>
|
|
136
|
+
|
|
137
|
+
// Authentication
|
|
138
|
+
login(credentials): Promise<AuthenticationResponse>
|
|
139
|
+
signup(data): Promise<AuthenticationResponse>
|
|
140
|
+
logout(): Promise<void>
|
|
141
|
+
checkAuth(): Promise<boolean>
|
|
142
|
+
|
|
143
|
+
// SSO
|
|
144
|
+
sso: {
|
|
145
|
+
google: { redirect(), callback() }
|
|
146
|
+
github: { redirect(), callback() }
|
|
147
|
+
microsoft: { redirect(), callback() }
|
|
148
|
+
// ... more providers
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Password Management
|
|
152
|
+
forgotPassword(email): Promise<void>
|
|
153
|
+
resetPassword(token, newPassword): Promise<void>
|
|
154
|
+
changePassword(current, new): Promise<void>
|
|
155
|
+
|
|
156
|
+
// Profile
|
|
157
|
+
updateProfile(updates): Promise<void>
|
|
158
|
+
|
|
159
|
+
// Getters
|
|
160
|
+
getFullName(): string
|
|
161
|
+
getIsLoggedIn(): boolean
|
|
162
|
+
getEmail(): string
|
|
163
|
+
getRoles(): string[]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Custom Guard Composition
|
|
168
|
+
|
|
169
|
+
For multi-tenant apps or advanced permission logic, disable the auto-guard and compose your own:
|
|
170
|
+
|
|
171
|
+
#### Option 1: Using `composeGuards` utility (recommended)
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { composeGuards } from '@bagelink/auth'
|
|
175
|
+
|
|
176
|
+
// Disable auto-guard
|
|
177
|
+
auth.use(router, { guard: false })
|
|
178
|
+
|
|
179
|
+
// Custom org access guard
|
|
180
|
+
const orgAccessGuard = () => async (to, from, next) => {
|
|
181
|
+
if (to.meta.requiresOrg) {
|
|
182
|
+
const hasAccess = await checkOrgAccess(to.params.orgId)
|
|
183
|
+
if (!hasAccess) return next('/no-access')
|
|
184
|
+
}
|
|
185
|
+
next()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Compose all guards in sequence
|
|
189
|
+
router.beforeEach(composeGuards([
|
|
190
|
+
auth.routerGuard(), // Auth first
|
|
191
|
+
orgAccessGuard(), // Then org check
|
|
192
|
+
]))
|
|
86
193
|
```
|
|
87
194
|
|
|
88
|
-
|
|
195
|
+
#### Option 2: Multiple `beforeEach` calls
|
|
89
196
|
|
|
90
197
|
```typescript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
198
|
+
auth.use(router, { guard: false })
|
|
199
|
+
|
|
200
|
+
// Guards run in order they're registered
|
|
201
|
+
router.beforeEach(auth.routerGuard())
|
|
202
|
+
|
|
203
|
+
router.beforeEach(async (to, from, next) => {
|
|
204
|
+
// Custom org/tenant check
|
|
205
|
+
if (to.meta.requiresOrg && !await checkOrgAccess(to.params.orgId)) {
|
|
206
|
+
return next('/no-access')
|
|
96
207
|
}
|
|
208
|
+
next()
|
|
97
209
|
})
|
|
98
210
|
```
|
|
99
211
|
|
|
100
|
-
|
|
212
|
+
#### Option 3: Manual composition
|
|
101
213
|
|
|
102
214
|
```typescript
|
|
103
|
-
|
|
215
|
+
auth.use(router, { guard: false })
|
|
104
216
|
|
|
105
|
-
|
|
106
|
-
|
|
217
|
+
router.beforeEach(async (to, from, next) => {
|
|
218
|
+
// Run auth guard first
|
|
219
|
+
const authPassed = await new Promise<boolean>((resolve) => {
|
|
220
|
+
auth.routerGuard()(to, from, (result?: any) => {
|
|
221
|
+
if (result !== undefined) {
|
|
222
|
+
next(result)
|
|
223
|
+
resolve(false)
|
|
224
|
+
} else {
|
|
225
|
+
resolve(true)
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if (!authPassed) return
|
|
231
|
+
|
|
232
|
+
// Your custom logic
|
|
233
|
+
if (to.meta.requiresOrg && !await checkOrgAccess(to.params.orgId)) {
|
|
234
|
+
return next('/no-access')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
next()
|
|
107
238
|
})
|
|
108
239
|
```
|
|
109
240
|
|
|
110
241
|
## Components
|
|
111
242
|
|
|
112
|
-
###
|
|
113
|
-
|
|
114
|
-
Use these if you want to build custom pages:
|
|
243
|
+
### Pre-built Pages
|
|
115
244
|
|
|
116
245
|
```vue
|
|
117
246
|
<script setup>
|
|
118
|
-
import {
|
|
247
|
+
import { LoginPage, SignupPage, ForgotPasswordPage } from '@bagelink/auth'
|
|
119
248
|
</script>
|
|
120
249
|
|
|
121
250
|
<template>
|
|
122
|
-
<
|
|
251
|
+
<LoginPage
|
|
123
252
|
:texts="{ title: 'Welcome Back' }"
|
|
124
|
-
|
|
125
|
-
:show-google="true"
|
|
126
|
-
@switch-form="handleFormSwitch"
|
|
253
|
+
card-width="400px"
|
|
127
254
|
/>
|
|
128
255
|
</template>
|
|
129
256
|
```
|
|
130
257
|
|
|
131
|
-
###
|
|
258
|
+
### Form Components
|
|
132
259
|
|
|
133
|
-
|
|
260
|
+
For custom layouts:
|
|
134
261
|
|
|
135
262
|
```vue
|
|
136
263
|
<script setup>
|
|
137
|
-
import {
|
|
264
|
+
import { LoginForm, SignupForm } from '@bagelink/auth'
|
|
138
265
|
</script>
|
|
139
266
|
|
|
140
267
|
<template>
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
268
|
+
<div class="custom-layout">
|
|
269
|
+
<LoginForm
|
|
270
|
+
:texts="{ title: 'Sign In' }"
|
|
271
|
+
:show-google="true"
|
|
272
|
+
:show-github="true"
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
145
275
|
</template>
|
|
146
276
|
```
|
|
147
277
|
|
|
148
|
-
## SSO
|
|
278
|
+
## SSO Integration
|
|
149
279
|
|
|
150
|
-
###
|
|
151
|
-
|
|
152
|
-
- GitHub
|
|
153
|
-
- Google
|
|
154
|
-
- Microsoft
|
|
155
|
-
- Apple
|
|
156
|
-
- Okta
|
|
157
|
-
- Facebook
|
|
158
|
-
|
|
159
|
-
### Usage
|
|
280
|
+
### Quick SSO Login
|
|
160
281
|
|
|
161
282
|
```typescript
|
|
162
283
|
const { sso } = useAuth()
|
|
163
284
|
|
|
164
|
-
//
|
|
165
|
-
await sso.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
await sso.handleCallback()
|
|
169
|
-
|
|
170
|
-
// Link additional account
|
|
171
|
-
await sso.linkAccount('google')
|
|
285
|
+
// Redirect to Google OAuth
|
|
286
|
+
await sso.google.redirect({
|
|
287
|
+
redirect_uri: `${window.location.origin}/auth/callback`
|
|
288
|
+
})
|
|
172
289
|
```
|
|
173
290
|
|
|
174
|
-
###
|
|
291
|
+
### SSO Callback Route
|
|
175
292
|
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
:
|
|
180
|
-
:
|
|
181
|
-
:
|
|
182
|
-
|
|
183
|
-
:sso-brand-background="true"
|
|
184
|
-
/>
|
|
293
|
+
```typescript
|
|
294
|
+
// router.ts
|
|
295
|
+
{
|
|
296
|
+
path: '/auth/callback',
|
|
297
|
+
name: 'Callback',
|
|
298
|
+
component: () => import('@bagelink/auth').then(m => m.Callback),
|
|
299
|
+
}
|
|
185
300
|
```
|
|
186
301
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
### `useAuth()`
|
|
190
|
-
|
|
191
|
-
Returns the authentication composable with:
|
|
302
|
+
### Available Providers
|
|
192
303
|
|
|
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
|
|
304
|
+
- Google
|
|
305
|
+
- GitHub
|
|
306
|
+
- Microsoft
|
|
307
|
+
- Apple
|
|
308
|
+
- Okta
|
|
309
|
+
- Facebook
|
|
202
310
|
|
|
203
|
-
|
|
311
|
+
## Advanced Configuration
|
|
204
312
|
|
|
205
|
-
|
|
313
|
+
### Security: Restrict Redirect Paths
|
|
206
314
|
|
|
207
315
|
```typescript
|
|
208
316
|
createAuth({
|
|
209
|
-
baseURL:
|
|
317
|
+
baseURL: 'https://api.example.com',
|
|
318
|
+
redirect: {
|
|
319
|
+
allowedPaths: [
|
|
320
|
+
/^\/dashboard/,
|
|
321
|
+
/^\/profile/,
|
|
322
|
+
/^\/settings/,
|
|
323
|
+
],
|
|
324
|
+
// Only these paths can be redirect targets
|
|
325
|
+
}
|
|
210
326
|
})
|
|
211
327
|
```
|
|
212
328
|
|
|
213
|
-
###
|
|
329
|
+
### Disable Auto-Redirect
|
|
214
330
|
|
|
215
|
-
|
|
331
|
+
For custom logic:
|
|
216
332
|
|
|
217
333
|
```typescript
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
routeNames?: { // Custom route names
|
|
222
|
-
login?: string
|
|
223
|
-
signup?: string
|
|
224
|
-
forgotPassword?: string
|
|
225
|
-
resetPassword?: string
|
|
226
|
-
callback?: string
|
|
334
|
+
createAuth({
|
|
335
|
+
redirect: {
|
|
336
|
+
autoRedirect: false, // Manual control
|
|
227
337
|
}
|
|
228
|
-
redirectTo?: string // Redirect after auth (default: '/')
|
|
229
|
-
layout?: Component // Wrapper layout component
|
|
230
338
|
})
|
|
231
|
-
```
|
|
232
339
|
|
|
233
|
-
|
|
340
|
+
// Then in login handler:
|
|
341
|
+
import { performRedirect, getRedirectConfig } from '@bagelink/auth'
|
|
342
|
+
|
|
343
|
+
await auth.login(credentials)
|
|
344
|
+
await performRedirect(router, getRedirectConfig())
|
|
345
|
+
```
|
|
234
346
|
|
|
235
|
-
|
|
347
|
+
### Custom Route Meta Key
|
|
236
348
|
|
|
237
349
|
```typescript
|
|
238
|
-
|
|
239
|
-
|
|
350
|
+
createAuth({
|
|
351
|
+
redirect: {
|
|
352
|
+
authMetaKey: 'requiresAuth', // Default: 'auth'
|
|
353
|
+
}
|
|
240
354
|
})
|
|
355
|
+
|
|
356
|
+
// Then in routes:
|
|
357
|
+
{
|
|
358
|
+
path: '/dashboard',
|
|
359
|
+
meta: { requiresAuth: true } // Custom meta key
|
|
360
|
+
}
|
|
241
361
|
```
|
|
242
362
|
|
|
243
|
-
##
|
|
363
|
+
## Event System
|
|
244
364
|
|
|
245
|
-
|
|
365
|
+
Listen to auth events:
|
|
246
366
|
|
|
247
|
-
|
|
367
|
+
```typescript
|
|
368
|
+
import { createAuth, AuthState } from '@bagelink/auth'
|
|
248
369
|
|
|
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
|
-
```
|
|
370
|
+
const auth = createAuth({ ... })
|
|
260
371
|
|
|
261
|
-
|
|
372
|
+
auth.on(AuthState.LOGIN, () => {
|
|
373
|
+
console.log('User logged in')
|
|
374
|
+
})
|
|
262
375
|
|
|
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
|
-
}
|
|
376
|
+
auth.on(AuthState.LOGOUT, () => {
|
|
377
|
+
console.log('User logged out')
|
|
285
378
|
})
|
|
286
|
-
</script>
|
|
287
379
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
380
|
+
auth.on(AuthState.PROFILE_UPDATE, () => {
|
|
381
|
+
console.log('Profile updated')
|
|
382
|
+
})
|
|
291
383
|
```
|
|
292
384
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
385
|
+
Available events:
|
|
386
|
+
- `LOGIN`
|
|
387
|
+
- `LOGOUT`
|
|
388
|
+
- `SIGNUP`
|
|
389
|
+
- `PASSWORD_RESET`
|
|
390
|
+
- `PASSWORD_CHANGE`
|
|
391
|
+
- `PROFILE_UPDATE`
|
|
392
|
+
- `AUTH_CHECK`
|
|
393
|
+
- `EMAIL_VERIFIED`
|
|
394
|
+
- `SESSION_REFRESH`
|
|
296
395
|
|
|
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
|
-
```
|
|
396
|
+
## TypeScript Support
|
|
313
397
|
|
|
314
|
-
|
|
398
|
+
Full type definitions included:
|
|
315
399
|
|
|
316
400
|
```typescript
|
|
317
|
-
import
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
401
|
+
import type {
|
|
402
|
+
User,
|
|
403
|
+
AccountInfo,
|
|
404
|
+
AuthState,
|
|
405
|
+
SSOProvider,
|
|
406
|
+
RedirectConfig,
|
|
407
|
+
LoginTexts,
|
|
408
|
+
SignupTexts,
|
|
409
|
+
} from '@bagelink/auth'
|
|
322
410
|
```
|
|
323
411
|
|
|
324
|
-
##
|
|
412
|
+
## Examples
|
|
325
413
|
|
|
326
|
-
### Protected
|
|
414
|
+
### Protected Route Flow
|
|
327
415
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (to.meta.requiresAuth && !isAuthenticated.value) {
|
|
333
|
-
next('/login')
|
|
334
|
-
} else {
|
|
335
|
-
next()
|
|
336
|
-
}
|
|
337
|
-
})
|
|
338
|
-
```
|
|
416
|
+
1. User visits `/dashboard` (protected)
|
|
417
|
+
2. Not authenticated → redirected to `/login?redirect=/dashboard`
|
|
418
|
+
3. User logs in
|
|
419
|
+
4. Auto-redirected to `/dashboard`
|
|
339
420
|
|
|
340
|
-
###
|
|
421
|
+
### Custom Login Page
|
|
341
422
|
|
|
342
|
-
```
|
|
343
|
-
|
|
423
|
+
```vue
|
|
424
|
+
<script setup lang="ts">
|
|
425
|
+
import { ref } from 'vue'
|
|
344
426
|
import { useAuth } from '@bagelink/auth'
|
|
345
427
|
|
|
346
|
-
const
|
|
347
|
-
const
|
|
428
|
+
const auth = useAuth()
|
|
429
|
+
const email = ref('')
|
|
430
|
+
const password = ref('')
|
|
431
|
+
const error = ref('')
|
|
348
432
|
|
|
349
433
|
async function handleLogin() {
|
|
350
434
|
try {
|
|
351
|
-
await login(email, password)
|
|
352
|
-
|
|
353
|
-
} catch (
|
|
354
|
-
|
|
435
|
+
await auth.login({ email: email.value, password: password.value })
|
|
436
|
+
// Auto-redirect happens automatically!
|
|
437
|
+
} catch (err: any) {
|
|
438
|
+
error.value = err.response?.data?.message || 'Login failed'
|
|
355
439
|
}
|
|
356
440
|
}
|
|
441
|
+
</script>
|
|
442
|
+
|
|
443
|
+
<template>
|
|
444
|
+
<form @submit.prevent="handleLogin">
|
|
445
|
+
<input v-model="email" type="email" placeholder="Email" />
|
|
446
|
+
<input v-model="password" type="password" placeholder="Password" />
|
|
447
|
+
<button type="submit">Login</button>
|
|
448
|
+
<p v-if="error" class="error">{{ error }}</p>
|
|
449
|
+
</form>
|
|
450
|
+
</template>
|
|
357
451
|
```
|
|
358
452
|
|
|
359
|
-
###
|
|
453
|
+
### Role-Based Access
|
|
360
454
|
|
|
361
455
|
```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
|
-
```
|
|
456
|
+
import { useAuth } from '@bagelink/auth'
|
|
374
457
|
|
|
375
|
-
|
|
458
|
+
const auth = useAuth()
|
|
376
459
|
|
|
377
|
-
|
|
460
|
+
// Check if user has admin role
|
|
461
|
+
if (auth.getRoles().includes('admin')) {
|
|
462
|
+
// Allow access
|
|
463
|
+
}
|
|
378
464
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
AuthRouteConfig
|
|
388
|
-
} from '@bagelink/auth'
|
|
465
|
+
// Or in route guard
|
|
466
|
+
router.beforeEach((to, from, next) => {
|
|
467
|
+
if (to.meta.requiresAdmin && !auth.getRoles().includes('admin')) {
|
|
468
|
+
next('/unauthorized')
|
|
469
|
+
} else {
|
|
470
|
+
next()
|
|
471
|
+
}
|
|
472
|
+
})
|
|
389
473
|
```
|
|
390
474
|
|
|
475
|
+
## Migration
|
|
476
|
+
|
|
477
|
+
See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) for migration guide from previous versions.
|
|
478
|
+
|
|
391
479
|
## License
|
|
392
480
|
|
|
393
481
|
MIT
|