@lenne.tech/nuxt-extensions 1.0.0 → 1.1.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 +1 -1
- package/dist/module.d.mts +4 -1
- package/dist/module.mjs +28 -9
- package/dist/runtime/components/transition/LtTransitionFade.vue +3 -1
- package/dist/runtime/components/transition/LtTransitionFadeScale.vue +3 -1
- package/dist/runtime/composables/auth/use-lt-auth.d.ts +1 -1
- package/dist/runtime/composables/auth/use-lt-auth.js +49 -13
- package/dist/runtime/composables/index.d.ts +6 -5
- package/dist/runtime/composables/index.js +1 -0
- package/dist/runtime/composables/use-lt-auth-client.d.ts +9 -1
- package/dist/runtime/composables/use-lt-auth-client.js +26 -2
- package/dist/runtime/composables/use-lt-error-translation.d.ts +33 -0
- package/dist/runtime/composables/use-lt-error-translation.js +133 -0
- package/dist/runtime/composables/use-lt-file.d.ts +1 -1
- package/dist/runtime/composables/use-lt-share.js +4 -1
- package/dist/runtime/composables/use-lt-tus-upload.d.ts +1 -1
- package/dist/runtime/composables/use-lt-tus-upload.js +7 -1
- package/dist/runtime/lib/auth-client.d.ts +1 -1
- package/dist/runtime/lib/auth-client.js +18 -6
- package/dist/runtime/lib/auth-state.d.ts +10 -1
- package/dist/runtime/lib/auth-state.js +17 -1
- package/dist/runtime/lib/index.d.ts +2 -2
- package/dist/runtime/locales/de.json +4 -0
- package/dist/runtime/locales/en.json +4 -0
- package/dist/runtime/plugins/auth-interceptor.client.d.ts +1 -1
- package/dist/runtime/plugins/auth-interceptor.client.js +12 -4
- package/dist/runtime/plugins/error-translation.client.d.ts +8 -0
- package/dist/runtime/plugins/error-translation.client.js +17 -0
- package/dist/runtime/server/tsconfig.json +1 -1
- package/dist/runtime/testing/index.d.ts +21 -0
- package/dist/runtime/testing/index.js +17 -0
- package/dist/runtime/testing/playwright-helpers.d.ts +296 -0
- package/dist/runtime/testing/playwright-helpers.js +127 -0
- package/dist/runtime/types/auth.d.ts +2 -2
- package/dist/runtime/types/error.d.ts +48 -0
- package/dist/runtime/types/error.js +0 -0
- package/dist/runtime/types/index.d.ts +4 -3
- package/dist/runtime/types/module.d.ts +17 -2
- package/dist/runtime/types/upload.d.ts +2 -2
- package/dist/runtime/utils/index.d.ts +2 -2
- package/dist/types.d.mts +7 -1
- package/package.json +18 -2
package/LICENSE
CHANGED
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { LtExtensionsModuleOptions } from '../dist/runtime/types/index.js';
|
|
3
|
-
export {
|
|
3
|
+
export { LtAuthClientConfig, LtAuthMode, LtAuthResponse, LtAuthState, LtPasskeyAuthResult, LtPasskeyRegisterResult, LtUser, UseLtAuthReturn } from '../dist/runtime/types/auth.js';
|
|
4
|
+
export { LtFileInfo, LtUploadItem, LtUploadOptions, LtUploadProgress, LtUploadStatus, UseLtFileReturn, UseLtTusUploadReturn } from '../dist/runtime/types/upload.js';
|
|
5
|
+
export { LtAuthModuleOptions, LtErrorTranslationModuleOptions, LtExtensionsModuleOptions, LtExtensionsPublicRuntimeConfig, LtI18nModuleOptions, LtTusModuleOptions } from '../dist/runtime/types/module.js';
|
|
6
|
+
export { LtErrorTranslationResponse, LtParsedError, UseLtErrorTranslationReturn } from '../dist/runtime/types/error.js';
|
|
4
7
|
|
|
5
8
|
declare const name = "@lenne.tech/nuxt-extensions";
|
|
6
9
|
declare const version = "1.0.0";
|
package/dist/module.mjs
CHANGED
|
@@ -18,6 +18,10 @@ const defaultOptions = {
|
|
|
18
18
|
loginPath: "/auth/login",
|
|
19
19
|
twoFactorRedirectPath: "/auth/2fa"
|
|
20
20
|
},
|
|
21
|
+
errorTranslation: {
|
|
22
|
+
enabled: true,
|
|
23
|
+
defaultLocale: "de"
|
|
24
|
+
},
|
|
21
25
|
i18n: {
|
|
22
26
|
autoMerge: true
|
|
23
27
|
},
|
|
@@ -38,6 +42,7 @@ const module$1 = defineNuxtModule({
|
|
|
38
42
|
const { resolve } = createResolver(import.meta.url);
|
|
39
43
|
const resolvedOptions = {
|
|
40
44
|
auth: { ...defaultOptions.auth, ...options.auth },
|
|
45
|
+
errorTranslation: { ...defaultOptions.errorTranslation, ...options.errorTranslation },
|
|
41
46
|
i18n: { ...defaultOptions.i18n, ...options.i18n },
|
|
42
47
|
tus: { ...defaultOptions.tus, ...options.tus }
|
|
43
48
|
};
|
|
@@ -56,6 +61,10 @@ const module$1 = defineNuxtModule({
|
|
|
56
61
|
loginPath: resolvedOptions.auth?.loginPath || "/auth/login",
|
|
57
62
|
twoFactorRedirectPath: resolvedOptions.auth?.twoFactorRedirectPath || "/auth/2fa"
|
|
58
63
|
},
|
|
64
|
+
errorTranslation: {
|
|
65
|
+
enabled: resolvedOptions.errorTranslation?.enabled ?? true,
|
|
66
|
+
defaultLocale: resolvedOptions.errorTranslation?.defaultLocale || "de"
|
|
67
|
+
},
|
|
59
68
|
tus: {
|
|
60
69
|
defaultChunkSize: resolvedOptions.tus?.defaultChunkSize || 5 * 1024 * 1024,
|
|
61
70
|
defaultEndpoint: resolvedOptions.tus?.defaultEndpoint || "/files/upload"
|
|
@@ -66,6 +75,10 @@ const module$1 = defineNuxtModule({
|
|
|
66
75
|
{ name: "useLtAuth", from: resolve("./runtime/composables/auth/use-lt-auth") },
|
|
67
76
|
{ name: "useLtAuthClient", from: resolve("./runtime/composables/use-lt-auth-client") },
|
|
68
77
|
{ name: "ltAuthClient", from: resolve("./runtime/composables/use-lt-auth-client") },
|
|
78
|
+
{
|
|
79
|
+
name: "useLtErrorTranslation",
|
|
80
|
+
from: resolve("./runtime/composables/use-lt-error-translation")
|
|
81
|
+
},
|
|
69
82
|
{ name: "useLtFile", from: resolve("./runtime/composables/use-lt-file") },
|
|
70
83
|
{ name: "useLtTusUpload", from: resolve("./runtime/composables/use-lt-tus-upload") },
|
|
71
84
|
{ name: "useLtShare", from: resolve("./runtime/composables/use-lt-share") },
|
|
@@ -102,16 +115,22 @@ const module$1 = defineNuxtModule({
|
|
|
102
115
|
if (resolvedOptions.auth?.enabled && resolvedOptions.auth?.interceptor?.enabled) {
|
|
103
116
|
addPlugin(resolve("./runtime/plugins/auth-interceptor.client"));
|
|
104
117
|
}
|
|
118
|
+
if (resolvedOptions.errorTranslation?.enabled) {
|
|
119
|
+
addPlugin(resolve("./runtime/plugins/error-translation.client"));
|
|
120
|
+
}
|
|
105
121
|
if (resolvedOptions.i18n?.autoMerge) {
|
|
106
|
-
nuxt.hook(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
nuxt.hook(
|
|
123
|
+
"i18n:registerModule",
|
|
124
|
+
(register) => {
|
|
125
|
+
register({
|
|
126
|
+
langDir: resolve("./runtime/locales"),
|
|
127
|
+
locales: [
|
|
128
|
+
{ code: "en", file: "en.json" },
|
|
129
|
+
{ code: "de", file: "de.json" }
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
);
|
|
115
134
|
}
|
|
116
135
|
nuxt.options.build.transpile.push(resolve("./runtime"));
|
|
117
136
|
nuxt.options.build.transpile.push("tus-js-client");
|
|
@@ -6,7 +6,9 @@ const props = defineProps({
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
|
-
<div
|
|
9
|
+
<div
|
|
10
|
+
:style="`--start-duration: ${props.startDuration}ms; --leave-duration: ${props.leaveDuration}ms;`"
|
|
11
|
+
>
|
|
10
12
|
<Transition
|
|
11
13
|
enter-active-class="transition ease-out duration-[--start-duration]"
|
|
12
14
|
enter-from-class="transform opacity-0"
|
|
@@ -6,7 +6,9 @@ const props = defineProps({
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
9
|
-
<div
|
|
9
|
+
<div
|
|
10
|
+
:style="`--start-duration: ${props.startDuration}ms; --leave-duration: ${props.leaveDuration}ms;`"
|
|
11
|
+
>
|
|
10
12
|
<Transition
|
|
11
13
|
enter-active-class="transition ease-out duration-[--start-duration]"
|
|
12
14
|
enter-from-class="transform opacity-0 scale-95"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - If session cookie works -> use cookies
|
|
10
10
|
* - If cookies fail (401) -> switch to JWT mode
|
|
11
11
|
*/
|
|
12
|
-
import type { UseLtAuthReturn } from
|
|
12
|
+
import type { UseLtAuthReturn } from "../../types/index.js";
|
|
13
13
|
/**
|
|
14
14
|
* Better Auth composable with Cookie/JWT dual-mode authentication
|
|
15
15
|
*
|
|
@@ -81,7 +81,13 @@ export function useLtAuth() {
|
|
|
81
81
|
const maxAge = 60 * 60 * 24 * 7;
|
|
82
82
|
document.cookie = `lt-auth-state=${encodeURIComponent(JSON.stringify(clearedState))}; path=/; max-age=${maxAge}; samesite=lax`;
|
|
83
83
|
document.cookie = `lt-jwt-token=; path=/; max-age=0`;
|
|
84
|
-
const sessionCookieNames = [
|
|
84
|
+
const sessionCookieNames = [
|
|
85
|
+
"better-auth.session_token",
|
|
86
|
+
"better-auth.session",
|
|
87
|
+
"__Secure-better-auth.session_token",
|
|
88
|
+
"session_token",
|
|
89
|
+
"session"
|
|
90
|
+
];
|
|
85
91
|
for (const name of sessionCookieNames) {
|
|
86
92
|
document.cookie = `${name}=; path=/; max-age=0`;
|
|
87
93
|
document.cookie = `${name}=; path=/api; max-age=0`;
|
|
@@ -244,11 +250,17 @@ export function useLtAuth() {
|
|
|
244
250
|
isLoading.value = true;
|
|
245
251
|
try {
|
|
246
252
|
const apiBase = getApiBase();
|
|
247
|
-
const optionsResponse = await fetchWithAuth(
|
|
248
|
-
|
|
249
|
-
|
|
253
|
+
const optionsResponse = await fetchWithAuth(
|
|
254
|
+
`${apiBase}/passkey/generate-authenticate-options`,
|
|
255
|
+
{
|
|
256
|
+
method: "GET"
|
|
257
|
+
}
|
|
258
|
+
);
|
|
250
259
|
if (!optionsResponse.ok) {
|
|
251
|
-
return {
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: t("lt.auth.passkeyError", "Konnte Passkey-Optionen nicht laden")
|
|
263
|
+
};
|
|
252
264
|
}
|
|
253
265
|
const options = await optionsResponse.json();
|
|
254
266
|
const challengeBuffer = ltBase64UrlToUint8Array(options.challenge).buffer;
|
|
@@ -284,7 +296,10 @@ export function useLtAuth() {
|
|
|
284
296
|
});
|
|
285
297
|
const result = await authResponse.json();
|
|
286
298
|
if (!authResponse.ok) {
|
|
287
|
-
return {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: result.message || t("lt.auth.passkeyFailed", "Passkey-Anmeldung fehlgeschlagen")
|
|
302
|
+
};
|
|
288
303
|
}
|
|
289
304
|
if (result.user) {
|
|
290
305
|
setUser(result.user, "cookie");
|
|
@@ -300,9 +315,15 @@ export function useLtAuth() {
|
|
|
300
315
|
return { success: true, user: result.user, session: result.session };
|
|
301
316
|
} catch (err) {
|
|
302
317
|
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
303
|
-
return {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: t("lt.auth.passkeyAborted", "Passkey-Authentifizierung wurde abgebrochen")
|
|
321
|
+
};
|
|
304
322
|
}
|
|
305
|
-
return {
|
|
323
|
+
return {
|
|
324
|
+
success: false,
|
|
325
|
+
error: err instanceof Error ? err.message : t("lt.auth.passkeyFailed", "Passkey-Anmeldung fehlgeschlagen")
|
|
326
|
+
};
|
|
306
327
|
} finally {
|
|
307
328
|
isLoading.value = false;
|
|
308
329
|
}
|
|
@@ -316,7 +337,10 @@ export function useLtAuth() {
|
|
|
316
337
|
});
|
|
317
338
|
if (!optionsResponse.ok) {
|
|
318
339
|
const error = await optionsResponse.json().catch(() => ({}));
|
|
319
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
error: error.message || t("lt.auth.registerOptionsError", "Konnte Registrierungsoptionen nicht laden")
|
|
343
|
+
};
|
|
320
344
|
}
|
|
321
345
|
const options = await optionsResponse.json();
|
|
322
346
|
const challengeBuffer = ltBase64UrlToUint8Array(options.challenge).buffer;
|
|
@@ -341,7 +365,10 @@ export function useLtAuth() {
|
|
|
341
365
|
}
|
|
342
366
|
});
|
|
343
367
|
if (!credential) {
|
|
344
|
-
return {
|
|
368
|
+
return {
|
|
369
|
+
success: false,
|
|
370
|
+
error: t("lt.auth.passkeyCreationAborted", "Passkey-Erstellung abgebrochen")
|
|
371
|
+
};
|
|
345
372
|
}
|
|
346
373
|
const attestationResponse = credential.response;
|
|
347
374
|
const credentialBody = {
|
|
@@ -367,14 +394,23 @@ export function useLtAuth() {
|
|
|
367
394
|
});
|
|
368
395
|
const result = await registerResponse.json();
|
|
369
396
|
if (!registerResponse.ok) {
|
|
370
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
success: false,
|
|
399
|
+
error: result.message || t("lt.auth.passkeyRegisterFailed", "Passkey-Registrierung fehlgeschlagen")
|
|
400
|
+
};
|
|
371
401
|
}
|
|
372
402
|
return { success: true, passkey: result };
|
|
373
403
|
} catch (err) {
|
|
374
404
|
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
375
|
-
return {
|
|
405
|
+
return {
|
|
406
|
+
success: false,
|
|
407
|
+
error: t("lt.auth.passkeyCreationAborted", "Passkey-Erstellung wurde abgebrochen")
|
|
408
|
+
};
|
|
376
409
|
}
|
|
377
|
-
return {
|
|
410
|
+
return {
|
|
411
|
+
success: false,
|
|
412
|
+
error: err instanceof Error ? err.message : t("lt.auth.passkeyRegisterFailed", "Passkey-Registrierung fehlgeschlagen")
|
|
413
|
+
};
|
|
378
414
|
} finally {
|
|
379
415
|
isLoading.value = false;
|
|
380
416
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { useLtAuth } from
|
|
2
|
-
export { useLtAuthClient, ltAuthClient } from
|
|
3
|
-
export { useLtTusUpload } from
|
|
4
|
-
export { useLtFile } from
|
|
5
|
-
export { useLtShare, type UseLtShareReturn } from
|
|
1
|
+
export { useLtAuth } from "./auth/use-lt-auth.js";
|
|
2
|
+
export { useLtAuthClient, ltAuthClient } from "./use-lt-auth-client.js";
|
|
3
|
+
export { useLtTusUpload } from "./use-lt-tus-upload.js";
|
|
4
|
+
export { useLtFile } from "./use-lt-file.js";
|
|
5
|
+
export { useLtShare, type UseLtShareReturn } from "./use-lt-share.js";
|
|
6
|
+
export { useLtErrorTranslation } from "./use-lt-error-translation.js";
|
|
@@ -3,3 +3,4 @@ export { useLtAuthClient, ltAuthClient } from "./use-lt-auth-client.js";
|
|
|
3
3
|
export { useLtTusUpload } from "./use-lt-tus-upload.js";
|
|
4
4
|
export { useLtFile } from "./use-lt-file.js";
|
|
5
5
|
export { useLtShare } from "./use-lt-share.js";
|
|
6
|
+
export { useLtErrorTranslation } from "./use-lt-error-translation.js";
|
|
@@ -16,12 +16,20 @@
|
|
|
16
16
|
* await authClient.passkey.listUserPasskeys();
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
import { type LtAuthClient } from
|
|
19
|
+
import { type LtAuthClient } from "../lib/auth-client.js";
|
|
20
|
+
/**
|
|
21
|
+
* Reset the auth client singleton (useful for testing or config changes)
|
|
22
|
+
*/
|
|
23
|
+
export declare function resetLtAuthClient(): void;
|
|
20
24
|
/**
|
|
21
25
|
* Returns the Better-Auth client singleton
|
|
22
26
|
*
|
|
23
27
|
* The client is created once and reused across all calls.
|
|
24
28
|
* Configuration is read from RuntimeConfig on first call.
|
|
29
|
+
*
|
|
30
|
+
* IMPORTANT: In dev mode, the basePath is automatically prefixed with '/api'
|
|
31
|
+
* to leverage Nuxt's server proxy. This is required for WebAuthn/Passkey
|
|
32
|
+
* to work correctly (same-origin policy).
|
|
25
33
|
*/
|
|
26
34
|
export declare function useLtAuthClient(): LtAuthClient;
|
|
27
35
|
export declare const ltAuthClient: {
|
|
@@ -1,14 +1,38 @@
|
|
|
1
1
|
import { useNuxtApp } from "#imports";
|
|
2
2
|
import { createLtAuthClient } from "../lib/auth-client.js";
|
|
3
3
|
let authClientInstance = null;
|
|
4
|
+
export function resetLtAuthClient() {
|
|
5
|
+
authClientInstance = null;
|
|
6
|
+
}
|
|
7
|
+
function isDevMode() {
|
|
8
|
+
if (import.meta.server) {
|
|
9
|
+
return process.env.NODE_ENV !== "production";
|
|
10
|
+
}
|
|
11
|
+
if (typeof window !== "undefined") {
|
|
12
|
+
const buildId = window.__NUXT__?.config?.app?.buildId;
|
|
13
|
+
if (buildId === "dev") {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
const hostname = window.location?.hostname;
|
|
17
|
+
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
4
23
|
export function useLtAuthClient() {
|
|
5
24
|
if (!authClientInstance) {
|
|
6
25
|
try {
|
|
7
26
|
const nuxtApp = useNuxtApp();
|
|
8
27
|
const config = nuxtApp.$config?.public?.ltExtensions?.auth || {};
|
|
28
|
+
const isDev = isDevMode();
|
|
29
|
+
let basePath = config.basePath || "/iam";
|
|
30
|
+
if (isDev && basePath && !basePath.startsWith("/api")) {
|
|
31
|
+
basePath = `/api${basePath}`;
|
|
32
|
+
}
|
|
9
33
|
authClientInstance = createLtAuthClient({
|
|
10
|
-
baseURL: config.baseURL,
|
|
11
|
-
basePath
|
|
34
|
+
baseURL: isDev ? "" : config.baseURL,
|
|
35
|
+
basePath,
|
|
12
36
|
twoFactorRedirectPath: config.twoFactorRedirectPath,
|
|
13
37
|
enableAdmin: config.enableAdmin,
|
|
14
38
|
enableTwoFactor: config.enableTwoFactor,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Translation Composable
|
|
3
|
+
*
|
|
4
|
+
* Translates backend error codes to user-friendly messages.
|
|
5
|
+
* Works with or without @nuxtjs/i18n.
|
|
6
|
+
*
|
|
7
|
+
* Backend error format: "#LTNS_0100: Unauthorized - User is not logged in"
|
|
8
|
+
* Translations loaded from: GET /api/i18n/errors/:locale
|
|
9
|
+
*/
|
|
10
|
+
import type { UseLtErrorTranslationReturn } from "../types/error.js";
|
|
11
|
+
/**
|
|
12
|
+
* Error Translation composable
|
|
13
|
+
*
|
|
14
|
+
* @returns Functions and state for error translation
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const { translateError, showErrorToast, parseError } = useLtErrorTranslation();
|
|
19
|
+
*
|
|
20
|
+
* // Translate error from API response
|
|
21
|
+
* const message = translateError(error.message);
|
|
22
|
+
* // Input: "#LTNS_0100: Unauthorized - User is not logged in"
|
|
23
|
+
* // Output: "Sie sind nicht angemeldet." (when locale is 'de')
|
|
24
|
+
*
|
|
25
|
+
* // Show error as toast
|
|
26
|
+
* showErrorToast(error, 'Login fehlgeschlagen');
|
|
27
|
+
*
|
|
28
|
+
* // Parse error for custom handling
|
|
29
|
+
* const parsed = parseError(error.message);
|
|
30
|
+
* // { code: 'LTNS_0100', developerMessage: 'Unauthorized...', translatedMessage: '...' }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function useLtErrorTranslation(): UseLtErrorTranslationReturn;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { computed, ref, useState, useNuxtApp, useRuntimeConfig } from "#imports";
|
|
2
|
+
const ERROR_CODE_REGEX = /^#([A-Z_]+_\d+):\s*(.+)$/;
|
|
3
|
+
export function useLtErrorTranslation() {
|
|
4
|
+
const nuxtApp = useNuxtApp();
|
|
5
|
+
const runtimeConfig = useRuntimeConfig();
|
|
6
|
+
const translations = useState(
|
|
7
|
+
"lt-error-translations",
|
|
8
|
+
() => ({})
|
|
9
|
+
);
|
|
10
|
+
const isLoading = ref(false);
|
|
11
|
+
const config = runtimeConfig.public?.ltExtensions?.errorTranslation;
|
|
12
|
+
function t(key, germanFallback) {
|
|
13
|
+
const i18n = nuxtApp.$i18n;
|
|
14
|
+
if (!i18n?.t) {
|
|
15
|
+
return germanFallback;
|
|
16
|
+
}
|
|
17
|
+
return i18n.t(key);
|
|
18
|
+
}
|
|
19
|
+
function detectLocale() {
|
|
20
|
+
const i18n = nuxtApp.$i18n;
|
|
21
|
+
if (i18n?.locale?.value) {
|
|
22
|
+
return i18n.locale.value;
|
|
23
|
+
}
|
|
24
|
+
if (import.meta.client && navigator?.language) {
|
|
25
|
+
const browserLang = navigator.language.split("-")[0] ?? "de";
|
|
26
|
+
if (["de", "en"].includes(browserLang)) {
|
|
27
|
+
return browserLang;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return config?.defaultLocale || "de";
|
|
31
|
+
}
|
|
32
|
+
function getApiBase() {
|
|
33
|
+
return runtimeConfig.public?.ltExtensions?.auth?.baseURL || "";
|
|
34
|
+
}
|
|
35
|
+
async function loadTranslations(locale) {
|
|
36
|
+
const targetLocale = locale || detectLocale();
|
|
37
|
+
if (translations.value[targetLocale]) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (isLoading.value) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
isLoading.value = true;
|
|
44
|
+
try {
|
|
45
|
+
const apiBase = getApiBase();
|
|
46
|
+
const response = await $fetch(
|
|
47
|
+
`${apiBase}/api/i18n/errors/${targetLocale}`
|
|
48
|
+
);
|
|
49
|
+
if (response?.errors) {
|
|
50
|
+
translations.value = {
|
|
51
|
+
...translations.value,
|
|
52
|
+
[targetLocale]: response.errors
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn(`[LtErrorTranslation] Failed to load translations for ${targetLocale}:`, error);
|
|
57
|
+
} finally {
|
|
58
|
+
isLoading.value = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function extractMessage(errorOrMessage) {
|
|
62
|
+
if (typeof errorOrMessage === "string") {
|
|
63
|
+
return errorOrMessage;
|
|
64
|
+
}
|
|
65
|
+
if (errorOrMessage instanceof Error) {
|
|
66
|
+
return errorOrMessage.message;
|
|
67
|
+
}
|
|
68
|
+
if (typeof errorOrMessage === "object" && errorOrMessage !== null) {
|
|
69
|
+
const obj = errorOrMessage;
|
|
70
|
+
return obj.message || obj.error?.message || obj.data?.message || obj.statusMessage || String(obj);
|
|
71
|
+
}
|
|
72
|
+
return String(errorOrMessage);
|
|
73
|
+
}
|
|
74
|
+
function parseError(errorOrMessage) {
|
|
75
|
+
const message = extractMessage(errorOrMessage);
|
|
76
|
+
const match = message.match(ERROR_CODE_REGEX);
|
|
77
|
+
if (match) {
|
|
78
|
+
const code = match[1] || "";
|
|
79
|
+
const developerMessage = match[2] || "";
|
|
80
|
+
const locale = detectLocale();
|
|
81
|
+
const localeTranslations = translations.value[locale] || {};
|
|
82
|
+
const translatedMessage = localeTranslations[code] || developerMessage;
|
|
83
|
+
return {
|
|
84
|
+
code,
|
|
85
|
+
developerMessage,
|
|
86
|
+
translatedMessage
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
code: null,
|
|
91
|
+
developerMessage: message,
|
|
92
|
+
translatedMessage: message
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function translateError(errorOrMessage) {
|
|
96
|
+
const parsed = parseError(errorOrMessage);
|
|
97
|
+
return parsed.translatedMessage;
|
|
98
|
+
}
|
|
99
|
+
function showErrorToast(errorOrMessage, title) {
|
|
100
|
+
if (!import.meta.client) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const parsed = parseError(errorOrMessage);
|
|
104
|
+
try {
|
|
105
|
+
nuxtApp.runWithContext(() => {
|
|
106
|
+
const toastComposable = nuxtApp.useToast || globalThis.useToast;
|
|
107
|
+
if (typeof toastComposable === "function") {
|
|
108
|
+
const toast = toastComposable();
|
|
109
|
+
toast.add({
|
|
110
|
+
color: "error",
|
|
111
|
+
title: title || t("lt.error.title", "Fehler"),
|
|
112
|
+
description: parsed.translatedMessage
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
console.error("[LtErrorTranslation]", parsed.translatedMessage);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
console.error("[LtErrorTranslation]", parsed.translatedMessage);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const currentLocale = computed(() => detectLocale());
|
|
123
|
+
const isLoaded = computed(() => !!translations.value[currentLocale.value]);
|
|
124
|
+
return {
|
|
125
|
+
translateError,
|
|
126
|
+
parseError,
|
|
127
|
+
showErrorToast,
|
|
128
|
+
loadTranslations,
|
|
129
|
+
isLoaded,
|
|
130
|
+
isLoading,
|
|
131
|
+
currentLocale
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -32,7 +32,10 @@ export function useLtShare() {
|
|
|
32
32
|
const toast = useToast();
|
|
33
33
|
toast.add({
|
|
34
34
|
color: "success",
|
|
35
|
-
description: t(
|
|
35
|
+
description: t(
|
|
36
|
+
"lt.share.copiedDescription",
|
|
37
|
+
"Der Link wurde in die Zwischenablage kopiert."
|
|
38
|
+
),
|
|
36
39
|
title: t("lt.share.copied", "Link kopiert")
|
|
37
40
|
});
|
|
38
41
|
} catch {
|
|
@@ -135,7 +135,13 @@ export function useLtTusUpload(defaultOptions = {}) {
|
|
|
135
135
|
file,
|
|
136
136
|
id,
|
|
137
137
|
metadata: defaultConfig.metadata,
|
|
138
|
-
progress: {
|
|
138
|
+
progress: {
|
|
139
|
+
bytesTotal: file.size,
|
|
140
|
+
bytesUploaded: 0,
|
|
141
|
+
percentage: 0,
|
|
142
|
+
remainingTime: 0,
|
|
143
|
+
speed: 0
|
|
144
|
+
},
|
|
139
145
|
status: "idle"
|
|
140
146
|
};
|
|
141
147
|
const newMap = new Map(uploadItems.value);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* SECURITY: Passwords are hashed with SHA256 client-side to prevent
|
|
8
8
|
* plain text password transmission over the network.
|
|
9
9
|
*/
|
|
10
|
-
import type { LtAuthClientConfig } from
|
|
10
|
+
import type { LtAuthClientConfig } from "../types/index.js";
|
|
11
11
|
/**
|
|
12
12
|
* Creates a configured Better-Auth client with password hashing
|
|
13
13
|
*
|
|
@@ -3,9 +3,9 @@ import { adminClient, twoFactorClient } from "better-auth/client/plugins";
|
|
|
3
3
|
import { createAuthClient } from "better-auth/vue";
|
|
4
4
|
import { navigateTo } from "#imports";
|
|
5
5
|
import { ltSha256 } from "../utils/crypto.js";
|
|
6
|
-
import { createLtAuthFetch } from "./auth-state.js";
|
|
6
|
+
import { createLtAuthFetch, isLtDevMode } from "./auth-state.js";
|
|
7
7
|
export function createLtAuthClient(config = {}) {
|
|
8
|
-
const isDev =
|
|
8
|
+
const isDev = isLtDevMode();
|
|
9
9
|
const defaultBaseURL = isDev ? "" : import.meta.env?.VITE_API_URL || process.env.API_URL || "http://localhost:3000";
|
|
10
10
|
const defaultBasePath = isDev ? "/api/iam" : "/iam";
|
|
11
11
|
const {
|
|
@@ -58,8 +58,14 @@ export function createLtAuthClient(config = {}) {
|
|
|
58
58
|
*/
|
|
59
59
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
60
|
changePassword: async (params, options) => {
|
|
61
|
-
const [hashedCurrent, hashedNew] = await Promise.all([
|
|
62
|
-
|
|
61
|
+
const [hashedCurrent, hashedNew] = await Promise.all([
|
|
62
|
+
ltSha256(params.currentPassword),
|
|
63
|
+
ltSha256(params.newPassword)
|
|
64
|
+
]);
|
|
65
|
+
return baseClient.changePassword?.(
|
|
66
|
+
{ currentPassword: hashedCurrent, newPassword: hashedNew },
|
|
67
|
+
options
|
|
68
|
+
);
|
|
63
69
|
},
|
|
64
70
|
/**
|
|
65
71
|
* Reset password with token (new password is hashed before sending)
|
|
@@ -67,7 +73,10 @@ export function createLtAuthClient(config = {}) {
|
|
|
67
73
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
74
|
resetPassword: async (params, options) => {
|
|
69
75
|
const hashedPassword = await ltSha256(params.newPassword);
|
|
70
|
-
return baseClient.resetPassword?.(
|
|
76
|
+
return baseClient.resetPassword?.(
|
|
77
|
+
{ newPassword: hashedPassword, token: params.token },
|
|
78
|
+
options
|
|
79
|
+
);
|
|
71
80
|
},
|
|
72
81
|
// Override signIn to hash password (keep passkey method from plugin)
|
|
73
82
|
signIn: {
|
|
@@ -127,7 +136,10 @@ export function createLtAuthClient(config = {}) {
|
|
|
127
136
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
137
|
generateBackupCodes: async (params, options) => {
|
|
129
138
|
const hashedPassword = await ltSha256(params.password);
|
|
130
|
-
return baseClient.twoFactor.generateBackupCodes(
|
|
139
|
+
return baseClient.twoFactor.generateBackupCodes(
|
|
140
|
+
{ password: hashedPassword },
|
|
141
|
+
options
|
|
142
|
+
);
|
|
131
143
|
},
|
|
132
144
|
/**
|
|
133
145
|
* Verify TOTP code (pass through to base client)
|
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The state is persisted in cookies for SSR compatibility.
|
|
13
13
|
*/
|
|
14
|
-
import type { LtAuthMode } from
|
|
14
|
+
import type { LtAuthMode } from "../types/index.js";
|
|
15
|
+
/**
|
|
16
|
+
* Detects if we're running in development mode at runtime.
|
|
17
|
+
*
|
|
18
|
+
* Note: `import.meta.dev` is evaluated at build time and doesn't work
|
|
19
|
+
* correctly for pre-built modules. This function uses runtime checks instead.
|
|
20
|
+
*
|
|
21
|
+
* @returns true if running in development mode
|
|
22
|
+
*/
|
|
23
|
+
export declare function isLtDevMode(): boolean;
|
|
15
24
|
/**
|
|
16
25
|
* Get the current auth mode from cookie
|
|
17
26
|
*/
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
export function isLtDevMode() {
|
|
2
|
+
if (import.meta.server) {
|
|
3
|
+
return process.env.NODE_ENV !== "production";
|
|
4
|
+
}
|
|
5
|
+
if (typeof window !== "undefined") {
|
|
6
|
+
const buildId = window.__NUXT__?.config?.app?.buildId;
|
|
7
|
+
if (buildId === "dev") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
const hostname = window.location?.hostname;
|
|
11
|
+
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
1
17
|
export function getLtAuthMode() {
|
|
2
18
|
if (import.meta.server) return "cookie";
|
|
3
19
|
try {
|
|
@@ -53,7 +69,7 @@ export function setLtAuthMode(mode) {
|
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
export function getLtApiBase(basePath = "/iam") {
|
|
56
|
-
const isDev =
|
|
72
|
+
const isDev = isLtDevMode();
|
|
57
73
|
if (isDev) {
|
|
58
74
|
return `/api${basePath}`;
|
|
59
75
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { attemptLtJwtSwitch, createLtAuthFetch, getLtApiBase, getLtAuthMode, getLtJwtToken, isLtAuthenticated, ltAuthFetch, setLtAuthMode, setLtJwtToken, } from
|
|
2
|
-
export { createLtAuthClient, type LtAuthClient } from
|
|
1
|
+
export { attemptLtJwtSwitch, createLtAuthFetch, getLtApiBase, getLtAuthMode, getLtJwtToken, isLtAuthenticated, ltAuthFetch, setLtAuthMode, setLtJwtToken, } from "./auth-state.js";
|
|
2
|
+
export { createLtAuthClient, type LtAuthClient } from "./auth-client.js";
|