@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.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/module.d.mts +10 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +126 -0
- package/dist/runtime/components/transition/LtTransitionFade.d.vue.ts +39 -0
- package/dist/runtime/components/transition/LtTransitionFade.vue +21 -0
- package/dist/runtime/components/transition/LtTransitionFade.vue.d.ts +39 -0
- package/dist/runtime/components/transition/LtTransitionFadeScale.d.vue.ts +42 -0
- package/dist/runtime/components/transition/LtTransitionFadeScale.vue +21 -0
- package/dist/runtime/components/transition/LtTransitionFadeScale.vue.d.ts +42 -0
- package/dist/runtime/components/transition/LtTransitionSlide.d.vue.ts +13 -0
- package/dist/runtime/components/transition/LtTransitionSlide.vue +16 -0
- package/dist/runtime/components/transition/LtTransitionSlide.vue.d.ts +13 -0
- package/dist/runtime/components/transition/LtTransitionSlideBottom.d.vue.ts +13 -0
- package/dist/runtime/components/transition/LtTransitionSlideBottom.vue +16 -0
- package/dist/runtime/components/transition/LtTransitionSlideBottom.vue.d.ts +13 -0
- package/dist/runtime/components/transition/LtTransitionSlideRevert.d.vue.ts +13 -0
- package/dist/runtime/components/transition/LtTransitionSlideRevert.vue +16 -0
- package/dist/runtime/components/transition/LtTransitionSlideRevert.vue.d.ts +13 -0
- package/dist/runtime/composables/auth/use-lt-auth.d.ts +32 -0
- package/dist/runtime/composables/auth/use-lt-auth.js +411 -0
- package/dist/runtime/composables/index.d.ts +5 -0
- package/dist/runtime/composables/index.js +5 -0
- package/dist/runtime/composables/use-lt-auth-client.d.ts +29 -0
- package/dist/runtime/composables/use-lt-auth-client.js +27 -0
- package/dist/runtime/composables/use-lt-file.d.ts +27 -0
- package/dist/runtime/composables/use-lt-file.js +60 -0
- package/dist/runtime/composables/use-lt-share.d.ts +32 -0
- package/dist/runtime/composables/use-lt-share.js +52 -0
- package/dist/runtime/composables/use-lt-tus-upload.d.ts +32 -0
- package/dist/runtime/composables/use-lt-tus-upload.js +236 -0
- package/dist/runtime/lib/auth-client.d.ts +831 -0
- package/dist/runtime/lib/auth-client.js +144 -0
- package/dist/runtime/lib/auth-state.d.ts +58 -0
- package/dist/runtime/lib/auth-state.js +131 -0
- package/dist/runtime/lib/index.d.ts +2 -0
- package/dist/runtime/lib/index.js +12 -0
- package/dist/runtime/locales/de.json +19 -0
- package/dist/runtime/locales/en.json +19 -0
- package/dist/runtime/plugins/auth-interceptor.client.d.ts +14 -0
- package/dist/runtime/plugins/auth-interceptor.client.js +88 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/types/auth.d.ts +127 -0
- package/dist/runtime/types/auth.js +0 -0
- package/dist/runtime/types/index.d.ts +3 -0
- package/dist/runtime/types/index.js +0 -0
- package/dist/runtime/types/module.d.ts +88 -0
- package/dist/runtime/types/module.js +0 -0
- package/dist/runtime/types/upload.d.ts +133 -0
- package/dist/runtime/types/upload.js +0 -0
- package/dist/runtime/utils/crypto.d.ts +40 -0
- package/dist/runtime/utils/crypto.js +17 -0
- package/dist/runtime/utils/index.d.ts +2 -0
- package/dist/runtime/utils/index.js +2 -0
- package/dist/runtime/utils/tw.d.ts +20 -0
- package/dist/runtime/utils/tw.js +1 -0
- package/dist/types.d.mts +9 -0
- 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,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,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
|