@meistrari/auth-nuxt 2.3.1 → 3.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,421 +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
- ## Setup
12
-
13
- Add the module to your `nuxt.config.ts`:
14
-
15
- ```typescript
16
- export default defineNuxtConfig({
17
- modules: ['@meistrari/auth-nuxt'],
18
- telaAuth: {
19
- apiUrl: 'https://your-auth-api.com',
20
- jwtCookieName: 'tela-jwt', // Optional: custom JWT cookie name
21
- skipServerMiddleware: false, // Optional: skip automatic server middleware
22
- }
23
- })
24
- ```
25
-
26
- ### Configuration Options
27
-
28
- | Option | Type | Default | Description |
29
- |--------|------|---------|-------------|
30
- | `apiUrl` | `string` | **Required** | Base URL of your authentication API |
31
- | `jwtCookieName` | `string` | `'tela-jwt'` | Name of the JWT cookie |
32
- | `skipServerMiddleware` | `boolean` | `false` | Skip automatic server-side auth context setup |
33
-
34
- ## Composables
35
-
36
- The SDK provides three main composables for different aspects of authentication and organization management:
37
-
38
- ### useTelaSession
39
-
40
- Manages user sessions, authentication, and sign-in/sign-out operations.
41
-
42
- ```vue
43
- <script setup>
44
- const { user, session, signOut, getToken } = useTelaSession()
45
-
46
- // Access current user
47
- console.log(user.value) // User object or null
48
-
49
- // Access current session
50
- console.log(session.value) // Session object or null
51
-
52
- // Get a valid JWT token
53
- const token = await getToken()
54
-
55
- // Sign out
56
- await signOut(() => {
57
- console.log('User signed out')
58
- })
59
- </script>
60
-
61
- <template>
62
- <div v-if="user">
63
- Welcome, {{ user.name }}!
64
- <button @click="signOut">
65
- Sign Out
66
- </button>
67
- </div>
68
- <div v-else>
69
- Please sign in
70
- </div>
71
- </template>
72
- ```
73
-
74
- #### Available Methods
75
-
76
- **Session Management:**
77
- - `getSession()` - Retrieves the current user session
78
- - `getToken()` - Retrieves a valid JWT token (refreshes if needed)
79
- - `signOut(callback?)` - Signs out the user
80
-
81
- **Authentication Methods:**
82
- - `signInWithEmailAndPassword(options)` - Email/password authentication
83
- - `signInWithSocialProvider(options)` - Social authentication (Google, Microsoft)
84
- - `signInWithSaml(options)` - SAML-based SSO authentication
85
-
86
- **Password Management:**
87
- - `requestPasswordReset(email, callbackURL)` - Initiates password reset
88
- - `resetPassword(token, password)` - Completes password reset
89
-
90
- #### Sign-In Examples
91
-
92
- **Email and Password:**
93
- ```typescript
94
- await signInWithEmailAndPassword({
95
- email: 'user@example.com',
96
- password: 'secure-password',
97
- })
98
- ```
99
-
100
- **Social Authentication:**
101
- ```typescript
102
- await signInWithSocialProvider({
103
- provider: 'google', // or 'microsoft'
104
- callbackURL: '/',
105
- errorCallbackURL: '/login?error=true'
106
- })
107
- ```
108
-
109
- **SAML SSO:**
110
- ```typescript
111
- await signInWithSaml({
112
- email: 'user@example.com',
113
- callbackURL: '/',
114
- errorCallbackURL: '/login?error=true'
115
- })
116
- ```
117
-
118
- ### useTelaOrganization
119
-
120
- Manages organizations, members, invitations, and teams.
121
-
122
- ```vue
123
- <script setup>
124
- const {
125
- activeOrganization,
126
- activeMember,
127
- getActiveOrganization,
128
- setActiveOrganization,
129
- inviteUserToOrganization
130
- } = useTelaOrganization()
131
-
132
- // Get the active organization
133
- await getActiveOrganization()
134
-
135
- // Switch organizations
136
- await setActiveOrganization('org-id')
137
-
138
- // Invite a user
139
- await inviteUserToOrganization({
140
- userEmail: 'user@example.com',
141
- role: 'member'
142
- })
143
- </script>
144
-
145
- <template>
146
- <div v-if="activeOrganization">
147
- <h2>{{ activeOrganization.name }}</h2>
148
- <p>{{ activeOrganization.members.length }} members</p>
149
- </div>
150
- </template>
151
- ```
152
-
153
- #### Available Methods
154
-
155
- **Organization Management:**
156
- - `getActiveOrganization()` - Gets the current active organization with members, invitations, and teams
157
- - `listOrganizations()` - Lists all organizations for the user
158
- - `setActiveOrganization(id)` - Sets the active organization
159
- - `updateOrganization(payload)` - Updates organization details (name, logo, settings)
160
-
161
- **Member Management:**
162
- - `listMembers(options?)` - Lists organization members with pagination
163
- - `getActiveMember()` - Gets the current user's member record
164
- - `inviteUserToOrganization(options)` - Invites a user to the organization
165
- - `removeUserFromOrganization(options)` - Removes a user from the organization
166
- - `updateMemberRole(options)` - Updates a member's role
167
- - `acceptInvitation(id)` - Accepts an organization invitation
168
- - `cancelInvitation(id)` - Cancels a pending invitation
169
-
170
- **Team Management:**
171
- - `createTeam(payload)` - Creates a new team
172
- - `updateTeam(id, payload)` - Updates team details
173
- - `deleteTeam(id)` - Deletes a team
174
- - `listTeams()` - Lists all teams
175
- - `listTeamMembers(id)` - Lists members of a specific team
176
- - `addTeamMember(teamId, userId)` - Adds a user to a team
177
- - `removeTeamMember(teamId, userId)` - Removes a user from a team
178
-
179
- #### Organization Examples
180
-
181
- **Update Organization:**
182
- ```typescript
183
- await updateOrganization({
184
- name: 'New Organization Name',
185
- logo: 'https://example.com/logo.png',
186
- settings: { /* custom settings */ }
187
- })
188
- ```
189
-
190
- **Invite Member:**
191
- ```typescript
192
- await inviteUserToOrganization({
193
- userEmail: 'user@example.com',
194
- role: 'admin', // or 'member', 'reviewer'
195
- teamId: 'team-id', // optional
196
- resend: false // optional: resend if invitation exists
197
- })
198
- ```
199
-
200
- **Create and Manage Teams:**
201
- ```typescript
202
- // Create team
203
- const team = await createTeam({
204
- name: 'Development Team'
205
- })
206
-
207
- // Add member to team
208
- await addTeamMember(team.id, 'user-id')
5
+ ## Auth Types
209
6
 
210
- // List team members
211
- const members = await listTeamMembers(team.id)
212
- ```
213
-
214
- ### useTelaApiKey
215
-
216
- Manages API keys for programmatic access.
217
-
218
- ```vue
219
- <script setup>
220
- const {
221
- listApiKeys,
222
- createApiKey,
223
- deleteApiKey
224
- } = useTelaApiKey()
225
-
226
- // List all API keys
227
- const apiKeys = await listApiKeys()
228
-
229
- // Create a new API key
230
- const newKey = await createApiKey({
231
- name: 'Production API Key',
232
- expiresIn: '90d',
233
- prefix: 'prod',
234
- metadata: { environment: 'production' }
235
- })
236
-
237
- // Delete an API key
238
- await deleteApiKey('key-id')
239
- </script>
240
-
241
- <template>
242
- <div v-for="key in apiKeys" :key="key.id">
243
- <span>{{ key.name }}</span>
244
- <button @click="deleteApiKey(key.id)">
245
- Delete
246
- </button>
247
- </div>
248
- </template>
249
- ```
250
-
251
- #### Available Methods
252
-
253
- - `listApiKeys()` - Lists all API keys for the current user
254
- - `getApiKey(id)` - Retrieves a specific API key
255
- - `createApiKey(payload)` - Creates a new API key
256
- - `updateApiKey(payload)` - Updates an API key (name)
257
- - `deleteApiKey(id)` - Deletes an API key
258
-
259
- ## Server-Side Usage
260
-
261
- The SDK automatically sets up server-side authentication context for API routes.
262
-
263
- ### Using Authentication Context
264
-
265
- ```typescript
266
- // server/api/protected.ts
267
- export default defineEventHandler(async (event) => {
268
- // Authentication context is automatically available
269
- const { user, workspace } = event.context.auth
270
-
271
- if (!user) {
272
- throw createError({
273
- statusCode: 401,
274
- statusMessage: 'Unauthorized'
275
- })
276
- }
277
-
278
- return {
279
- message: `Hello, ${user.email}!`,
280
- userId: user.id
281
- }
282
- })
283
- ```
284
-
285
- ### Custom Middleware
286
-
287
- Create custom server middleware using the provided helper:
288
-
289
- ```typescript
290
- // server/middleware/custom.ts
291
- import { meistrariAuthMiddleware } from '@meistrari/auth-nuxt/server/middleware/auth'
292
-
293
- export default meistrariAuthMiddleware(async (event) => {
294
- // event.context.auth contains user and workspace
295
- const { user, workspace } = event.context.auth
296
-
297
- // Your custom logic here
298
- if (user) {
299
- console.log(`Authenticated user: ${user.email}`)
300
- }
301
- })
302
- ```
303
-
304
- ## Types
305
-
306
- The SDK exports comprehensive TypeScript types from the core package:
7
+ This SDK supports two authentication models:
307
8
 
308
- ```typescript
309
- import type {
310
- User,
311
- Session,
312
- Organization,
313
- FullOrganization,
314
- Member,
315
- Invitation,
316
- Team,
317
- TeamMember,
318
- ApiKey,
319
- CreateApiKeyPayload,
320
- UpdateApiKeyPayload,
321
- CreateTeamPayload,
322
- UpdateTeamPayload,
323
- InviteUserToOrganizationOptions,
324
- RemoveUserFromOrganizationOptions,
325
- UpdateMemberRoleOptions,
326
- UpdateOrganizationPayload
327
- } from '@meistrari/auth-nuxt/core'
328
- ```
329
-
330
- ### Key Types
331
-
332
- - **User**: User account information including email, name, and verification status
333
- - **Session**: Active session data with expiration and organization info
334
- - **Organization**: Basic organization entity
335
- - **FullOrganization**: Organization with members, invitations, and teams
336
- - **Member**: Organization member with role and team assignments
337
- - **Team**: Team entity within an organization
338
- - **Invitation**: Pending organization invitations
339
- - **ApiKey**: API key entity with metadata
340
-
341
- ## JWT Token Management
342
-
343
- The SDK automatically manages JWT tokens:
344
-
345
- - Stores JWT tokens in cookies (configurable name)
346
- - Refreshes tokens before expiration (every 60 seconds)
347
- - Validates tokens client-side and server-side
348
- - Provides `getToken()` method for accessing valid tokens
349
-
350
- The handshake flow ensures secure authentication:
351
-
352
- 1. On initial visit without a token, redirects to auth API for handshake
353
- 2. Auth API validates the session and redirects back with a nonce
354
- 3. SDK exchanges the nonce for a JWT token
355
- 4. Token is stored in a cookie and used for subsequent requests
356
-
357
- ## Utilities
358
-
359
- ### Token Validation
360
-
361
- ```typescript
362
- import { isTokenExpired, validateToken, extractTokenPayload } from '@meistrari/auth-nuxt/core'
363
-
364
- // Check if token is expired
365
- if (isTokenExpired(jwtToken)) {
366
- // Token needs refresh
367
- }
368
-
369
- // Validate token against JWKS endpoint
370
- const isValid = await validateToken(jwtToken, 'https://your-api.com')
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).
371
11
 
372
- // Extract token payload (without validation)
373
- const payload = extractTokenPayload(jwtToken)
374
- console.log(payload.user, payload.workspace)
375
- ```
376
-
377
- ## Security Features
378
-
379
- - **JWT Validation**: Cryptographic validation using JWKS
380
- - **Secure Cookies**: HTTP-only, secure cookies in production
381
- - **Token Refresh**: Automatic token rotation every 60 seconds
382
- - **Session Management**: Secure session handling with handshake flow
383
- - **Server-Side Validation**: Middleware validates tokens on every API request
384
-
385
- ## Error Handling
386
-
387
- The SDK handles common authentication errors automatically:
388
-
389
- - Expired tokens trigger sign-out when refresh fails
390
- - Invalid tokens result in `null` user/session values
391
- - Network errors are handled gracefully
392
- - API errors can be caught and handled in your code
393
-
394
- ```typescript
395
- import { APIError } from '@meistrari/auth-nuxt/core'
12
+ ## Installation
396
13
 
397
- try {
398
- await signInWithEmailAndPassword({
399
- email: 'user@example.com',
400
- password: 'wrong-password',
401
- })
402
- }
403
- catch (error) {
404
- if (error instanceof APIError) {
405
- console.error('Authentication failed:', error.message, error.status)
406
- }
407
- }
14
+ ```bash
15
+ npm install @meistrari/auth-nuxt
408
16
  ```
409
-
410
- ## Migration
411
-
412
- If upgrading from a previous version:
413
-
414
- 1. Update package name to `@meistrari/auth-nuxt`
415
- 2. Change config key from `authSdk` to `telaAuth`
416
- 3. Replace `useMeistrariAuth()` with appropriate composable:
417
- - `useTelaSession()` for authentication
418
- - `useTelaOrganization()` for organization management
419
- - `useTelaApiKey()` for API key management
420
- 4. Update cookie name if using custom value (default is now `tela-jwt`)
421
- 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.3.1",
4
+ "version": "2.1.3",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -40,6 +40,7 @@ export declare function useTelaApplicationAuth(): {
40
40
  getAvailableOrganizations: () => Promise<FullOrganization[]>;
41
41
  switchOrganization: (organizationId: string) => Promise<void>;
42
42
  refreshToken: () => Promise<void>;
43
+ getToken: () => Promise<string | null | undefined>;
43
44
  user: import("vue").Ref<{
44
45
  id: string;
45
46
  createdAt: Date;
@@ -1,6 +1,7 @@
1
1
  import { navigateTo, useCookie, useRuntimeConfig } from "#app";
2
2
  import { AuthorizationFlowError, isTokenExpired, RefreshTokenExpiredError, UserNotLoggedInError } from "@meistrari/auth-core";
3
3
  import { useApplicationSessionState } from "./state.js";
4
+ import { willTokenExpireIn } from "../helpers/token.js";
4
5
  const FIFTEEN_MINUTES = 60 * 15;
5
6
  const ONE_MINUTE = 60 * 1e3;
6
7
  export function useTelaApplicationAuth() {
@@ -87,6 +88,13 @@ export function useTelaApplicationAuth() {
87
88
  throw error;
88
89
  }
89
90
  }
91
+ async function getToken() {
92
+ const shouldRefresh = accessTokenCookie.value ? willTokenExpireIn(accessTokenCookie.value, ONE_MINUTE * 2) : true;
93
+ if (shouldRefresh) {
94
+ await refreshToken();
95
+ }
96
+ return accessTokenCookie.value;
97
+ }
90
98
  return {
91
99
  ...state,
92
100
  login,
@@ -94,6 +102,7 @@ export function useTelaApplicationAuth() {
94
102
  initSession,
95
103
  getAvailableOrganizations,
96
104
  switchOrganization,
97
- refreshToken
105
+ refreshToken,
106
+ getToken
98
107
  };
99
108
  }
@@ -100,13 +100,12 @@ export declare function useSessionState(): {
100
100
  export declare function useOrganizationState(): {
101
101
  activeOrganization: import("vue").Ref<FullOrganization | null, FullOrganization | null>;
102
102
  activeMember: import("vue").Ref<{
103
- [x: string]: string | number | boolean | string[] | Record<string, any> | (string & Record<never, never>) | Date | number[] | undefined;
104
103
  id: string;
105
104
  organizationId: string;
106
105
  role: "org:admin" | "org:member" | "org:reviewer";
107
106
  createdAt: Date;
108
107
  userId: string;
109
- teamId?: string | undefined | undefined;
108
+ teamId?: string | undefined | undefined | undefined;
110
109
  user: {
111
110
  id: string;
112
111
  email: string;
@@ -114,13 +113,12 @@ export declare function useOrganizationState(): {
114
113
  image?: string | undefined;
115
114
  };
116
115
  } | null, {
117
- [x: string]: string | number | boolean | string[] | Record<string, any> | (string & Record<never, never>) | Date | number[] | undefined;
118
116
  id: string;
119
117
  organizationId: string;
120
118
  role: "org:admin" | "org:member" | "org:reviewer";
121
119
  createdAt: Date;
122
120
  userId: string;
123
- teamId?: string | undefined | undefined;
121
+ teamId?: string | undefined | undefined | undefined;
124
122
  user: {
125
123
  id: string;
126
124
  email: string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Parses a JWT token to extract its expiration time
3
+ *
4
+ * @param token - The JWT token string
5
+ * @returns The expiration timestamp in milliseconds, or null if parsing fails
6
+ */
7
+ export declare function parseTokenExpiry(token: string): number | null;
8
+ /**
9
+ * Checks if a JWT token will expire within a certain time window, or if it is already expired
10
+ *
11
+ * @param token - The JWT token string
12
+ * @param timeWindow - The time window in milliseconds
13
+ * @returns True if the token will expire within the time window, false otherwise
14
+ */
15
+ export declare function willTokenExpireIn(token: string, timeWindow: number): boolean | 0;
@@ -0,0 +1,19 @@
1
+ import { decodeJwt } from "jose";
2
+ export function parseTokenExpiry(token) {
3
+ try {
4
+ const payload = decodeJwt(token);
5
+ if (!payload.exp)
6
+ return null;
7
+ return payload.exp * 1e3;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ export function willTokenExpireIn(token, timeWindow) {
13
+ const now = Date.now();
14
+ const expiry = parseTokenExpiry(token);
15
+ if (expiry === null) {
16
+ return true;
17
+ }
18
+ return expiry && expiry - timeWindow <= now;
19
+ }
@@ -1,22 +1,12 @@
1
1
  import { defineNuxtPlugin, useCookie, useRuntimeConfig } from "#app";
2
2
  import { isTokenExpired } from "@meistrari/auth-core";
3
- import { decodeJwt } from "jose";
4
3
  import { useTelaApplicationAuth } from "../composables/application-auth.js";
5
4
  import { useApplicationSessionState } from "../composables/state.js";
6
5
  import { createNuxtAuthClient } from "../shared.js";
6
+ import { parseTokenExpiry } from "../helpers/token.js";
7
7
  const SEVEN_DAYS = 60 * 60 * 24 * 7;
8
8
  const FIFTEEN_MINUTES = 60 * 15;
9
9
  const TWO_MINUTES = 2 * 60 * 1e3;
10
- function parseTokenExpiry(token) {
11
- try {
12
- const payload = decodeJwt(token);
13
- if (!payload.exp)
14
- return null;
15
- return payload.exp * 1e3;
16
- } catch {
17
- return null;
18
- }
19
- }
20
10
  export default defineNuxtPlugin({
21
11
  name: "tela-application-token-refresh",
22
12
  enforce: "post",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/auth-nuxt",
3
- "version": "2.3.1",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -27,12 +27,9 @@
27
27
  "files": [
28
28
  "dist"
29
29
  ],
30
- "scripts": {
31
- "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
32
- },
33
30
  "dependencies": {
34
- "@meistrari/auth-core": "1.11.1",
35
- "jose": "6.1.3"
31
+ "jose": "6.1.3",
32
+ "@meistrari/auth-core": "1.11.3"
36
33
  },
37
34
  "peerDependencies": {
38
35
  "nuxt": "^3.0.0 || ^4.0.0",
@@ -52,5 +49,8 @@
52
49
  "unbuild": "3.6.1",
53
50
  "vitest": "3.2.4",
54
51
  "vue-tsc": "3.0.6"
52
+ },
53
+ "scripts": {
54
+ "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
55
55
  }
56
- }
56
+ }