@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 CHANGED
@@ -1,6 +1,16 @@
1
1
  # @bagelink/auth
2
2
 
3
- Authentication library for Bagelink applications with built-in SSO support, route management, and Vue components.
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
- createAuth({
19
- baseURL: 'https://api.your-app.com'
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. Add Routes
49
+ ### 2. Define Routes
24
50
 
25
51
  ```typescript
26
- import { createRouter } from 'vue-router'
27
- import { createAuthRoutes, createAuthGuard } from '@bagelink/auth'
28
-
29
- const router = createRouter({
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 { user, logout, isAuthenticated } = useAuth()
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="isAuthenticated">
64
- <p>Welcome, {{ user.name }}</p>
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
- ## Route Configuration
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
- ### Basic Setup
106
+ ## Core API
107
+
108
+ ### `createAuth(config)`
109
+
110
+ Initialize the auth module with redirect configuration:
73
111
 
74
112
  ```typescript
75
- createAuthRoutes()
76
- // Creates routes: /login, /signup, /forgot-password, /reset-password, /callback
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
- ### Custom Base Path
127
+ ### `useAuth()`
128
+
129
+ Returns auth composable with:
80
130
 
81
131
  ```typescript
82
- createAuthRoutes({
83
- basePath: '/auth'
84
- })
85
- // Creates routes: /auth/login, /auth/signup, etc.
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
- ### Custom Route Names
195
+ #### Option 2: Multiple `beforeEach` calls
89
196
 
90
197
  ```typescript
91
- createAuthRoutes({
92
- routeNames: {
93
- login: 'UserLogin',
94
- signup: 'UserRegister',
95
- callback: 'OAuthCallback'
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
- ### With Layout Component
212
+ #### Option 3: Manual composition
101
213
 
102
214
  ```typescript
103
- import AuthLayout from './layouts/AuthLayout.vue'
215
+ auth.use(router, { guard: false })
104
216
 
105
- createAuthRoutes({
106
- layout: AuthLayout
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
- ### Form Components
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 { LoginForm, SignupForm } from '@bagelink/auth'
247
+ import { LoginPage, SignupPage, ForgotPasswordPage } from '@bagelink/auth'
119
248
  </script>
120
249
 
121
250
  <template>
122
- <LoginForm
251
+ <LoginPage
123
252
  :texts="{ title: 'Welcome Back' }"
124
- :show-github="true"
125
- :show-google="true"
126
- @switch-form="handleFormSwitch"
253
+ card-width="400px"
127
254
  />
128
255
  </template>
129
256
  ```
130
257
 
131
- ### Page Components
258
+ ### Form Components
132
259
 
133
- Pre-built page components with layout:
260
+ For custom layouts:
134
261
 
135
262
  ```vue
136
263
  <script setup>
137
- import { LoginPage } from '@bagelink/auth'
264
+ import { LoginForm, SignupForm } from '@bagelink/auth'
138
265
  </script>
139
266
 
140
267
  <template>
141
- <LoginPage
142
- :texts="{ title: 'Sign In' }"
143
- card-width="500px"
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 Configuration
278
+ ## SSO Integration
149
279
 
150
- ### Available Providers
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
- // Initiate SSO login
165
- await sso.login('github')
166
-
167
- // Handle callback (automatically handled in /callback route)
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
- ### Customizing SSO Buttons
291
+ ### SSO Callback Route
175
292
 
176
- ```vue
177
- <LoginForm
178
- :show-github="true"
179
- :show-google="true"
180
- :show-microsoft="false"
181
- :sso-outline="true"
182
- :sso-show-value="true"
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
- ## API
188
-
189
- ### `useAuth()`
190
-
191
- Returns the authentication composable with:
302
+ ### Available Providers
192
303
 
193
- - `user` - Current user object (reactive)
194
- - `isAuthenticated` - Boolean indicating auth status (computed)
195
- - `accountInfo` - Extended account information (reactive)
196
- - `login(email, password)` - Login with credentials
197
- - `signup(data)` - Create new account
198
- - `logout()` - Logout current user
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
- ### `createAuth(config)`
311
+ ## Advanced Configuration
204
312
 
205
- Initialize the auth module:
313
+ ### Security: Restrict Redirect Paths
206
314
 
207
315
  ```typescript
208
316
  createAuth({
209
- baseURL: string // Required: Your API endpoint
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
- ### `createAuthRoutes(config)`
329
+ ### Disable Auto-Redirect
214
330
 
215
- Create auth routes for Vue Router:
331
+ For custom logic:
216
332
 
217
333
  ```typescript
218
- createAuthRoutes({
219
- basePath?: string // Base path for routes (default: '')
220
- namePrefix?: string // Prefix for route names (default: '')
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
- ### `createAuthGuard(config)`
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
- Create a navigation guard:
347
+ ### Custom Route Meta Key
236
348
 
237
349
  ```typescript
238
- createAuthGuard({
239
- redirectTo?: string // Where to redirect authenticated users (default: '/')
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
- ## Customization
363
+ ## Event System
244
364
 
245
- ### Text Customization
365
+ Listen to auth events:
246
366
 
247
- All forms support custom text via the `texts` prop:
367
+ ```typescript
368
+ import { createAuth, AuthState } from '@bagelink/auth'
248
369
 
249
- ```vue
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
- ### Multi-language Support
372
+ auth.on(AuthState.LOGIN, () => {
373
+ console.log('User logged in')
374
+ })
262
375
 
263
- ```vue
264
- <script setup>
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
- <template>
289
- <LoginForm :texts="texts" />
290
- </template>
380
+ auth.on(AuthState.PROFILE_UPDATE, () => {
381
+ console.log('Profile updated')
382
+ })
291
383
  ```
292
384
 
293
- ### Custom Layout
294
-
295
- Create a layout wrapper:
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
- ```vue
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
- Then use it:
398
+ Full type definitions included:
315
399
 
316
400
  ```typescript
317
- import AuthLayout from './AuthLayout.vue'
318
-
319
- createAuthRoutes({
320
- layout: AuthLayout
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
- ## Advanced Usage
412
+ ## Examples
325
413
 
326
- ### Protected Routes
414
+ ### Protected Route Flow
327
415
 
328
- ```typescript
329
- router.beforeEach(async (to, from, next) => {
330
- const { isAuthenticated } = useAuth()
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
- ### Programmatic Navigation
421
+ ### Custom Login Page
341
422
 
342
- ```typescript
343
- import { useRouter } from 'vue-router'
423
+ ```vue
424
+ <script setup lang="ts">
425
+ import { ref } from 'vue'
344
426
  import { useAuth } from '@bagelink/auth'
345
427
 
346
- const router = useRouter()
347
- const { login } = useAuth()
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
- router.push('/dashboard')
353
- } catch (error) {
354
- console.error('Login failed:', error)
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
- ### Custom Error Handling
453
+ ### Role-Based Access
360
454
 
361
455
  ```typescript
362
- const { login } = useAuth()
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
- ## TypeScript Support
458
+ const auth = useAuth()
376
459
 
377
- Full TypeScript support with exported types:
460
+ // Check if user has admin role
461
+ if (auth.getRoles().includes('admin')) {
462
+ // Allow access
463
+ }
378
464
 
379
- ```typescript
380
- import type {
381
- AuthState,
382
- User,
383
- AccountInfo,
384
- SSOProvider,
385
- LoginTexts,
386
- SignupTexts,
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