@lenne.tech/nuxt-extensions 1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/dist/module.d.mts +10 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +126 -0
  6. package/dist/runtime/components/transition/LtTransitionFade.d.vue.ts +39 -0
  7. package/dist/runtime/components/transition/LtTransitionFade.vue +21 -0
  8. package/dist/runtime/components/transition/LtTransitionFade.vue.d.ts +39 -0
  9. package/dist/runtime/components/transition/LtTransitionFadeScale.d.vue.ts +42 -0
  10. package/dist/runtime/components/transition/LtTransitionFadeScale.vue +21 -0
  11. package/dist/runtime/components/transition/LtTransitionFadeScale.vue.d.ts +42 -0
  12. package/dist/runtime/components/transition/LtTransitionSlide.d.vue.ts +13 -0
  13. package/dist/runtime/components/transition/LtTransitionSlide.vue +16 -0
  14. package/dist/runtime/components/transition/LtTransitionSlide.vue.d.ts +13 -0
  15. package/dist/runtime/components/transition/LtTransitionSlideBottom.d.vue.ts +13 -0
  16. package/dist/runtime/components/transition/LtTransitionSlideBottom.vue +16 -0
  17. package/dist/runtime/components/transition/LtTransitionSlideBottom.vue.d.ts +13 -0
  18. package/dist/runtime/components/transition/LtTransitionSlideRevert.d.vue.ts +13 -0
  19. package/dist/runtime/components/transition/LtTransitionSlideRevert.vue +16 -0
  20. package/dist/runtime/components/transition/LtTransitionSlideRevert.vue.d.ts +13 -0
  21. package/dist/runtime/composables/auth/use-lt-auth.d.ts +32 -0
  22. package/dist/runtime/composables/auth/use-lt-auth.js +411 -0
  23. package/dist/runtime/composables/index.d.ts +5 -0
  24. package/dist/runtime/composables/index.js +5 -0
  25. package/dist/runtime/composables/use-lt-auth-client.d.ts +29 -0
  26. package/dist/runtime/composables/use-lt-auth-client.js +27 -0
  27. package/dist/runtime/composables/use-lt-file.d.ts +27 -0
  28. package/dist/runtime/composables/use-lt-file.js +60 -0
  29. package/dist/runtime/composables/use-lt-share.d.ts +32 -0
  30. package/dist/runtime/composables/use-lt-share.js +52 -0
  31. package/dist/runtime/composables/use-lt-tus-upload.d.ts +32 -0
  32. package/dist/runtime/composables/use-lt-tus-upload.js +236 -0
  33. package/dist/runtime/lib/auth-client.d.ts +831 -0
  34. package/dist/runtime/lib/auth-client.js +144 -0
  35. package/dist/runtime/lib/auth-state.d.ts +58 -0
  36. package/dist/runtime/lib/auth-state.js +131 -0
  37. package/dist/runtime/lib/index.d.ts +2 -0
  38. package/dist/runtime/lib/index.js +12 -0
  39. package/dist/runtime/locales/de.json +19 -0
  40. package/dist/runtime/locales/en.json +19 -0
  41. package/dist/runtime/plugins/auth-interceptor.client.d.ts +14 -0
  42. package/dist/runtime/plugins/auth-interceptor.client.js +88 -0
  43. package/dist/runtime/server/tsconfig.json +3 -0
  44. package/dist/runtime/types/auth.d.ts +127 -0
  45. package/dist/runtime/types/auth.js +0 -0
  46. package/dist/runtime/types/index.d.ts +3 -0
  47. package/dist/runtime/types/index.js +0 -0
  48. package/dist/runtime/types/module.d.ts +88 -0
  49. package/dist/runtime/types/module.js +0 -0
  50. package/dist/runtime/types/upload.d.ts +133 -0
  51. package/dist/runtime/types/upload.js +0 -0
  52. package/dist/runtime/utils/crypto.d.ts +40 -0
  53. package/dist/runtime/utils/crypto.js +17 -0
  54. package/dist/runtime/utils/index.d.ts +2 -0
  55. package/dist/runtime/utils/index.js +2 -0
  56. package/dist/runtime/utils/tw.d.ts +20 -0
  57. package/dist/runtime/utils/tw.js +1 -0
  58. package/dist/types.d.mts +9 -0
  59. package/package.json +99 -0
@@ -0,0 +1,144 @@
1
+ import { passkeyClient } from "@better-auth/passkey/client";
2
+ import { adminClient, twoFactorClient } from "better-auth/client/plugins";
3
+ import { createAuthClient } from "better-auth/vue";
4
+ import { navigateTo } from "#imports";
5
+ import { ltSha256 } from "../utils/crypto.js";
6
+ import { createLtAuthFetch } from "./auth-state.js";
7
+ export function createLtAuthClient(config = {}) {
8
+ const isDev = import.meta.dev || process.env.NODE_ENV === "local";
9
+ const defaultBaseURL = isDev ? "" : import.meta.env?.VITE_API_URL || process.env.API_URL || "http://localhost:3000";
10
+ const defaultBasePath = isDev ? "/api/iam" : "/iam";
11
+ const {
12
+ baseURL = defaultBaseURL,
13
+ basePath = defaultBasePath,
14
+ twoFactorRedirectPath = "/auth/2fa",
15
+ enableAdmin = true,
16
+ enableTwoFactor = true,
17
+ enablePasskey = true
18
+ } = config;
19
+ const plugins = [];
20
+ if (enableAdmin) {
21
+ plugins.push(adminClient());
22
+ }
23
+ if (enableTwoFactor) {
24
+ plugins.push(
25
+ twoFactorClient({
26
+ onTwoFactorRedirect() {
27
+ navigateTo(twoFactorRedirectPath);
28
+ }
29
+ })
30
+ );
31
+ }
32
+ if (enablePasskey) {
33
+ plugins.push(passkeyClient());
34
+ }
35
+ const authFetch = createLtAuthFetch(basePath.replace("/api", ""));
36
+ const baseClient = createAuthClient({
37
+ basePath,
38
+ baseURL,
39
+ fetchOptions: {
40
+ customFetchImpl: authFetch
41
+ },
42
+ plugins
43
+ });
44
+ return {
45
+ // Spread all base client properties and methods
46
+ ...baseClient,
47
+ // Explicitly pass through methods not captured by spread operator
48
+ useSession: baseClient.useSession,
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ passkey: baseClient.passkey,
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ admin: baseClient.admin,
53
+ $Infer: baseClient.$Infer,
54
+ $fetch: baseClient.$fetch,
55
+ $store: baseClient.$store,
56
+ /**
57
+ * Change password for an authenticated user (both passwords are hashed)
58
+ */
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ changePassword: async (params, options) => {
61
+ const [hashedCurrent, hashedNew] = await Promise.all([ltSha256(params.currentPassword), ltSha256(params.newPassword)]);
62
+ return baseClient.changePassword?.({ currentPassword: hashedCurrent, newPassword: hashedNew }, options);
63
+ },
64
+ /**
65
+ * Reset password with token (new password is hashed before sending)
66
+ */
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ resetPassword: async (params, options) => {
69
+ const hashedPassword = await ltSha256(params.newPassword);
70
+ return baseClient.resetPassword?.({ newPassword: hashedPassword, token: params.token }, options);
71
+ },
72
+ // Override signIn to hash password (keep passkey method from plugin)
73
+ signIn: {
74
+ ...baseClient.signIn,
75
+ /**
76
+ * Sign in with email and password (password is hashed before sending)
77
+ */
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ email: async (params, options) => {
80
+ const hashedPassword = await ltSha256(params.password);
81
+ return baseClient.signIn.email({ ...params, password: hashedPassword }, options);
82
+ },
83
+ /**
84
+ * Sign in with passkey (pass through to base client - provided by passkeyClient plugin)
85
+ * @see https://www.better-auth.com/docs/plugins/passkey
86
+ */
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ passkey: baseClient.signIn.passkey
89
+ },
90
+ // Explicitly pass through signOut (not captured by spread operator)
91
+ signOut: baseClient.signOut,
92
+ // Override signUp to hash password
93
+ signUp: {
94
+ ...baseClient.signUp,
95
+ /**
96
+ * Sign up with email and password (password is hashed before sending)
97
+ */
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ email: async (params, options) => {
100
+ const hashedPassword = await ltSha256(params.password);
101
+ return baseClient.signUp.email({ ...params, password: hashedPassword }, options);
102
+ }
103
+ },
104
+ // Override twoFactor to hash passwords (provided by twoFactorClient plugin)
105
+ twoFactor: {
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ ...baseClient.twoFactor,
108
+ /**
109
+ * Disable 2FA (password is hashed before sending)
110
+ */
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ disable: async (params, options) => {
113
+ const hashedPassword = await ltSha256(params.password);
114
+ return baseClient.twoFactor.disable({ password: hashedPassword }, options);
115
+ },
116
+ /**
117
+ * Enable 2FA (password is hashed before sending)
118
+ */
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ enable: async (params, options) => {
121
+ const hashedPassword = await ltSha256(params.password);
122
+ return baseClient.twoFactor.enable({ password: hashedPassword }, options);
123
+ },
124
+ /**
125
+ * Generate backup codes (password is hashed before sending)
126
+ */
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ generateBackupCodes: async (params, options) => {
129
+ const hashedPassword = await ltSha256(params.password);
130
+ return baseClient.twoFactor.generateBackupCodes({ password: hashedPassword }, options);
131
+ },
132
+ /**
133
+ * Verify TOTP code (pass through to base client)
134
+ */
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ verifyTotp: baseClient.twoFactor.verifyTotp,
137
+ /**
138
+ * Verify backup code (pass through to base client)
139
+ */
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ verifyBackupCode: baseClient.twoFactor.verifyBackupCode
142
+ }
143
+ };
144
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared authentication state for Cookie/JWT dual-mode authentication
3
+ *
4
+ * This module provides a reactive state that is shared between:
5
+ * - auth-client.ts (uses it for customFetch)
6
+ * - use-lt-auth.ts (manages the state)
7
+ *
8
+ * Auth Mode Strategy:
9
+ * 1. Primary: Session cookies (more secure, HttpOnly)
10
+ * 2. Fallback: JWT tokens (when cookies are not available/working)
11
+ *
12
+ * The state is persisted in cookies for SSR compatibility.
13
+ */
14
+ import type { LtAuthMode } from '../types/index.js';
15
+ /**
16
+ * Get the current auth mode from cookie
17
+ */
18
+ export declare function getLtAuthMode(): LtAuthMode;
19
+ /**
20
+ * Get the JWT token from cookie
21
+ */
22
+ export declare function getLtJwtToken(): string | null;
23
+ /**
24
+ * Set JWT token in cookie
25
+ */
26
+ export declare function setLtJwtToken(token: string | null): void;
27
+ /**
28
+ * Update auth mode in the lt-auth-state cookie
29
+ */
30
+ export declare function setLtAuthMode(mode: LtAuthMode): void;
31
+ /**
32
+ * Get the API base URL from runtime config
33
+ *
34
+ * @param basePath - The auth API base path (default: '/iam')
35
+ */
36
+ export declare function getLtApiBase(basePath?: string): string;
37
+ /**
38
+ * Attempt to switch to JWT mode by fetching a token
39
+ *
40
+ * @param basePath - The auth API base path (default: '/iam')
41
+ */
42
+ export declare function attemptLtJwtSwitch(basePath?: string): Promise<boolean>;
43
+ /**
44
+ * Check if user is authenticated (has lt-auth-state with user)
45
+ */
46
+ export declare function isLtAuthenticated(): boolean;
47
+ /**
48
+ * Custom fetch function that handles Cookie/JWT dual-mode authentication
49
+ *
50
+ * This function:
51
+ * 1. In cookie mode: Uses credentials: 'include'
52
+ * 2. In JWT mode: Adds Authorization header
53
+ * 3. On 401 in cookie mode: Attempts to switch to JWT and retries
54
+ *
55
+ * @param basePath - The auth API base path for JWT switch (default: '/iam')
56
+ */
57
+ export declare function createLtAuthFetch(basePath?: string): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
58
+ export declare const ltAuthFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -0,0 +1,131 @@
1
+ export function getLtAuthMode() {
2
+ if (import.meta.server) return "cookie";
3
+ try {
4
+ const cookie = document.cookie.split("; ").find((row) => row.startsWith("lt-auth-state="));
5
+ if (cookie) {
6
+ const parts = cookie.split("=");
7
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join("=")) : "";
8
+ const state = JSON.parse(value);
9
+ return state?.authMode || "cookie";
10
+ }
11
+ } catch {
12
+ }
13
+ return "cookie";
14
+ }
15
+ export function getLtJwtToken() {
16
+ if (import.meta.server) return null;
17
+ try {
18
+ const cookie = document.cookie.split("; ").find((row) => row.startsWith("lt-jwt-token="));
19
+ if (cookie) {
20
+ const parts = cookie.split("=");
21
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join("=")) : "";
22
+ if (value.startsWith('"') && value.endsWith('"')) {
23
+ return JSON.parse(value);
24
+ }
25
+ return value || null;
26
+ }
27
+ } catch {
28
+ }
29
+ return null;
30
+ }
31
+ export function setLtJwtToken(token) {
32
+ if (import.meta.server) return;
33
+ const maxAge = 60 * 60 * 24 * 7;
34
+ if (token) {
35
+ document.cookie = `lt-jwt-token=${encodeURIComponent(JSON.stringify(token))}; path=/; max-age=${maxAge}; samesite=lax`;
36
+ } else {
37
+ document.cookie = `lt-jwt-token=; path=/; max-age=0`;
38
+ }
39
+ }
40
+ export function setLtAuthMode(mode) {
41
+ if (import.meta.server) return;
42
+ try {
43
+ const cookie = document.cookie.split("; ").find((row) => row.startsWith("lt-auth-state="));
44
+ let state = { user: null, authMode: mode };
45
+ if (cookie) {
46
+ const parts = cookie.split("=");
47
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join("=")) : "";
48
+ state = { ...JSON.parse(value), authMode: mode };
49
+ }
50
+ const maxAge = 60 * 60 * 24 * 7;
51
+ document.cookie = `lt-auth-state=${encodeURIComponent(JSON.stringify(state))}; path=/; max-age=${maxAge}; samesite=lax`;
52
+ } catch {
53
+ }
54
+ }
55
+ export function getLtApiBase(basePath = "/iam") {
56
+ const isDev = import.meta.dev;
57
+ if (isDev) {
58
+ return `/api${basePath}`;
59
+ }
60
+ if (typeof window !== "undefined" && window.__NUXT__?.config?.public?.ltExtensions?.auth?.baseURL) {
61
+ return `${window.__NUXT__.config.public.ltExtensions.auth.baseURL}${basePath}`;
62
+ }
63
+ return `http://localhost:3000${basePath}`;
64
+ }
65
+ export async function attemptLtJwtSwitch(basePath = "/iam") {
66
+ try {
67
+ const apiBase = getLtApiBase(basePath);
68
+ const response = await fetch(`${apiBase}/token`, {
69
+ method: "GET",
70
+ credentials: "include"
71
+ });
72
+ if (response.ok) {
73
+ const data = await response.json();
74
+ if (data.token) {
75
+ setLtJwtToken(data.token);
76
+ setLtAuthMode("jwt");
77
+ console.debug("[LtAuth] Switched to JWT mode");
78
+ return true;
79
+ }
80
+ }
81
+ return false;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ export function isLtAuthenticated() {
87
+ if (import.meta.server) return false;
88
+ try {
89
+ const cookie = document.cookie.split("; ").find((row) => row.startsWith("lt-auth-state="));
90
+ if (cookie) {
91
+ const parts = cookie.split("=");
92
+ const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join("=")) : "";
93
+ const state = JSON.parse(value);
94
+ return !!state?.user;
95
+ }
96
+ } catch {
97
+ }
98
+ return false;
99
+ }
100
+ export function createLtAuthFetch(basePath = "/iam") {
101
+ return async function ltAuthFetch2(input, init) {
102
+ const authMode = getLtAuthMode();
103
+ const jwtToken = getLtJwtToken();
104
+ const headers = new Headers(init?.headers);
105
+ if (authMode === "jwt" && jwtToken) {
106
+ headers.set("Authorization", `Bearer ${jwtToken}`);
107
+ }
108
+ const response = await fetch(input, {
109
+ ...init,
110
+ headers,
111
+ credentials: "include"
112
+ });
113
+ if (response.status === 401 && authMode === "cookie" && isLtAuthenticated()) {
114
+ console.debug("[LtAuth] Cookie auth failed, attempting JWT fallback...");
115
+ const switched = await attemptLtJwtSwitch(basePath);
116
+ if (switched) {
117
+ const newToken = getLtJwtToken();
118
+ if (newToken) {
119
+ headers.set("Authorization", `Bearer ${newToken}`);
120
+ return fetch(input, {
121
+ ...init,
122
+ headers,
123
+ credentials: "include"
124
+ });
125
+ }
126
+ }
127
+ }
128
+ return response;
129
+ };
130
+ }
131
+ export const ltAuthFetch = createLtAuthFetch("/iam");
@@ -0,0 +1,2 @@
1
+ export { attemptLtJwtSwitch, createLtAuthFetch, getLtApiBase, getLtAuthMode, getLtJwtToken, isLtAuthenticated, ltAuthFetch, setLtAuthMode, setLtJwtToken, } from './auth-state.js';
2
+ export { createLtAuthClient, type LtAuthClient } from './auth-client.js';
@@ -0,0 +1,12 @@
1
+ export {
2
+ attemptLtJwtSwitch,
3
+ createLtAuthFetch,
4
+ getLtApiBase,
5
+ getLtAuthMode,
6
+ getLtJwtToken,
7
+ isLtAuthenticated,
8
+ ltAuthFetch,
9
+ setLtAuthMode,
10
+ setLtJwtToken
11
+ } from "./auth-state.js";
12
+ export { createLtAuthClient } from "./auth-client.js";
@@ -0,0 +1,19 @@
1
+ {
2
+ "lt": {
3
+ "auth": {
4
+ "loggingOut": "Abmelden...",
5
+ "noPasskeySelected": "Kein Passkey ausgewählt",
6
+ "passkeyAborted": "Passkey-Authentifizierung wurde abgebrochen",
7
+ "passkeyCreationAborted": "Passkey-Erstellung wurde abgebrochen",
8
+ "passkeyError": "Konnte Passkey-Optionen nicht laden",
9
+ "passkeyFailed": "Passkey-Anmeldung fehlgeschlagen",
10
+ "passkeyRegisterFailed": "Passkey-Registrierung fehlgeschlagen",
11
+ "registerOptionsError": "Konnte Registrierungsoptionen nicht laden",
12
+ "sessionExpired": "Sitzung abgelaufen"
13
+ },
14
+ "share": {
15
+ "copied": "Link kopiert",
16
+ "copiedDescription": "Der Link wurde in die Zwischenablage kopiert."
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "lt": {
3
+ "auth": {
4
+ "loggingOut": "Logging out...",
5
+ "noPasskeySelected": "No passkey selected",
6
+ "passkeyAborted": "Passkey authentication was cancelled",
7
+ "passkeyCreationAborted": "Passkey creation was cancelled",
8
+ "passkeyError": "Could not load passkey options",
9
+ "passkeyFailed": "Passkey login failed",
10
+ "passkeyRegisterFailed": "Passkey registration failed",
11
+ "registerOptionsError": "Could not load registration options",
12
+ "sessionExpired": "Session expired"
13
+ },
14
+ "share": {
15
+ "copied": "Link copied",
16
+ "copiedDescription": "The link has been copied to clipboard."
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Auth Interceptor Plugin
3
+ *
4
+ * This plugin intercepts all API responses and handles session expiration.
5
+ * When a 401 (Unauthorized) response is received, it automatically:
6
+ * 1. Clears the user session state
7
+ * 2. Redirects to the login page
8
+ *
9
+ * Note: This is a client-only plugin (.client.ts) since auth state
10
+ * management only makes sense in the browser context.
11
+ */
12
+ import type { NuxtApp } from '#app';
13
+ declare const _default: (nuxtApp: NuxtApp) => void;
14
+ export default _default;
@@ -0,0 +1,88 @@
1
+ import { useLtAuth } from "../composables/auth/use-lt-auth.js";
2
+ export default (nuxtApp) => {
3
+ if (import.meta.server) return;
4
+ const { clearUser, isAuthenticated } = useLtAuth();
5
+ const runtimeConfig = nuxtApp.$config?.public?.ltExtensions?.auth || {};
6
+ const loginPath = runtimeConfig.loginPath || "/auth/login";
7
+ const configuredPublicPaths = runtimeConfig.interceptor?.publicPaths || [];
8
+ let isHandling401 = false;
9
+ const defaultPublicPaths = ["/auth/login", "/auth/register", "/auth/forgot-password", "/auth/reset-password", "/auth/2fa"];
10
+ const publicAuthPaths = [.../* @__PURE__ */ new Set([...defaultPublicPaths, ...configuredPublicPaths])];
11
+ function isPublicAuthRoute() {
12
+ const route = nuxtApp.$router?.currentRoute?.value;
13
+ if (!route) return false;
14
+ return publicAuthPaths.some((path) => route.path.startsWith(path));
15
+ }
16
+ function isAuthEndpoint(url) {
17
+ const authEndpoints = [
18
+ "/sign-in",
19
+ "/sign-up",
20
+ "/sign-out",
21
+ "/forgot-password",
22
+ "/reset-password",
23
+ "/verify-email",
24
+ "/session",
25
+ "/token",
26
+ // Passkey endpoints - handled by authFetch with JWT fallback
27
+ "/passkey/",
28
+ "/list-user-passkeys",
29
+ "/generate-register-options",
30
+ "/verify-registration",
31
+ "/generate-authenticate-options",
32
+ "/verify-authentication",
33
+ // Two-factor endpoints
34
+ "/two-factor/"
35
+ ];
36
+ return authEndpoints.some((endpoint) => url.includes(endpoint));
37
+ }
38
+ async function handleUnauthorized(requestUrl) {
39
+ if (isHandling401) {
40
+ return;
41
+ }
42
+ if (requestUrl && isAuthEndpoint(requestUrl)) {
43
+ return;
44
+ }
45
+ if (isPublicAuthRoute()) {
46
+ return;
47
+ }
48
+ isHandling401 = true;
49
+ try {
50
+ if (isAuthenticated.value) {
51
+ console.debug("[LtAuth Interceptor] Session expired, logging out...");
52
+ clearUser();
53
+ const currentPath = nuxtApp.$router?.currentRoute?.value?.fullPath;
54
+ const redirectQuery = currentPath && currentPath !== loginPath ? `?redirect=${encodeURIComponent(currentPath)}` : "";
55
+ window.location.href = loginPath + redirectQuery;
56
+ }
57
+ } finally {
58
+ setTimeout(() => {
59
+ isHandling401 = false;
60
+ }, 1e3);
61
+ }
62
+ }
63
+ const originalFetch = globalThis.$fetch;
64
+ globalThis.$fetch = ((url, options) => {
65
+ return originalFetch(url, {
66
+ ...options,
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ onResponseError: (context) => {
69
+ if (options?.onResponseError) {
70
+ options.onResponseError(context);
71
+ }
72
+ if (context.response?.status === 401) {
73
+ handleUnauthorized(url);
74
+ }
75
+ }
76
+ });
77
+ });
78
+ const originalNativeFetch = globalThis.fetch;
79
+ globalThis.fetch = async (input, init) => {
80
+ const response = await originalNativeFetch(input, init);
81
+ if (response.status === 401) {
82
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
83
+ handleUnauthorized(url);
84
+ }
85
+ return response;
86
+ };
87
+ nuxtApp.provide("ltHandleUnauthorized", handleUnauthorized);
88
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json",
3
+ }
@@ -0,0 +1,127 @@
1
+ import type { ComputedRef, Ref } from 'vue';
2
+ /**
3
+ * User type for Better Auth session
4
+ * Compatible with @lenne.tech/nest-server IAM module
5
+ */
6
+ export interface LtUser {
7
+ banExpires?: Date;
8
+ banned?: boolean;
9
+ banReason?: string;
10
+ email: string;
11
+ emailVerified?: boolean;
12
+ id: string;
13
+ image?: string;
14
+ name?: string;
15
+ role?: string;
16
+ twoFactorEnabled?: boolean;
17
+ }
18
+ /**
19
+ * Authentication mode for Cookie/JWT dual-mode authentication
20
+ * - 'cookie': Primary mode using HttpOnly session cookies (more secure)
21
+ * - 'jwt': Fallback mode using JWT tokens in Authorization header
22
+ */
23
+ export type LtAuthMode = 'cookie' | 'jwt';
24
+ /**
25
+ * Stored auth state (persisted in cookie for SSR compatibility)
26
+ */
27
+ export interface LtAuthState {
28
+ authMode: LtAuthMode;
29
+ user: LtUser | null;
30
+ }
31
+ /**
32
+ * Configuration options for the auth client factory
33
+ * All options have sensible defaults for nest-server compatibility
34
+ */
35
+ export interface LtAuthClientConfig {
36
+ /** API base URL (default: from env or http://localhost:3000) */
37
+ baseURL?: string;
38
+ /** Auth API base path (default: '/iam' - must match nest-server betterAuth.basePath) */
39
+ basePath?: string;
40
+ /** Enable admin plugin (default: true) */
41
+ enableAdmin?: boolean;
42
+ /** Enable passkey plugin (default: true) */
43
+ enablePasskey?: boolean;
44
+ /** Enable 2FA plugin (default: true) */
45
+ enableTwoFactor?: boolean;
46
+ /** 2FA redirect path (default: '/auth/2fa') */
47
+ twoFactorRedirectPath?: string;
48
+ }
49
+ /**
50
+ * Normalized response type for Better-Auth operations
51
+ * The Vue client returns complex union types - this provides a consistent interface
52
+ */
53
+ export interface LtAuthResponse {
54
+ data?: null | {
55
+ redirect?: boolean;
56
+ token?: null | string;
57
+ url?: string;
58
+ user?: LtUser;
59
+ };
60
+ error?: null | {
61
+ code?: string;
62
+ message?: string;
63
+ status?: number;
64
+ };
65
+ }
66
+ /**
67
+ * Result of passkey authentication
68
+ */
69
+ export interface LtPasskeyAuthResult {
70
+ error?: string;
71
+ session?: {
72
+ token: string;
73
+ };
74
+ success: boolean;
75
+ user?: LtUser;
76
+ }
77
+ /**
78
+ * Result of passkey registration
79
+ */
80
+ export interface LtPasskeyRegisterResult {
81
+ error?: string;
82
+ passkey?: unknown;
83
+ success: boolean;
84
+ }
85
+ /**
86
+ * Return type for useLtAuth composable
87
+ */
88
+ export interface UseLtAuthReturn {
89
+ authMode: ComputedRef<LtAuthMode>;
90
+ isAuthenticated: ComputedRef<boolean>;
91
+ isJwtMode: ComputedRef<boolean>;
92
+ isLoading: ComputedRef<boolean>;
93
+ jwtToken: Ref<string | null>;
94
+ user: ComputedRef<LtUser | null>;
95
+ is2FAEnabled: ComputedRef<boolean>;
96
+ isAdmin: ComputedRef<boolean>;
97
+ authenticateWithPasskey: () => Promise<LtPasskeyAuthResult>;
98
+ changePassword: (params: {
99
+ currentPassword: string;
100
+ newPassword: string;
101
+ }, options?: unknown) => Promise<unknown>;
102
+ clearUser: () => void;
103
+ fetchWithAuth: (url: string, options?: RequestInit) => Promise<Response>;
104
+ refreshJwtToken: () => Promise<boolean>;
105
+ registerPasskey: (name?: string) => Promise<LtPasskeyRegisterResult>;
106
+ setUser: (userData: LtUser | null, mode?: LtAuthMode) => void;
107
+ signIn: {
108
+ email: (params: {
109
+ email: string;
110
+ password: string;
111
+ rememberMe?: boolean;
112
+ }, options?: unknown) => Promise<unknown>;
113
+ passkey?: (options?: unknown) => Promise<unknown>;
114
+ };
115
+ signOut: (options?: unknown) => Promise<unknown>;
116
+ signUp: {
117
+ email: (params: {
118
+ email: string;
119
+ name: string;
120
+ password: string;
121
+ }, options?: unknown) => Promise<unknown>;
122
+ };
123
+ switchToJwtMode: () => Promise<boolean>;
124
+ validateSession: () => Promise<boolean>;
125
+ passkey?: unknown;
126
+ twoFactor?: unknown;
127
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ export type { LtAuthClientConfig, LtAuthMode, LtAuthResponse, LtAuthState, LtPasskeyAuthResult, LtPasskeyRegisterResult, LtUser, UseLtAuthReturn, } from './auth.js';
2
+ export type { LtFileInfo, LtUploadItem, LtUploadOptions, LtUploadProgress, LtUploadStatus, UseLtFileReturn, UseLtTusUploadReturn, } from './upload.js';
3
+ export type { LtAuthModuleOptions, LtExtensionsModuleOptions, LtExtensionsPublicRuntimeConfig, LtI18nModuleOptions, LtTusModuleOptions, } from './module.js';
File without changes