@meistrari/auth-nuxt 2.1.3 → 2.2.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.
Files changed (32) hide show
  1. package/README.md +34 -30
  2. package/dist/module.d.mts +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +34 -11
  5. package/dist/runtime/components/tela-role.vue +6 -6
  6. package/dist/runtime/composables/application-auth.d.ts +11 -1
  7. package/dist/runtime/composables/application-auth.js +37 -112
  8. package/dist/runtime/composables/organization.d.ts +1 -1
  9. package/dist/runtime/composables/organization.js +2 -1
  10. package/dist/runtime/composables/state.d.ts +56 -22
  11. package/dist/runtime/plugins/application-token-refresh.js +19 -22
  12. package/dist/runtime/plugins/auth-guard.js +4 -4
  13. package/dist/runtime/plugins/handshake.js +4 -4
  14. package/dist/runtime/server/middleware/auth.js +2 -2
  15. package/dist/runtime/server/routes/auth/callback.d.ts +12 -0
  16. package/dist/runtime/server/routes/auth/callback.js +49 -0
  17. package/dist/runtime/server/routes/auth/login.d.ts +18 -0
  18. package/dist/runtime/server/routes/auth/login.js +41 -0
  19. package/dist/runtime/server/routes/auth/logout.d.ts +4 -0
  20. package/dist/runtime/server/routes/auth/logout.js +26 -0
  21. package/dist/runtime/server/routes/auth/organizations.d.ts +16 -0
  22. package/dist/runtime/server/routes/auth/organizations.js +40 -0
  23. package/dist/runtime/server/routes/auth/refresh.d.ts +39 -0
  24. package/dist/runtime/server/routes/auth/refresh.js +54 -0
  25. package/dist/runtime/server/routes/auth/switch-organization.d.ts +40 -0
  26. package/dist/runtime/server/routes/auth/switch-organization.js +59 -0
  27. package/dist/runtime/server/utils/require-auth.js +2 -2
  28. package/dist/runtime/types/page-meta.d.ts +7 -8
  29. package/package.json +51 -51
  30. package/dist/runtime/pages/callback.d.vue.ts +0 -2
  31. package/dist/runtime/pages/callback.vue +0 -35
  32. package/dist/runtime/pages/callback.vue.d.ts +0 -2
package/README.md CHANGED
@@ -54,18 +54,20 @@ const token = await getToken()
54
54
 
55
55
  // Sign out
56
56
  await signOut(() => {
57
- console.log('User signed out')
57
+ console.log('User signed out')
58
58
  })
59
59
  </script>
60
60
 
61
61
  <template>
62
- <div v-if="user">
63
- Welcome, {{ user.name }}!
64
- <button @click="signOut">Sign Out</button>
65
- </div>
66
- <div v-else>
67
- Please sign in
68
- </div>
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>
69
71
  </template>
70
72
  ```
71
73
 
@@ -120,11 +122,11 @@ Manages organizations, members, invitations, and teams.
120
122
  ```vue
121
123
  <script setup>
122
124
  const {
123
- activeOrganization,
124
- activeMember,
125
- getActiveOrganization,
126
- setActiveOrganization,
127
- inviteUserToOrganization
125
+ activeOrganization,
126
+ activeMember,
127
+ getActiveOrganization,
128
+ setActiveOrganization,
129
+ inviteUserToOrganization
128
130
  } = useTelaOrganization()
129
131
 
130
132
  // Get the active organization
@@ -135,16 +137,16 @@ await setActiveOrganization('org-id')
135
137
 
136
138
  // Invite a user
137
139
  await inviteUserToOrganization({
138
- userEmail: 'user@example.com',
139
- role: 'member'
140
+ userEmail: 'user@example.com',
141
+ role: 'member'
140
142
  })
141
143
  </script>
142
144
 
143
145
  <template>
144
- <div v-if="activeOrganization">
145
- <h2>{{ activeOrganization.name }}</h2>
146
- <p>{{ activeOrganization.members.length }} members</p>
147
- </div>
146
+ <div v-if="activeOrganization">
147
+ <h2>{{ activeOrganization.name }}</h2>
148
+ <p>{{ activeOrganization.members.length }} members</p>
149
+ </div>
148
150
  </template>
149
151
  ```
150
152
 
@@ -216,9 +218,9 @@ Manages API keys for programmatic access.
216
218
  ```vue
217
219
  <script setup>
218
220
  const {
219
- listApiKeys,
220
- createApiKey,
221
- deleteApiKey
221
+ listApiKeys,
222
+ createApiKey,
223
+ deleteApiKey
222
224
  } = useTelaApiKey()
223
225
 
224
226
  // List all API keys
@@ -226,10 +228,10 @@ const apiKeys = await listApiKeys()
226
228
 
227
229
  // Create a new API key
228
230
  const newKey = await createApiKey({
229
- name: 'Production API Key',
230
- expiresIn: '90d',
231
- prefix: 'prod',
232
- metadata: { environment: 'production' }
231
+ name: 'Production API Key',
232
+ expiresIn: '90d',
233
+ prefix: 'prod',
234
+ metadata: { environment: 'production' }
233
235
  })
234
236
 
235
237
  // Delete an API key
@@ -237,10 +239,12 @@ await deleteApiKey('key-id')
237
239
  </script>
238
240
 
239
241
  <template>
240
- <div v-for="key in apiKeys" :key="key.id">
241
- <span>{{ key.name }}</span>
242
- <button @click="deleteApiKey(key.id)">Delete</button>
243
- </div>
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>
244
248
  </template>
245
249
  ```
246
250
 
package/dist/module.d.mts CHANGED
@@ -14,7 +14,7 @@ interface ModuleOptions {
14
14
  dashboardUrl: string;
15
15
  /** The ID of the application to authenticate with */
16
16
  applicationId: string;
17
- /** The redirect URI to redirect to after authentication. Must be registered in the application's settings. */
17
+ /** The authentication callback URL. Usually `{your-app-url}/auth/callback` */
18
18
  redirectUri: string;
19
19
  /** Path to redirect to when authentication is required (default: '/login') */
20
20
  loginPath?: string;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/auth-nuxt",
3
3
  "configKey": "telaAuth",
4
- "version": "2.1.3",
4
+ "version": "2.2.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addImports, addPlugin, extendPages, addComponent, addServerImportsDir, addServerHandler } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addImports, addComponent, addServerImportsDir, addServerHandler, addPlugin } from '@nuxt/kit';
2
2
 
3
3
  const module$1 = defineNuxtModule({
4
4
  meta: {
@@ -18,14 +18,6 @@ const module$1 = defineNuxtModule({
18
18
  as: "useTelaApplicationAuth",
19
19
  from: resolver.resolve("runtime/composables/application-auth")
20
20
  });
21
- addPlugin(resolver.resolve("./runtime/plugins/application-token-refresh"));
22
- extendPages((pages) => {
23
- pages.unshift({
24
- name: "auth-callback",
25
- path: "/auth/callback",
26
- file: resolver.resolve("./runtime/pages/callback.vue")
27
- });
28
- });
29
21
  addComponent({
30
22
  name: "TelaRole",
31
23
  filePath: resolver.resolve("./runtime/components/tela-role.vue"),
@@ -37,8 +29,6 @@ const module$1 = defineNuxtModule({
37
29
  path: resolver.resolve("./runtime/types/page-meta.d.ts")
38
30
  });
39
31
  });
40
- addPlugin(resolver.resolve("./runtime/plugins/auth-guard"));
41
- addPlugin(resolver.resolve("./runtime/plugins/directives"));
42
32
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
43
33
  if (!options.skipServerMiddleware) {
44
34
  addServerHandler({
@@ -46,6 +36,39 @@ const module$1 = defineNuxtModule({
46
36
  handler: resolver.resolve("./runtime/server/middleware/application-auth")
47
37
  });
48
38
  }
39
+ addServerHandler({
40
+ route: "/auth/callback",
41
+ handler: resolver.resolve("./runtime/server/routes/auth/callback"),
42
+ method: "get"
43
+ });
44
+ addServerHandler({
45
+ route: "/auth/login",
46
+ handler: resolver.resolve("./runtime/server/routes/auth/login"),
47
+ method: "post"
48
+ });
49
+ addServerHandler({
50
+ route: "/auth/refresh",
51
+ handler: resolver.resolve("./runtime/server/routes/auth/refresh"),
52
+ method: "post"
53
+ });
54
+ addServerHandler({
55
+ route: "/auth/logout",
56
+ handler: resolver.resolve("./runtime/server/routes/auth/logout"),
57
+ method: "post"
58
+ });
59
+ addServerHandler({
60
+ route: "/auth/organizations",
61
+ handler: resolver.resolve("./runtime/server/routes/auth/organizations"),
62
+ method: "get"
63
+ });
64
+ addServerHandler({
65
+ route: "/auth/switch-organization",
66
+ handler: resolver.resolve("./runtime/server/routes/auth/switch-organization"),
67
+ method: "post"
68
+ });
69
+ addPlugin(resolver.resolve("./runtime/plugins/application-token-refresh"));
70
+ addPlugin(resolver.resolve("./runtime/plugins/auth-guard"));
71
+ addPlugin(resolver.resolve("./runtime/plugins/directives"));
49
72
  return;
50
73
  }
51
74
  if (!options.skipServerMiddleware) {
@@ -1,15 +1,15 @@
1
1
  <script setup>
2
2
  import { useTelaApplicationAuth } from "../composables/application-auth";
3
- const { user } = useTelaApplicationAuth();
4
3
  defineProps({
5
4
  allowedRoles: { type: Array, required: true }
6
5
  });
6
+ const { user } = useTelaApplicationAuth();
7
7
  </script>
8
8
 
9
9
  <template>
10
- <template v-if="user">
11
- <slot v-if="user.role && allowedRoles.includes(user.role)" :role="user.role" />
12
- <slot v-else name="not-authorized" :role="user.role" />
13
- </template>
14
- <slot v-else name="logged-out" />
10
+ <template v-if="user">
11
+ <slot v-if="user.role && allowedRoles.includes(user.role)" :role="user.role" />
12
+ <slot v-else name="not-authorized" :role="user.role" />
13
+ </template>
14
+ <slot v-else name="logged-out" />
15
15
  </template>
@@ -1,3 +1,11 @@
1
+ type RawOrganization = {
2
+ title: string;
3
+ id: string;
4
+ createdAt: Date;
5
+ avatarUrl: string | null;
6
+ metadata: string | null;
7
+ slug: string | null;
8
+ };
1
9
  /**
2
10
  * Composable for managing Tela application authentication with OAuth 2.0 PKCE flow
3
11
  *
@@ -36,8 +44,9 @@ export declare function useTelaApplicationAuth(): {
36
44
  login: () => Promise<void>;
37
45
  logout: () => Promise<void>;
38
46
  initSession: () => Promise<void>;
39
- getAvailableOrganizations: () => Promise<import("@meistrari/auth-core").Organization[]>;
47
+ getAvailableOrganizations: () => Promise<RawOrganization[]>;
40
48
  switchOrganization: (organizationId: string) => Promise<void>;
49
+ refreshToken: () => Promise<void>;
41
50
  user: import("vue").Ref<{
42
51
  id: string;
43
52
  createdAt: Date;
@@ -69,3 +78,4 @@ export declare function useTelaApplicationAuth(): {
69
78
  } | null>;
70
79
  activeOrganization: import("vue").Ref<Omit<import("@meistrari/auth-core").FullOrganization, "members" | "invitations" | "teams"> | null, Omit<import("@meistrari/auth-core").FullOrganization, "members" | "invitations" | "teams"> | null>;
71
80
  };
81
+ export {};
@@ -1,38 +1,8 @@
1
- import { navigateTo, useCookie, useRoute, useRuntimeConfig } from "#app";
2
- import { AuthorizationFlowError, isTokenExpired, RefreshTokenExpiredError } from "@meistrari/auth-core";
3
- import { createNuxtAuthClient } from "../shared.js";
1
+ import { navigateTo, useCookie, useRuntimeConfig } from "#app";
2
+ import { AuthorizationFlowError, isTokenExpired, RefreshTokenExpiredError, UserNotLoggedInError } from "@meistrari/auth-core";
4
3
  import { useApplicationSessionState } from "./state.js";
5
- const SEVEN_DAYS = 60 * 60 * 24 * 7;
6
4
  const FIFTEEN_MINUTES = 60 * 15;
7
5
  const ONE_MINUTE = 60 * 1e3;
8
- function verifier(stateKey) {
9
- return `code_verifier_${stateKey}`;
10
- }
11
- function hexEncode(bytes) {
12
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
13
- }
14
- function generateStateKey(query) {
15
- if (query.state && typeof query.state === "string") {
16
- return query.state;
17
- }
18
- const array = new Uint8Array(8);
19
- crypto.getRandomValues(array);
20
- return hexEncode(array);
21
- }
22
- function generateCodeVerifier() {
23
- const array = new Uint8Array(32);
24
- crypto.getRandomValues(array);
25
- return base64UrlEncode(array);
26
- }
27
- async function generateCodeChallenge(verifier2) {
28
- const encoder = new TextEncoder();
29
- const data = encoder.encode(verifier2);
30
- const digest = await crypto.subtle.digest("SHA-256", data);
31
- return base64UrlEncode(new Uint8Array(digest));
32
- }
33
- function base64UrlEncode(bytes) {
34
- return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
35
- }
36
6
  function mapOrganization(organization) {
37
7
  return {
38
8
  name: organization.title,
@@ -45,18 +15,11 @@ function mapOrganization(organization) {
45
15
  }
46
16
  export function useTelaApplicationAuth() {
47
17
  const appConfig = useRuntimeConfig().public.telaAuth;
48
- const query = useRoute().query;
49
18
  const accessTokenCookie = useCookie("tela-access-token", {
50
- secure: true,
19
+ secure: !import.meta.dev,
51
20
  sameSite: "lax",
52
21
  maxAge: FIFTEEN_MINUTES
53
22
  });
54
- const refreshTokenCookie = useCookie("tela-refresh-token", {
55
- secure: true,
56
- sameSite: "lax",
57
- maxAge: SEVEN_DAYS
58
- });
59
- const authClient = createNuxtAuthClient(appConfig.apiUrl, () => null, () => refreshTokenCookie.value ?? null);
60
23
  const state = useApplicationSessionState();
61
24
  if (!appConfig.application?.dashboardUrl) {
62
25
  throw new Error(
@@ -78,100 +41,61 @@ export function useTelaApplicationAuth() {
78
41
  if (import.meta.server) {
79
42
  throw new AuthorizationFlowError("The login function can only be called on the client side.");
80
43
  }
81
- if (typeof localStorage === "undefined") {
82
- throw new AuthorizationFlowError("localStorage is not available. The login function must be called on the client side.");
83
- }
84
- const codeVerifier = generateCodeVerifier();
85
- const codeChallenge = await generateCodeChallenge(codeVerifier);
86
- const stateKey = generateStateKey(query);
87
- localStorage.setItem(verifier(stateKey), codeVerifier);
44
+ const { state: stateKey, challenge: codeChallenge } = await $fetch("/auth/login", { method: "POST" });
88
45
  const url = new URL("/applications/login", appConfig.application?.dashboardUrl);
89
46
  url.searchParams.set("application_id", applicationId);
90
47
  url.searchParams.set("code_challenge", codeChallenge);
91
48
  url.searchParams.set("redirect_uri", redirectUri);
92
49
  url.searchParams.set("state", stateKey);
93
- navigateTo(url.toString(), { external: true });
50
+ await navigateTo(url.toString(), { external: true });
94
51
  }
95
52
  async function logout() {
96
- accessTokenCookie.value = null;
97
- refreshTokenCookie.value = null;
98
53
  state.user.value = null;
99
54
  state.activeOrganization.value = null;
100
- if (typeof localStorage !== "undefined") {
101
- for (const key in localStorage) {
102
- if (key.startsWith("code_verifier_")) {
103
- localStorage.removeItem(key);
104
- }
105
- }
106
- }
107
- }
108
- async function exchangeCodeForToken() {
109
- if (import.meta.server) {
110
- throw new AuthorizationFlowError("The exchangeCodeForToken function can only be called on the client side.");
111
- }
112
- const code = query.code;
113
- const stateKey = query.state;
114
- if (!code) {
115
- throw new AuthorizationFlowError("Authorization code not found in query parameters");
116
- }
117
- if (!stateKey) {
118
- throw new AuthorizationFlowError("State parameter not found in query parameters");
119
- }
120
- if (typeof localStorage === "undefined") {
121
- throw new AuthorizationFlowError("localStorage is not available");
122
- }
123
- const codeVerifierKey = verifier(stateKey);
124
- const codeVerifier = localStorage.getItem(codeVerifierKey);
125
- if (!codeVerifier) {
126
- throw new AuthorizationFlowError("Code verifier not found. This may indicate a CSRF attack or expired session.");
127
- }
128
- try {
129
- const { accessToken, refreshToken: refreshToken2, user, organization } = await authClient.application.completeAuthorizationFlow(code, codeVerifier);
130
- accessTokenCookie.value = accessToken;
131
- refreshTokenCookie.value = refreshToken2;
132
- state.user.value = user;
133
- state.activeOrganization.value = mapOrganization(organization);
134
- } finally {
135
- localStorage.removeItem(codeVerifierKey);
136
- }
55
+ await $fetch("/auth/logout", { method: "POST" });
137
56
  }
138
57
  async function refreshToken() {
139
- if (!refreshTokenCookie.value) {
58
+ try {
59
+ const result = await $fetch("/auth/refresh", {
60
+ method: "POST"
61
+ });
62
+ state.user.value = result.user;
63
+ state.activeOrganization.value = mapOrganization(result.organization);
64
+ } catch (error) {
65
+ console.error("[Auth Refresh] Failed to refresh token:", error);
140
66
  throw new RefreshTokenExpiredError();
141
67
  }
142
- const { accessToken, refreshToken: refreshToken2, user, organization } = await authClient.application.refreshAccessToken(refreshTokenCookie.value);
143
- accessTokenCookie.value = accessToken;
144
- refreshTokenCookie.value = refreshToken2;
145
- state.user.value = user;
146
- state.activeOrganization.value = mapOrganization(organization);
147
68
  }
148
69
  async function initSession() {
149
- const code = query.code;
150
- if (code) {
151
- await exchangeCodeForToken();
152
- }
153
- if (!accessTokenCookie.value && !refreshTokenCookie.value) {
154
- throw new RefreshTokenExpiredError();
70
+ if (!accessTokenCookie.value) {
71
+ throw new UserNotLoggedInError("No access token found in cookies");
155
72
  }
156
73
  const isExpiredOrClose = accessTokenCookie.value ? isTokenExpired(accessTokenCookie.value, ONE_MINUTE) : true;
157
- if (isExpiredOrClose && !refreshTokenCookie.value) {
158
- await logout();
159
- throw new RefreshTokenExpiredError();
160
- }
161
- if (isExpiredOrClose && refreshTokenCookie.value) {
74
+ if (isExpiredOrClose) {
162
75
  await refreshToken();
163
76
  }
164
77
  }
165
78
  async function getAvailableOrganizations() {
166
- const { organizations } = await authClient.application.listCandidateOrganizations(applicationId);
167
- return organizations;
79
+ try {
80
+ const result = await $fetch("/auth/organizations", { method: "GET" });
81
+ return result.organizations;
82
+ } catch (error) {
83
+ console.error("[Auth Orgs] Failed to list organizations:", error);
84
+ throw error;
85
+ }
168
86
  }
169
87
  async function switchOrganization(organizationId) {
170
- const { accessToken, refreshToken: refreshToken2, user, organization } = await authClient.application.switchOrganization(organizationId, accessTokenCookie.value);
171
- accessTokenCookie.value = accessToken;
172
- refreshTokenCookie.value = refreshToken2;
173
- state.user.value = user;
174
- state.activeOrganization.value = mapOrganization(organization);
88
+ try {
89
+ const result = await $fetch("/auth/switch-organization", {
90
+ method: "POST",
91
+ body: { organizationId }
92
+ });
93
+ state.user.value = result.user;
94
+ state.activeOrganization.value = mapOrganization(result.organization);
95
+ } catch (error) {
96
+ console.error("[Auth Switch Org] Failed to switch organization:", error);
97
+ throw error;
98
+ }
175
99
  }
176
100
  return {
177
101
  ...state,
@@ -179,6 +103,7 @@ export function useTelaApplicationAuth() {
179
103
  logout,
180
104
  initSession,
181
105
  getAvailableOrganizations,
182
- switchOrganization
106
+ switchOrganization,
107
+ refreshToken
183
108
  };
184
109
  }
@@ -1,5 +1,5 @@
1
- import type { Ref } from 'vue';
2
1
  import type { CreateTeamPayload, FullOrganization, Invitation, InviteUserToOrganizationOptions, ListMembersOptions, Member, RemoveUserFromOrganizationOptions, Team, TeamMember, UpdateMemberRoleOptions, UpdateOrganizationPayload, UpdateTeamPayload } from '@meistrari/auth-core';
2
+ import type { Ref } from 'vue';
3
3
  export interface UseTelaOrganizationReturn {
4
4
  /** Reactive reference to the active organization with members, invitations, and teams. */
5
5
  activeOrganization: Ref<FullOrganization | null>;
@@ -10,7 +10,8 @@ export function useTelaOrganization() {
10
10
  if (!session.value?.activeOrganizationId) {
11
11
  return;
12
12
  }
13
- const organization = await authClient.organization.getOrganization(session.value?.activeOrganizationId);
13
+ const activeOrganizationId = session.value?.activeOrganizationId;
14
+ const organization = await authClient.organization.getOrganization(activeOrganizationId);
14
15
  activeOrganization.value = organization;
15
16
  return organization;
16
17
  }
@@ -34,29 +34,63 @@ export declare function useSessionState(): {
34
34
  lastActiveAt?: Date | null | undefined;
35
35
  } | null>;
36
36
  session: import("vue").Ref<{
37
- id: string;
38
- createdAt: Date;
39
- updatedAt: Date;
40
- userId: string;
41
- expiresAt: Date;
42
- token: string;
43
- ipAddress?: string | null | undefined;
44
- userAgent?: string | null | undefined;
45
- activeOrganizationId?: string | null | undefined;
46
- activeTeamId?: string | null | undefined;
47
- impersonatedBy?: string | null | undefined;
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
+ };
48
65
  } | null, {
49
- id: string;
50
- createdAt: Date;
51
- updatedAt: Date;
52
- userId: string;
53
- expiresAt: Date;
54
- token: string;
55
- ipAddress?: string | null | undefined;
56
- userAgent?: string | null | undefined;
57
- activeOrganizationId?: string | null | undefined;
58
- activeTeamId?: string | null | undefined;
59
- impersonatedBy?: string | null | undefined;
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
+ };
60
94
  } | null>;
61
95
  };
62
96
  /**
@@ -1,19 +1,15 @@
1
1
  import { defineNuxtPlugin, useCookie, useRuntimeConfig } from "#app";
2
- import { isTokenExpired, RefreshTokenExpiredError } from "@meistrari/auth-core";
3
- import { watch } from "vue";
2
+ import { isTokenExpired } from "@meistrari/auth-core";
3
+ import { decodeJwt } from "jose";
4
+ import { useTelaApplicationAuth } from "../composables/application-auth.js";
4
5
  import { useApplicationSessionState } from "../composables/state.js";
5
6
  import { createNuxtAuthClient } from "../shared.js";
6
- import { useTelaApplicationAuth } from "../composables/application-auth.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
10
  function parseTokenExpiry(token) {
11
11
  try {
12
- const tokenParts = token.split(".");
13
- const payloadPart = tokenParts[1];
14
- if (!payloadPart)
15
- return null;
16
- const payload = JSON.parse(atob(payloadPart));
12
+ const payload = decodeJwt(token);
17
13
  if (!payload.exp)
18
14
  return null;
19
15
  return payload.exp * 1e3;
@@ -42,12 +38,13 @@ export default defineNuxtPlugin({
42
38
  const state = useApplicationSessionState();
43
39
  const { login, logout: sdkLogout } = useTelaApplicationAuth();
44
40
  const accessTokenCookie = useCookie("tela-access-token", {
45
- secure: true,
41
+ secure: !import.meta.dev,
46
42
  sameSite: "lax",
47
43
  maxAge: FIFTEEN_MINUTES
48
44
  });
49
45
  const refreshTokenCookie = useCookie("tela-refresh-token", {
50
- secure: true,
46
+ httpOnly: true,
47
+ secure: !import.meta.dev,
51
48
  sameSite: "lax",
52
49
  maxAge: SEVEN_DAYS
53
50
  });
@@ -60,12 +57,17 @@ export default defineNuxtPlugin({
60
57
  }
61
58
  isRefreshing = true;
62
59
  try {
63
- if (!refreshTokenCookie.value) {
64
- throw new RefreshTokenExpiredError();
60
+ if (import.meta.server) {
61
+ const { accessToken, refreshToken: refreshToken2, user: user2, organization: organization2 } = await authClient.application.refreshAccessToken(refreshTokenCookie.value ?? "");
62
+ accessTokenCookie.value = accessToken;
63
+ refreshTokenCookie.value = refreshToken2;
64
+ state.user.value = user2;
65
+ state.activeOrganization.value = mapOrganization(organization2);
66
+ return;
65
67
  }
66
- const { accessToken, refreshToken: refreshToken2, user, organization } = await authClient.application.refreshAccessToken(refreshTokenCookie.value);
67
- accessTokenCookie.value = accessToken;
68
- refreshTokenCookie.value = refreshToken2;
68
+ const { user, organization } = await $fetch("/auth/refresh", {
69
+ method: "POST"
70
+ });
69
71
  state.user.value = user;
70
72
  state.activeOrganization.value = mapOrganization(organization);
71
73
  } catch {
@@ -79,7 +81,6 @@ export default defineNuxtPlugin({
79
81
  }
80
82
  function logout() {
81
83
  accessTokenCookie.value = null;
82
- refreshTokenCookie.value = null;
83
84
  state.user.value = null;
84
85
  state.activeOrganization.value = null;
85
86
  }
@@ -99,7 +100,7 @@ export default defineNuxtPlugin({
99
100
  return;
100
101
  }
101
102
  const nextRefresh = Math.max(expiry - TWO_MINUTES - Date.now(), 0);
102
- tokenRefreshInterval = window.setTimeout(refreshToken, nextRefresh);
103
+ tokenRefreshInterval = window.setTimeout(() => void refreshToken(), nextRefresh);
103
104
  }
104
105
  if (import.meta.server) {
105
106
  if (accessTokenCookie.value) {
@@ -134,11 +135,7 @@ export default defineNuxtPlugin({
134
135
  return;
135
136
  }
136
137
  if (import.meta.client) {
137
- watch(refreshTokenCookie, async (newVal) => {
138
- if (newVal) {
139
- await scheduleTokenRefresh();
140
- }
141
- }, { immediate: true });
138
+ void scheduleTokenRefresh();
142
139
  }
143
140
  }
144
141
  });