@meistrari/auth-nuxt 2.4.0 → 4.0.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,428 +1,16 @@
1
1
  # @meistrari/auth-nuxt
2
2
 
3
- A Nuxt module that provides comprehensive authentication, organization management, and API key capabilities.
3
+ A Nuxt module for authentication, organization management, and API key capabilities.
4
4
 
5
- ## Installation
6
-
7
- ```bash
8
- npm install @meistrari/auth-nuxt
9
- ```
10
-
11
- ## Prerequisites
12
-
13
- Before setting up the SDK, make sure the following are configured in the Auth API:
14
-
15
- 1. **Allowed Origins**: Your application URL (e.g., `https://your-app.com`) must be added to the allowed origins list in the Auth API. This ensures the Auth API accepts requests from your application.
16
- 2. **Allowed Email Domains**: The email domains of users who will access the application must be added to the allowed email domains list in the Auth API. For example, if your users sign in with `@company.com` emails, that domain must be whitelisted.
17
-
18
- ## Setup
19
-
20
- Add the module to your `nuxt.config.ts`:
21
-
22
- ```typescript
23
- export default defineNuxtConfig({
24
- modules: ['@meistrari/auth-nuxt'],
25
- telaAuth: {
26
- apiUrl: 'https://your-auth-api.com',
27
- jwtCookieName: 'tela-jwt', // Optional: custom JWT cookie name
28
- skipServerMiddleware: false, // Optional: skip automatic server middleware
29
- }
30
- })
31
- ```
32
-
33
- ### Configuration Options
34
-
35
- | Option | Type | Default | Description |
36
- |--------|------|---------|-------------|
37
- | `apiUrl` | `string` | **Required** | Base URL of your authentication API |
38
- | `jwtCookieName` | `string` | `'tela-jwt'` | Name of the JWT cookie |
39
- | `skipServerMiddleware` | `boolean` | `false` | Skip automatic server-side auth context setup |
40
-
41
- ## Composables
42
-
43
- The SDK provides three main composables for different aspects of authentication and organization management:
44
-
45
- ### useTelaSession
46
-
47
- Manages user sessions, authentication, and sign-in/sign-out operations.
48
-
49
- ```vue
50
- <script setup>
51
- const { user, session, signOut, getToken } = useTelaSession()
52
-
53
- // Access current user
54
- console.log(user.value) // User object or null
55
-
56
- // Access current session
57
- console.log(session.value) // Session object or null
58
-
59
- // Get a valid JWT token
60
- const token = await getToken()
61
-
62
- // Sign out
63
- await signOut(() => {
64
- console.log('User signed out')
65
- })
66
- </script>
67
-
68
- <template>
69
- <div v-if="user">
70
- Welcome, {{ user.name }}!
71
- <button @click="signOut">
72
- Sign Out
73
- </button>
74
- </div>
75
- <div v-else>
76
- Please sign in
77
- </div>
78
- </template>
79
- ```
80
-
81
- #### Available Methods
82
-
83
- **Session Management:**
84
- - `getSession()` - Retrieves the current user session
85
- - `getToken()` - Retrieves a valid JWT token (refreshes if needed)
86
- - `signOut(callback?)` - Signs out the user
5
+ ## Auth Types
87
6
 
88
- **Authentication Methods:**
89
- - `signInWithEmailAndPassword(options)` - Email/password authentication
90
- - `signInWithSocialProvider(options)` - Social authentication (Google, Microsoft)
91
- - `signInWithSaml(options)` - SAML-based SSO authentication
7
+ This SDK supports two authentication models:
92
8
 
93
- **Password Management:**
94
- - `requestPasswordReset(email, callbackURL)` - Initiates password reset
95
- - `resetPassword(token, password)` - Completes password reset
9
+ - **Application Authentication (recommended)**: Use this for product applications that need end-user authentication and authorization scoped to a specific application. See [Application Authentication Guide](./docs/APPLICATION_AUTH.md).
10
+ - **First-Party Authentication**: Use this only for trusted first-party tools that interact directly with the Auth API in an administrative way, such as the Auth Dashboard and Backoffice. See [First-Party Authentication Guide](./docs/FIRST_PARTY_AUTH.md).
96
11
 
97
- #### Sign-In Examples
98
-
99
- **Email and Password:**
100
- ```typescript
101
- await signInWithEmailAndPassword({
102
- email: 'user@example.com',
103
- password: 'secure-password',
104
- })
105
- ```
106
-
107
- **Social Authentication:**
108
- ```typescript
109
- await signInWithSocialProvider({
110
- provider: 'google', // or 'microsoft'
111
- callbackURL: '/',
112
- errorCallbackURL: '/login?error=true'
113
- })
114
- ```
115
-
116
- **SAML SSO:**
117
- ```typescript
118
- await signInWithSaml({
119
- email: 'user@example.com',
120
- callbackURL: '/',
121
- errorCallbackURL: '/login?error=true'
122
- })
123
- ```
124
-
125
- ### useTelaOrganization
126
-
127
- Manages organizations, members, invitations, and teams.
128
-
129
- ```vue
130
- <script setup>
131
- const {
132
- activeOrganization,
133
- activeMember,
134
- getActiveOrganization,
135
- setActiveOrganization,
136
- inviteUserToOrganization
137
- } = useTelaOrganization()
138
-
139
- // Get the active organization
140
- await getActiveOrganization()
141
-
142
- // Switch organizations
143
- await setActiveOrganization('org-id')
144
-
145
- // Invite a user
146
- await inviteUserToOrganization({
147
- userEmail: 'user@example.com',
148
- role: 'member'
149
- })
150
- </script>
151
-
152
- <template>
153
- <div v-if="activeOrganization">
154
- <h2>{{ activeOrganization.name }}</h2>
155
- <p>{{ activeOrganization.members.length }} members</p>
156
- </div>
157
- </template>
158
- ```
159
-
160
- #### Available Methods
161
-
162
- **Organization Management:**
163
- - `getActiveOrganization()` - Gets the current active organization with members, invitations, and teams
164
- - `listOrganizations()` - Lists all organizations for the user
165
- - `setActiveOrganization(id)` - Sets the active organization
166
- - `updateOrganization(payload)` - Updates organization details (name, logo, settings)
167
-
168
- **Member Management:**
169
- - `listMembers(options?)` - Lists organization members with pagination
170
- - `getActiveMember()` - Gets the current user's member record
171
- - `inviteUserToOrganization(options)` - Invites a user to the organization
172
- - `removeUserFromOrganization(options)` - Removes a user from the organization
173
- - `updateMemberRole(options)` - Updates a member's role
174
- - `acceptInvitation(id)` - Accepts an organization invitation
175
- - `cancelInvitation(id)` - Cancels a pending invitation
176
-
177
- **Team Management:**
178
- - `createTeam(payload)` - Creates a new team
179
- - `updateTeam(id, payload)` - Updates team details
180
- - `deleteTeam(id)` - Deletes a team
181
- - `listTeams()` - Lists all teams
182
- - `listTeamMembers(id)` - Lists members of a specific team
183
- - `addTeamMember(teamId, userId)` - Adds a user to a team
184
- - `removeTeamMember(teamId, userId)` - Removes a user from a team
185
-
186
- #### Organization Examples
187
-
188
- **Update Organization:**
189
- ```typescript
190
- await updateOrganization({
191
- name: 'New Organization Name',
192
- logo: 'https://example.com/logo.png',
193
- settings: { /* custom settings */ }
194
- })
195
- ```
196
-
197
- **Invite Member:**
198
- ```typescript
199
- await inviteUserToOrganization({
200
- userEmail: 'user@example.com',
201
- role: 'admin', // or 'member', 'reviewer'
202
- teamId: 'team-id', // optional
203
- resend: false // optional: resend if invitation exists
204
- })
205
- ```
206
-
207
- **Create and Manage Teams:**
208
- ```typescript
209
- // Create team
210
- const team = await createTeam({
211
- name: 'Development Team'
212
- })
213
-
214
- // Add member to team
215
- await addTeamMember(team.id, 'user-id')
216
-
217
- // List team members
218
- const members = await listTeamMembers(team.id)
219
- ```
220
-
221
- ### useTelaApiKey
222
-
223
- Manages API keys for programmatic access.
224
-
225
- ```vue
226
- <script setup>
227
- const {
228
- listApiKeys,
229
- createApiKey,
230
- deleteApiKey
231
- } = useTelaApiKey()
232
-
233
- // List all API keys
234
- const apiKeys = await listApiKeys()
235
-
236
- // Create a new API key
237
- const newKey = await createApiKey({
238
- name: 'Production API Key',
239
- expiresIn: '90d',
240
- prefix: 'prod',
241
- metadata: { environment: 'production' }
242
- })
243
-
244
- // Delete an API key
245
- await deleteApiKey('key-id')
246
- </script>
247
-
248
- <template>
249
- <div v-for="key in apiKeys" :key="key.id">
250
- <span>{{ key.name }}</span>
251
- <button @click="deleteApiKey(key.id)">
252
- Delete
253
- </button>
254
- </div>
255
- </template>
256
- ```
257
-
258
- #### Available Methods
259
-
260
- - `listApiKeys()` - Lists all API keys for the current user
261
- - `getApiKey(id)` - Retrieves a specific API key
262
- - `createApiKey(payload)` - Creates a new API key
263
- - `updateApiKey(payload)` - Updates an API key (name)
264
- - `deleteApiKey(id)` - Deletes an API key
265
-
266
- ## Server-Side Usage
267
-
268
- The SDK automatically sets up server-side authentication context for API routes.
269
-
270
- ### Using Authentication Context
271
-
272
- ```typescript
273
- // server/api/protected.ts
274
- export default defineEventHandler(async (event) => {
275
- // Authentication context is automatically available
276
- const { user, workspace } = event.context.auth
277
-
278
- if (!user) {
279
- throw createError({
280
- statusCode: 401,
281
- statusMessage: 'Unauthorized'
282
- })
283
- }
284
-
285
- return {
286
- message: `Hello, ${user.email}!`,
287
- userId: user.id
288
- }
289
- })
290
- ```
291
-
292
- ### Custom Middleware
293
-
294
- Create custom server middleware using the provided helper:
295
-
296
- ```typescript
297
- // server/middleware/custom.ts
298
- import { meistrariAuthMiddleware } from '@meistrari/auth-nuxt/server/middleware/auth'
299
-
300
- export default meistrariAuthMiddleware(async (event) => {
301
- // event.context.auth contains user and workspace
302
- const { user, workspace } = event.context.auth
303
-
304
- // Your custom logic here
305
- if (user) {
306
- console.log(`Authenticated user: ${user.email}`)
307
- }
308
- })
309
- ```
310
-
311
- ## Types
312
-
313
- The SDK exports comprehensive TypeScript types from the core package:
314
-
315
- ```typescript
316
- import type {
317
- User,
318
- Session,
319
- Organization,
320
- FullOrganization,
321
- Member,
322
- Invitation,
323
- Team,
324
- TeamMember,
325
- ApiKey,
326
- CreateApiKeyPayload,
327
- UpdateApiKeyPayload,
328
- CreateTeamPayload,
329
- UpdateTeamPayload,
330
- InviteUserToOrganizationOptions,
331
- RemoveUserFromOrganizationOptions,
332
- UpdateMemberRoleOptions,
333
- UpdateOrganizationPayload
334
- } from '@meistrari/auth-nuxt/core'
335
- ```
336
-
337
- ### Key Types
338
-
339
- - **User**: User account information including email, name, and verification status
340
- - **Session**: Active session data with expiration and organization info
341
- - **Organization**: Basic organization entity
342
- - **FullOrganization**: Organization with members, invitations, and teams
343
- - **Member**: Organization member with role and team assignments
344
- - **Team**: Team entity within an organization
345
- - **Invitation**: Pending organization invitations
346
- - **ApiKey**: API key entity with metadata
347
-
348
- ## JWT Token Management
349
-
350
- The SDK automatically manages JWT tokens:
351
-
352
- - Stores JWT tokens in cookies (configurable name)
353
- - Refreshes tokens before expiration (every 60 seconds)
354
- - Validates tokens client-side and server-side
355
- - Provides `getToken()` method for accessing valid tokens
356
-
357
- The handshake flow ensures secure authentication:
358
-
359
- 1. On initial visit without a token, redirects to auth API for handshake
360
- 2. Auth API validates the session and redirects back with a nonce
361
- 3. SDK exchanges the nonce for a JWT token
362
- 4. Token is stored in a cookie and used for subsequent requests
363
-
364
- ## Utilities
365
-
366
- ### Token Validation
367
-
368
- ```typescript
369
- import { isTokenExpired, validateToken, extractTokenPayload } from '@meistrari/auth-nuxt/core'
370
-
371
- // Check if token is expired
372
- if (isTokenExpired(jwtToken)) {
373
- // Token needs refresh
374
- }
375
-
376
- // Validate token against JWKS endpoint
377
- const isValid = await validateToken(jwtToken, 'https://your-api.com')
378
-
379
- // Extract token payload (without validation)
380
- const payload = extractTokenPayload(jwtToken)
381
- console.log(payload.user, payload.workspace)
382
- ```
383
-
384
- ## Security Features
385
-
386
- - **JWT Validation**: Cryptographic validation using JWKS
387
- - **Secure Cookies**: HTTP-only, secure cookies in production
388
- - **Token Refresh**: Automatic token rotation every 60 seconds
389
- - **Session Management**: Secure session handling with handshake flow
390
- - **Server-Side Validation**: Middleware validates tokens on every API request
391
-
392
- ## Error Handling
393
-
394
- The SDK handles common authentication errors automatically:
395
-
396
- - Expired tokens trigger sign-out when refresh fails
397
- - Invalid tokens result in `null` user/session values
398
- - Network errors are handled gracefully
399
- - API errors can be caught and handled in your code
400
-
401
- ```typescript
402
- import { APIError } from '@meistrari/auth-nuxt/core'
12
+ ## Installation
403
13
 
404
- try {
405
- await signInWithEmailAndPassword({
406
- email: 'user@example.com',
407
- password: 'wrong-password',
408
- })
409
- }
410
- catch (error) {
411
- if (error instanceof APIError) {
412
- console.error('Authentication failed:', error.message, error.status)
413
- }
414
- }
14
+ ```bash
15
+ npm install @meistrari/auth-nuxt
415
16
  ```
416
-
417
- ## Migration
418
-
419
- If upgrading from a previous version:
420
-
421
- 1. Update package name to `@meistrari/auth-nuxt`
422
- 2. Change config key from `authSdk` to `telaAuth`
423
- 3. Replace `useMeistrariAuth()` with appropriate composable:
424
- - `useTelaSession()` for authentication
425
- - `useTelaOrganization()` for organization management
426
- - `useTelaApiKey()` for API key management
427
- 4. Update cookie name if using custom value (default is now `tela-jwt`)
428
- 5. Remove `useJwt` and `isDevelopment` options (no longer needed)
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/auth-nuxt",
3
3
  "configKey": "telaAuth",
4
- "version": "2.4.0",
4
+ "version": "4.0.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1,4 +1,3 @@
1
- import type { FullOrganization } from '@meistrari/auth-core';
2
1
  /**
3
2
  * Composable for managing Tela application authentication with OAuth 2.0 PKCE flow
4
3
  *
@@ -41,34 +40,6 @@ export declare function useTelaApplicationAuth(): {
41
40
  switchOrganization: (organizationId: string) => Promise<void>;
42
41
  refreshToken: () => Promise<void>;
43
42
  getToken: () => Promise<string | null | undefined>;
44
- user: import("vue").Ref<{
45
- id: string;
46
- createdAt: Date;
47
- updatedAt: Date;
48
- email: string;
49
- emailVerified: boolean;
50
- name: string;
51
- image?: string | null | undefined;
52
- twoFactorEnabled: boolean | null | undefined;
53
- banned: boolean | null | undefined;
54
- role?: string | null | undefined;
55
- banReason?: string | null | undefined;
56
- banExpires?: Date | null | undefined;
57
- lastActiveAt?: Date | null | undefined;
58
- } | null, {
59
- id: string;
60
- createdAt: Date;
61
- updatedAt: Date;
62
- email: string;
63
- emailVerified: boolean;
64
- name: string;
65
- image?: string | null | undefined;
66
- twoFactorEnabled: boolean | null | undefined;
67
- banned: boolean | null | undefined;
68
- role?: string | null | undefined;
69
- banReason?: string | null | undefined;
70
- banExpires?: Date | null | undefined;
71
- lastActiveAt?: Date | null | undefined;
72
- } | null>;
73
- activeOrganization: import("vue").Ref<FullOrganization | null, FullOrganization | null>;
43
+ user: import("vue").Ref<any, any>;
44
+ activeOrganization: import("vue").Ref<any, any>;
74
45
  };
@@ -33,6 +33,11 @@ export function useTelaApplicationAuth() {
33
33
  throw new AuthorizationFlowError("The login function can only be called on the client side.");
34
34
  }
35
35
  const { state: stateKey, challenge: codeChallenge } = await $fetch("/auth/login", { method: "POST" });
36
+ const returnTo = new URL(window.location.href).searchParams.get("returnTo");
37
+ if (returnTo && returnTo.startsWith("/") && !returnTo.startsWith("//")) {
38
+ const returnUrlCookie = useCookie("tela-return-url", { sameSite: "lax", path: "/" });
39
+ returnUrlCookie.value = returnTo;
40
+ }
36
41
  const url = new URL("/applications/login", appConfig.application?.dashboardUrl);
37
42
  url.searchParams.set("application_id", applicationId);
38
43
  url.searchParams.set("code_challenge", codeChallenge);
@@ -1,161 +1,20 @@
1
- import type { FullOrganization } from '@meistrari/auth-core';
2
1
  /**
3
2
  * Shared state for session management.
4
3
  * This module provides access to session-related state without creating circular dependencies.
5
4
  */
6
5
  export declare function useSessionState(): {
7
- user: import("vue").Ref<{
8
- id: string;
9
- createdAt: Date;
10
- updatedAt: Date;
11
- email: string;
12
- emailVerified: boolean;
13
- name: string;
14
- image?: string | null | undefined;
15
- twoFactorEnabled: boolean | null | undefined;
16
- banned: boolean | null | undefined;
17
- role?: string | null | undefined;
18
- banReason?: string | null | undefined;
19
- banExpires?: Date | null | undefined;
20
- lastActiveAt?: Date | null | undefined;
21
- } | null, {
22
- id: string;
23
- createdAt: Date;
24
- updatedAt: Date;
25
- email: string;
26
- emailVerified: boolean;
27
- name: string;
28
- image?: string | null | undefined;
29
- twoFactorEnabled: boolean | null | undefined;
30
- banned: boolean | null | undefined;
31
- role?: string | null | undefined;
32
- banReason?: string | null | undefined;
33
- banExpires?: Date | null | undefined;
34
- lastActiveAt?: Date | null | undefined;
35
- } | null>;
36
- session: import("vue").Ref<{
37
- user: {
38
- id: string;
39
- createdAt: Date;
40
- updatedAt: Date;
41
- email: string;
42
- emailVerified: boolean;
43
- name: string;
44
- image?: string | null | undefined;
45
- twoFactorEnabled: boolean | null | undefined;
46
- banned: boolean | null | undefined;
47
- role?: string | null | undefined;
48
- banReason?: string | null | undefined;
49
- banExpires?: Date | null | undefined;
50
- lastActiveAt?: Date | null | undefined;
51
- };
52
- session: {
53
- id: string;
54
- createdAt: Date;
55
- updatedAt: Date;
56
- userId: string;
57
- expiresAt: Date;
58
- token: string;
59
- ipAddress?: string | null | undefined;
60
- userAgent?: string | null | undefined;
61
- activeOrganizationId?: string | null | undefined;
62
- activeTeamId?: string | null | undefined;
63
- impersonatedBy?: string | null | undefined;
64
- };
65
- } | null, {
66
- user: {
67
- id: string;
68
- createdAt: Date;
69
- updatedAt: Date;
70
- email: string;
71
- emailVerified: boolean;
72
- name: string;
73
- image?: string | null | undefined;
74
- twoFactorEnabled: boolean | null | undefined;
75
- banned: boolean | null | undefined;
76
- role?: string | null | undefined;
77
- banReason?: string | null | undefined;
78
- banExpires?: Date | null | undefined;
79
- lastActiveAt?: Date | null | undefined;
80
- };
81
- session: {
82
- id: string;
83
- createdAt: Date;
84
- updatedAt: Date;
85
- userId: string;
86
- expiresAt: Date;
87
- token: string;
88
- ipAddress?: string | null | undefined;
89
- userAgent?: string | null | undefined;
90
- activeOrganizationId?: string | null | undefined;
91
- activeTeamId?: string | null | undefined;
92
- impersonatedBy?: string | null | undefined;
93
- };
94
- } | null>;
6
+ user: import("vue").Ref<any, any>;
7
+ session: import("vue").Ref<any, any>;
95
8
  };
96
9
  /**
97
10
  * Shared state for organization management.
98
11
  * This module provides access to organization-related state without creating circular dependencies.
99
12
  */
100
13
  export declare function useOrganizationState(): {
101
- activeOrganization: import("vue").Ref<FullOrganization | null, FullOrganization | null>;
102
- activeMember: import("vue").Ref<{
103
- id: string;
104
- organizationId: string;
105
- role: "org:admin" | "org:member" | "org:reviewer";
106
- createdAt: Date;
107
- userId: string;
108
- teamId?: string | undefined | undefined | undefined;
109
- user: {
110
- id: string;
111
- email: string;
112
- name: string;
113
- image?: string | undefined;
114
- };
115
- } | null, {
116
- id: string;
117
- organizationId: string;
118
- role: "org:admin" | "org:member" | "org:reviewer";
119
- createdAt: Date;
120
- userId: string;
121
- teamId?: string | undefined | undefined | undefined;
122
- user: {
123
- id: string;
124
- email: string;
125
- name: string;
126
- image?: string | undefined;
127
- };
128
- } | null>;
14
+ activeOrganization: import("vue").Ref<any, any>;
15
+ activeMember: import("vue").Ref<any, any>;
129
16
  };
130
17
  export declare function useApplicationSessionState(): {
131
- user: import("vue").Ref<{
132
- id: string;
133
- createdAt: Date;
134
- updatedAt: Date;
135
- email: string;
136
- emailVerified: boolean;
137
- name: string;
138
- image?: string | null | undefined;
139
- twoFactorEnabled: boolean | null | undefined;
140
- banned: boolean | null | undefined;
141
- role?: string | null | undefined;
142
- banReason?: string | null | undefined;
143
- banExpires?: Date | null | undefined;
144
- lastActiveAt?: Date | null | undefined;
145
- } | null, {
146
- id: string;
147
- createdAt: Date;
148
- updatedAt: Date;
149
- email: string;
150
- emailVerified: boolean;
151
- name: string;
152
- image?: string | null | undefined;
153
- twoFactorEnabled: boolean | null | undefined;
154
- banned: boolean | null | undefined;
155
- role?: string | null | undefined;
156
- banReason?: string | null | undefined;
157
- banExpires?: Date | null | undefined;
158
- lastActiveAt?: Date | null | undefined;
159
- } | null>;
160
- activeOrganization: import("vue").Ref<FullOrganization | null, FullOrganization | null>;
18
+ user: import("vue").Ref<any, any>;
19
+ activeOrganization: import("vue").Ref<any, any>;
161
20
  };
@@ -43,13 +43,14 @@ export default defineNuxtPlugin({
43
43
  refreshTokenCookie.value = refreshToken2;
44
44
  state.user.value = user2;
45
45
  state.activeOrganization.value = organization2;
46
- return;
46
+ return true;
47
47
  }
48
48
  const { user, organization } = await $fetch("/auth/refresh", {
49
49
  method: "POST"
50
50
  });
51
51
  state.user.value = user;
52
52
  state.activeOrganization.value = organization;
53
+ return true;
53
54
  } catch {
54
55
  await sdkLogout();
55
56
  if (import.meta.client) {
@@ -69,11 +70,11 @@ export default defineNuxtPlugin({
69
70
  clearTimeout(tokenRefreshInterval);
70
71
  tokenRefreshInterval = null;
71
72
  }
72
- if (!accessTokenCookie.value) {
73
- return;
74
- }
75
- if (isTokenExpired(accessTokenCookie.value, TWO_MINUTES)) {
76
- await refreshToken();
73
+ if (!accessTokenCookie.value || isTokenExpired(accessTokenCookie.value, TWO_MINUTES)) {
74
+ const result = await refreshToken();
75
+ if (!result) {
76
+ return;
77
+ }
77
78
  }
78
79
  const expiry = parseTokenExpiry(accessTokenCookie.value);
79
80
  if (!expiry) {
@@ -5,35 +5,38 @@ export default defineNuxtPlugin(() => {
5
5
  const authConfig = config.public.telaAuth;
6
6
  const loginPath = authConfig.application?.loginPath ?? "/login";
7
7
  const unauthorizedPath = authConfig.application?.unauthorizedPath ?? "/unauthorized";
8
+ const exemptPaths = [loginPath, unauthorizedPath];
8
9
  addRouteMiddleware("auth-guard", async (to) => {
9
10
  const authMeta = to.meta?.auth;
10
- if (!authMeta || authMeta.required !== true) {
11
+ const path = decodeURI(to.path);
12
+ if (authMeta === false || exemptPaths.includes(path)) {
11
13
  return;
12
14
  }
13
15
  const token = useCookie("tela-access-token");
14
16
  if (!token.value) {
15
17
  return await navigateTo({
16
18
  path: loginPath,
17
- query: { redirect: to.fullPath }
19
+ query: { returnTo: to.fullPath }
18
20
  });
19
21
  }
20
- if (authMeta.roles && authMeta.roles.length > 0) {
21
- try {
22
- const payload = decodeJwt(token.value);
23
- const userRole = payload.user?.role;
24
- if (!userRole || !authMeta.roles.includes(userRole)) {
25
- return await navigateTo({
26
- path: unauthorizedPath,
27
- query: { redirect: to.fullPath }
28
- });
29
- }
30
- } catch (error) {
31
- console.error("Failed to decode token:", error);
22
+ if (!authMeta?.roles?.length) {
23
+ return;
24
+ }
25
+ try {
26
+ const payload = decodeJwt(token.value);
27
+ const userRole = payload.user?.role;
28
+ if (!userRole || !authMeta.roles.includes(userRole)) {
32
29
  return await navigateTo({
33
- path: loginPath,
34
- query: { redirect: to.fullPath }
30
+ path: unauthorizedPath,
31
+ query: { returnTo: to.fullPath }
35
32
  });
36
33
  }
34
+ } catch (error) {
35
+ console.error("Failed to decode token:", error);
36
+ return await navigateTo({
37
+ path: loginPath,
38
+ query: { returnTo: to.fullPath }
39
+ });
37
40
  }
38
41
  }, { global: true });
39
42
  });
@@ -40,6 +40,13 @@ export default defineEventHandler(async (event) => {
40
40
  path: "/"
41
41
  });
42
42
  deleteCookie(event, `tela-verifier-${state}`, { path: "/" });
43
+ const returnUrlCookie = getCookie(event, "tela-return-url");
44
+ if (returnUrlCookie) {
45
+ deleteCookie(event, "tela-return-url", { path: "/" });
46
+ if (returnUrlCookie.startsWith("/") && !returnUrlCookie.startsWith("//")) {
47
+ return await sendRedirect(event, returnUrlCookie);
48
+ }
49
+ }
43
50
  return await sendRedirect(event, "/");
44
51
  } catch (error) {
45
52
  console.error("[Auth Callback] OAuth flow error:", error);
@@ -11,6 +11,6 @@
11
11
  */
12
12
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
13
13
  success: boolean;
14
- organizations: import("@meistrari/auth-core").FullOrganization[];
14
+ organizations: any;
15
15
  }>>;
16
16
  export default _default;
@@ -11,21 +11,7 @@
11
11
  */
12
12
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
13
13
  success: boolean;
14
- user: {
15
- id: string;
16
- createdAt: Date;
17
- updatedAt: Date;
18
- email: string;
19
- emailVerified: boolean;
20
- name: string;
21
- image?: string | null | undefined;
22
- twoFactorEnabled: boolean | null | undefined;
23
- banned: boolean | null | undefined;
24
- role?: string | null | undefined;
25
- banReason?: string | null | undefined;
26
- banExpires?: Date | null | undefined;
27
- lastActiveAt?: Date | null | undefined;
28
- };
29
- organization: import("@meistrari/auth-core").FullOrganization;
14
+ user: any;
15
+ organization: any;
30
16
  }>>;
31
17
  export default _default;
@@ -12,21 +12,7 @@
12
12
  */
13
13
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
14
14
  success: boolean;
15
- user: {
16
- id: string;
17
- createdAt: Date;
18
- updatedAt: Date;
19
- email: string;
20
- emailVerified: boolean;
21
- name: string;
22
- image?: string | null | undefined;
23
- twoFactorEnabled: boolean | null | undefined;
24
- banned: boolean | null | undefined;
25
- role?: string | null | undefined;
26
- banReason?: string | null | undefined;
27
- banExpires?: Date | null | undefined;
28
- lastActiveAt?: Date | null | undefined;
29
- };
30
- organization: import("@meistrari/auth-core").FullOrganization;
15
+ user: any;
16
+ organization: any;
31
17
  }>>;
32
18
  export default _default;
@@ -1,20 +1,6 @@
1
1
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
2
  success: boolean;
3
- user: {
4
- id: string;
5
- createdAt: Date;
6
- updatedAt: Date;
7
- email: string;
8
- emailVerified: boolean;
9
- name: string;
10
- image?: string | null | undefined;
11
- twoFactorEnabled: boolean | null | undefined;
12
- banned: boolean | null | undefined;
13
- role?: string | null | undefined;
14
- banReason?: string | null | undefined;
15
- banExpires?: Date | null | undefined;
16
- lastActiveAt?: Date | null | undefined;
17
- };
18
- organization: import("@meistrari/auth-core").FullOrganization;
3
+ user: any;
4
+ organization: any;
19
5
  }>>;
20
6
  export default _default;
@@ -3,10 +3,6 @@ import { createRemoteJWKSet, jwtVerify } from "jose";
3
3
  import { useRuntimeConfig } from "nitropack/runtime";
4
4
  export function requireAuth(handler, options) {
5
5
  return defineEventHandler(async (event) => {
6
- const moduleOptions = useRuntimeConfig(event).public.telaAuth;
7
- if (!moduleOptions.skipServerMiddleware && !options?.roles && import.meta.dev) {
8
- console.warn("You have enabled the global server middleware, meaning you only need to use requireAuth() on routes that require specific roles.", `Triggered at ${event.path}`);
9
- }
10
6
  if (event.context.auth?.user && event.context.auth?.token) {
11
7
  if (import.meta.dev) {
12
8
  console.debug("Using existing auth context from global server middleware");
@@ -1,2 +1 @@
1
- import { AuthClient } from '@meistrari/auth-core';
2
- export declare function createNuxtAuthClient(apiUrl: string, getAuthToken: () => string | null, getRefreshToken?: () => string | null): AuthClient;
1
+ export declare function createNuxtAuthClient(apiUrl: string, getAuthToken: () => string | null, getRefreshToken?: () => string | null): any;
@@ -1,10 +1,7 @@
1
1
  declare type Roles = 'meistrari:admin' | (string & {})
2
2
  declare module '#app' {
3
3
  interface PageMeta {
4
- auth?: {
5
- required: false
6
- } | {
7
- required: true
4
+ auth?: false | {
8
5
  roles?: Roles[]
9
6
  }
10
7
  }
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
1
  {
2
- "name": "@meistrari/auth-nuxt",
3
- "version": "2.4.0",
4
- "type": "module",
5
- "exports": {
6
- ".": {
7
- "types": "./dist/types.d.mts",
8
- "import": "./dist/module.mjs"
2
+ "name": "@meistrari/auth-nuxt",
3
+ "version": "4.0.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/types.d.mts",
8
+ "import": "./dist/module.mjs"
9
+ },
10
+ "./server/middleware/auth": {
11
+ "types": "./dist/runtime/server/middleware/auth.d.ts",
12
+ "import": "./dist/runtime/server/middleware/auth.js"
13
+ },
14
+ "./core": {
15
+ "types": "./dist/core.d.mts",
16
+ "import": "./dist/core.mjs"
17
+ }
9
18
  },
10
- "./server/middleware/auth": {
11
- "types": "./dist/runtime/server/middleware/auth.d.ts",
12
- "import": "./dist/runtime/server/middleware/auth.js"
19
+ "main": "./dist/module.mjs",
20
+ "typesVersions": {
21
+ "*": {
22
+ ".": [
23
+ "./dist/types.d.mts"
24
+ ]
25
+ }
13
26
  },
14
- "./core": {
15
- "types": "./dist/core.d.mts",
16
- "import": "./dist/core.mjs"
17
- }
18
- },
19
- "main": "./dist/module.mjs",
20
- "typesVersions": {
21
- "*": {
22
- ".": [
23
- "./dist/types.d.mts"
24
- ]
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
32
+ },
33
+ "dependencies": {
34
+ "@meistrari/auth-core": "1.11.3",
35
+ "jose": "6.1.3"
36
+ },
37
+ "peerDependencies": {
38
+ "nuxt": "^3.0.0 || ^4.0.0",
39
+ "vue": "^3.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@nuxt/devtools": "2.6.3",
43
+ "@nuxt/eslint-config": "1.9.0",
44
+ "@nuxt/kit": "4.0.3",
45
+ "@nuxt/module-builder": "1.0.2",
46
+ "@nuxt/schema": "4.0.3",
47
+ "@nuxt/test-utils": "3.19.2",
48
+ "@types/node": "latest",
49
+ "changelogen": "0.6.2",
50
+ "nuxt": "4.0.3",
51
+ "typescript": "5.9.2",
52
+ "unbuild": "3.6.1",
53
+ "vitest": "3.2.4",
54
+ "vue-tsc": "3.0.6"
25
55
  }
26
- },
27
- "files": [
28
- "dist"
29
- ],
30
- "scripts": {
31
- "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
32
- },
33
- "dependencies": {
34
- "@meistrari/auth-core": "1.11.2",
35
- "jose": "6.1.3"
36
- },
37
- "peerDependencies": {
38
- "nuxt": "^3.0.0 || ^4.0.0",
39
- "vue": "^3.0.0"
40
- },
41
- "devDependencies": {
42
- "@nuxt/devtools": "2.6.3",
43
- "@nuxt/eslint-config": "1.9.0",
44
- "@nuxt/kit": "4.0.3",
45
- "@nuxt/module-builder": "1.0.2",
46
- "@nuxt/schema": "4.0.3",
47
- "@nuxt/test-utils": "3.19.2",
48
- "@types/node": "latest",
49
- "changelogen": "0.6.2",
50
- "nuxt": "4.0.3",
51
- "typescript": "5.9.2",
52
- "unbuild": "3.6.1",
53
- "vitest": "3.2.4",
54
- "vue-tsc": "3.0.6"
55
- }
56
56
  }