@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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fade with scale transition component
|
|
3
|
+
*
|
|
4
|
+
* Provides a combined fade and scale animation.
|
|
5
|
+
* Elements fade in while scaling up from 95% to 100%,
|
|
6
|
+
* and fade out while scaling down to 95%.
|
|
7
|
+
*
|
|
8
|
+
* Great for modal dialogs, dropdown menus, and pop-ups.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```vue
|
|
12
|
+
* <LtTransitionFadeScale>
|
|
13
|
+
* <div v-if="show">Content fades and scales</div>
|
|
14
|
+
* </LtTransitionFadeScale>
|
|
15
|
+
*
|
|
16
|
+
* <LtTransitionFadeScale :start-duration="200" :leave-duration="150">
|
|
17
|
+
* <div v-if="show">Custom timing</div>
|
|
18
|
+
* </LtTransitionFadeScale>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
type __VLS_Props = {
|
|
22
|
+
/** Duration in ms for the leave animation (default: 100) */
|
|
23
|
+
leaveDuration?: `${number}` | number;
|
|
24
|
+
/** Duration in ms for the enter animation (default: 100) */
|
|
25
|
+
startDuration?: `${number}` | number;
|
|
26
|
+
};
|
|
27
|
+
declare var __VLS_7: {};
|
|
28
|
+
type __VLS_Slots = {} & {
|
|
29
|
+
default?: (props: typeof __VLS_7) => any;
|
|
30
|
+
};
|
|
31
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
32
|
+
leaveDuration: `${number}` | number;
|
|
33
|
+
startDuration: `${number}` | number;
|
|
34
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
38
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
39
|
+
new (): {
|
|
40
|
+
$slots: S;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_7: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_7) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<Transition
|
|
7
|
+
enter-active-class="transition ease-out duration-500"
|
|
8
|
+
enter-from-class="transform translate-x-full"
|
|
9
|
+
enter-to-class="transform translate-x-0"
|
|
10
|
+
leave-active-class="transition ease-in duration-500"
|
|
11
|
+
leave-from-class="transform translate-x-0"
|
|
12
|
+
leave-to-class="transform translate-x-full"
|
|
13
|
+
>
|
|
14
|
+
<slot></slot>
|
|
15
|
+
</Transition>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_7: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_7) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_7: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_7) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<Transition
|
|
7
|
+
enter-active-class="transition ease-out duration-500"
|
|
8
|
+
enter-from-class="transform translate-y-full"
|
|
9
|
+
enter-to-class="transform translate-y-0"
|
|
10
|
+
leave-active-class="transition ease-in duration-500"
|
|
11
|
+
leave-from-class="transform translate-y-0"
|
|
12
|
+
leave-to-class="transform translate-y-full"
|
|
13
|
+
>
|
|
14
|
+
<slot></slot>
|
|
15
|
+
</Transition>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_7: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_7) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_7: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_7) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<Transition
|
|
7
|
+
enter-active-class="transition ease-out duration-500"
|
|
8
|
+
enter-from-class="transform translate-x-[-100%]"
|
|
9
|
+
enter-to-class="transform translate-x-0"
|
|
10
|
+
leave-active-class="transition ease-in duration-500"
|
|
11
|
+
leave-from-class="transform translate-x-0"
|
|
12
|
+
leave-to-class="transform translate-x-[-100%]"
|
|
13
|
+
>
|
|
14
|
+
<slot></slot>
|
|
15
|
+
</Transition>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_7: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_7) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth composable with client-side state management
|
|
3
|
+
*
|
|
4
|
+
* This composable manages auth state using:
|
|
5
|
+
* 1. Primary: Session cookies (more secure, HttpOnly)
|
|
6
|
+
* 2. Fallback: JWT tokens (when cookies are not available/working)
|
|
7
|
+
*
|
|
8
|
+
* The auth mode is automatically detected:
|
|
9
|
+
* - If session cookie works -> use cookies
|
|
10
|
+
* - If cookies fail (401) -> switch to JWT mode
|
|
11
|
+
*/
|
|
12
|
+
import type { UseLtAuthReturn } from '../../types/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Better Auth composable with Cookie/JWT dual-mode authentication
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const { user, isAuthenticated, signIn, signOut } = useLtAuth();
|
|
19
|
+
*
|
|
20
|
+
* // Login
|
|
21
|
+
* await signIn.email({ email, password });
|
|
22
|
+
*
|
|
23
|
+
* // Check auth status
|
|
24
|
+
* if (isAuthenticated.value) {
|
|
25
|
+
* console.log('Logged in as:', user.value?.name);
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* // Logout
|
|
29
|
+
* await signOut();
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function useLtAuth(): UseLtAuthReturn;
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { useNuxtApp, useRuntimeConfig, useCookie, ref, computed, watch } from "#imports";
|
|
2
|
+
import { ltArrayBufferToBase64Url, ltBase64UrlToUint8Array } from "../../utils/crypto.js";
|
|
3
|
+
import { createLtAuthClient } from "../../lib/auth-client.js";
|
|
4
|
+
let _authClient = null;
|
|
5
|
+
function getAuthClient() {
|
|
6
|
+
if (!_authClient) {
|
|
7
|
+
_authClient = createLtAuthClient();
|
|
8
|
+
}
|
|
9
|
+
return _authClient;
|
|
10
|
+
}
|
|
11
|
+
function useTranslation() {
|
|
12
|
+
const nuxtApp = useNuxtApp();
|
|
13
|
+
const i18n = nuxtApp.$i18n;
|
|
14
|
+
return (key, germanFallback) => {
|
|
15
|
+
if (!i18n?.t) {
|
|
16
|
+
return germanFallback;
|
|
17
|
+
}
|
|
18
|
+
return i18n.t(key);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function useLtAuth() {
|
|
22
|
+
const authClient = getAuthClient();
|
|
23
|
+
const t = useTranslation();
|
|
24
|
+
const authState = useCookie("lt-auth-state", {
|
|
25
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
26
|
+
// 7 days
|
|
27
|
+
sameSite: "lax"
|
|
28
|
+
});
|
|
29
|
+
if (import.meta.client) {
|
|
30
|
+
try {
|
|
31
|
+
const cookieStr = document.cookie.split("; ").find((row) => row.startsWith("lt-auth-state="));
|
|
32
|
+
if (cookieStr) {
|
|
33
|
+
const parts = cookieStr.split("=");
|
|
34
|
+
const value = parts.length > 1 ? decodeURIComponent(parts.slice(1).join("=")) : "";
|
|
35
|
+
if (value) {
|
|
36
|
+
const parsed = JSON.parse(value);
|
|
37
|
+
if (parsed?.user && !authState.value?.user) {
|
|
38
|
+
authState.value = parsed;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (import.meta.server && (authState.value === null || authState.value === void 0)) {
|
|
46
|
+
authState.value = { user: null, authMode: "cookie" };
|
|
47
|
+
}
|
|
48
|
+
const jwtToken = useCookie("lt-jwt-token", {
|
|
49
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
50
|
+
// 7 days
|
|
51
|
+
sameSite: "lax"
|
|
52
|
+
});
|
|
53
|
+
const isLoading = ref(false);
|
|
54
|
+
const authMode = computed(() => authState.value?.authMode || "cookie");
|
|
55
|
+
const isJwtMode = computed(() => authMode.value === "jwt");
|
|
56
|
+
const user = computed(() => authState.value?.user ?? null);
|
|
57
|
+
const isAuthenticated = computed(() => !!user.value);
|
|
58
|
+
const isAdmin = computed(() => user.value?.role === "admin");
|
|
59
|
+
const is2FAEnabled = computed(() => user.value?.twoFactorEnabled ?? false);
|
|
60
|
+
function getApiBase() {
|
|
61
|
+
const isDev = import.meta.dev;
|
|
62
|
+
const runtimeConfig = useRuntimeConfig();
|
|
63
|
+
const config = runtimeConfig.public.ltExtensions?.auth;
|
|
64
|
+
const baseURL = config?.baseURL || "http://localhost:3000";
|
|
65
|
+
const basePath = config?.basePath || "/iam";
|
|
66
|
+
return isDev ? `/api${basePath}` : `${baseURL}${basePath}`;
|
|
67
|
+
}
|
|
68
|
+
function setUser(userData, mode = "cookie") {
|
|
69
|
+
const newState = { user: userData, authMode: mode };
|
|
70
|
+
authState.value = newState;
|
|
71
|
+
if (import.meta.client) {
|
|
72
|
+
const maxAge = 60 * 60 * 24 * 7;
|
|
73
|
+
document.cookie = `lt-auth-state=${encodeURIComponent(JSON.stringify(newState))}; path=/; max-age=${maxAge}; samesite=lax`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function clearUser() {
|
|
77
|
+
const clearedState = { user: null, authMode: "cookie" };
|
|
78
|
+
authState.value = clearedState;
|
|
79
|
+
jwtToken.value = null;
|
|
80
|
+
if (import.meta.client) {
|
|
81
|
+
const maxAge = 60 * 60 * 24 * 7;
|
|
82
|
+
document.cookie = `lt-auth-state=${encodeURIComponent(JSON.stringify(clearedState))}; path=/; max-age=${maxAge}; samesite=lax`;
|
|
83
|
+
document.cookie = `lt-jwt-token=; path=/; max-age=0`;
|
|
84
|
+
const sessionCookieNames = ["better-auth.session_token", "better-auth.session", "__Secure-better-auth.session_token", "session_token", "session"];
|
|
85
|
+
for (const name of sessionCookieNames) {
|
|
86
|
+
document.cookie = `${name}=; path=/; max-age=0`;
|
|
87
|
+
document.cookie = `${name}=; path=/api; max-age=0`;
|
|
88
|
+
document.cookie = `${name}=; path=/api/iam; max-age=0`;
|
|
89
|
+
document.cookie = `${name}=; path=/iam; max-age=0`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function switchToJwtMode() {
|
|
94
|
+
try {
|
|
95
|
+
const apiBase = getApiBase();
|
|
96
|
+
const response = await fetch(`${apiBase}/token`, {
|
|
97
|
+
method: "GET",
|
|
98
|
+
credentials: "include"
|
|
99
|
+
});
|
|
100
|
+
if (response.ok) {
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
if (data.token) {
|
|
103
|
+
jwtToken.value = data.token;
|
|
104
|
+
if (authState.value) {
|
|
105
|
+
authState.value = { ...authState.value, authMode: "jwt" };
|
|
106
|
+
}
|
|
107
|
+
console.debug("[LtAuth] Switched to JWT mode");
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function refreshJwtToken() {
|
|
117
|
+
if (!isJwtMode.value || !jwtToken.value) return false;
|
|
118
|
+
return switchToJwtMode();
|
|
119
|
+
}
|
|
120
|
+
async function fetchWithAuth(url, options = {}) {
|
|
121
|
+
const headers = new Headers(options.headers);
|
|
122
|
+
if (isJwtMode.value && jwtToken.value) {
|
|
123
|
+
headers.set("Authorization", `Bearer ${jwtToken.value}`);
|
|
124
|
+
}
|
|
125
|
+
const response = await fetch(url, {
|
|
126
|
+
...options,
|
|
127
|
+
headers,
|
|
128
|
+
// Only include credentials in cookie mode
|
|
129
|
+
credentials: isJwtMode.value ? "omit" : "include"
|
|
130
|
+
});
|
|
131
|
+
if (response.status === 401 && !isJwtMode.value && isAuthenticated.value) {
|
|
132
|
+
console.debug("[LtAuth] Cookie auth failed, attempting JWT fallback...");
|
|
133
|
+
const switched = await switchToJwtMode();
|
|
134
|
+
if (switched) {
|
|
135
|
+
headers.set("Authorization", `Bearer ${jwtToken.value}`);
|
|
136
|
+
return fetch(url, {
|
|
137
|
+
...options,
|
|
138
|
+
headers,
|
|
139
|
+
credentials: "omit"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
async function validateSession() {
|
|
146
|
+
try {
|
|
147
|
+
const session = authClient.useSession();
|
|
148
|
+
if (session.value.isPending) {
|
|
149
|
+
await new Promise((resolve) => {
|
|
150
|
+
const unwatch = watch(
|
|
151
|
+
() => session.value.isPending,
|
|
152
|
+
(isPending) => {
|
|
153
|
+
if (!isPending) {
|
|
154
|
+
unwatch();
|
|
155
|
+
resolve(true);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{ immediate: true }
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (session.value.data?.user) {
|
|
163
|
+
setUser(session.value.data.user, "cookie");
|
|
164
|
+
switchToJwtMode().catch(() => {
|
|
165
|
+
});
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (authState.value?.user) {
|
|
169
|
+
switchToJwtMode().catch(() => {
|
|
170
|
+
});
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.debug("Session validation failed:", error);
|
|
176
|
+
return !!authState.value?.user;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const signIn = {
|
|
180
|
+
...authClient.signIn,
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
email: async (params, options) => {
|
|
183
|
+
isLoading.value = true;
|
|
184
|
+
try {
|
|
185
|
+
const result = await authClient.signIn.email(params, options);
|
|
186
|
+
const resultAny = result;
|
|
187
|
+
const token = resultAny?.token || resultAny?.data?.token;
|
|
188
|
+
const userData = resultAny?.user || resultAny?.data?.user;
|
|
189
|
+
if (token) {
|
|
190
|
+
jwtToken.value = token;
|
|
191
|
+
if (userData) {
|
|
192
|
+
setUser(userData, "jwt");
|
|
193
|
+
}
|
|
194
|
+
console.debug("[LtAuth] JWT token received from login response");
|
|
195
|
+
} else if (userData) {
|
|
196
|
+
setUser(userData, "cookie");
|
|
197
|
+
switchToJwtMode().catch(() => {
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
} finally {
|
|
202
|
+
isLoading.value = false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const signUp = {
|
|
207
|
+
...authClient.signUp,
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
+
email: async (params, options) => {
|
|
210
|
+
isLoading.value = true;
|
|
211
|
+
try {
|
|
212
|
+
const result = await authClient.signUp.email(params, options);
|
|
213
|
+
const resultAny = result;
|
|
214
|
+
const token = resultAny?.token || resultAny?.data?.token;
|
|
215
|
+
const userData = resultAny?.user || resultAny?.data?.user;
|
|
216
|
+
if (token) {
|
|
217
|
+
jwtToken.value = token;
|
|
218
|
+
if (userData) {
|
|
219
|
+
setUser(userData, "jwt");
|
|
220
|
+
}
|
|
221
|
+
console.debug("[LtAuth] JWT token received from signup response");
|
|
222
|
+
} else if (userData) {
|
|
223
|
+
setUser(userData, "cookie");
|
|
224
|
+
switchToJwtMode().catch(() => {
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
} finally {
|
|
229
|
+
isLoading.value = false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const signOut = async (options) => {
|
|
234
|
+
isLoading.value = true;
|
|
235
|
+
try {
|
|
236
|
+
const result = await authClient.signOut(options);
|
|
237
|
+
clearUser();
|
|
238
|
+
return result;
|
|
239
|
+
} finally {
|
|
240
|
+
isLoading.value = false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
async function authenticateWithPasskey() {
|
|
244
|
+
isLoading.value = true;
|
|
245
|
+
try {
|
|
246
|
+
const apiBase = getApiBase();
|
|
247
|
+
const optionsResponse = await fetchWithAuth(`${apiBase}/passkey/generate-authenticate-options`, {
|
|
248
|
+
method: "GET"
|
|
249
|
+
});
|
|
250
|
+
if (!optionsResponse.ok) {
|
|
251
|
+
return { success: false, error: t("lt.auth.passkeyError", "Konnte Passkey-Optionen nicht laden") };
|
|
252
|
+
}
|
|
253
|
+
const options = await optionsResponse.json();
|
|
254
|
+
const challengeBuffer = ltBase64UrlToUint8Array(options.challenge).buffer;
|
|
255
|
+
const credential = await navigator.credentials.get({
|
|
256
|
+
publicKey: {
|
|
257
|
+
challenge: challengeBuffer,
|
|
258
|
+
rpId: options.rpId,
|
|
259
|
+
allowCredentials: options.allowCredentials || [],
|
|
260
|
+
userVerification: options.userVerification,
|
|
261
|
+
timeout: options.timeout
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
if (!credential) {
|
|
265
|
+
return { success: false, error: t("lt.auth.noPasskeySelected", "Kein Passkey ausgew\xE4hlt") };
|
|
266
|
+
}
|
|
267
|
+
const response = credential.response;
|
|
268
|
+
const credentialBody = {
|
|
269
|
+
id: credential.id,
|
|
270
|
+
rawId: ltArrayBufferToBase64Url(credential.rawId),
|
|
271
|
+
type: credential.type,
|
|
272
|
+
response: {
|
|
273
|
+
authenticatorData: ltArrayBufferToBase64Url(response.authenticatorData),
|
|
274
|
+
clientDataJSON: ltArrayBufferToBase64Url(response.clientDataJSON),
|
|
275
|
+
signature: ltArrayBufferToBase64Url(response.signature),
|
|
276
|
+
userHandle: response.userHandle ? ltArrayBufferToBase64Url(response.userHandle) : null
|
|
277
|
+
},
|
|
278
|
+
clientExtensionResults: credential.getClientExtensionResults?.() || {}
|
|
279
|
+
};
|
|
280
|
+
const authResponse = await fetchWithAuth(`${apiBase}/passkey/verify-authentication`, {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: { "Content-Type": "application/json" },
|
|
283
|
+
body: JSON.stringify({ challengeId: options.challengeId, response: credentialBody })
|
|
284
|
+
});
|
|
285
|
+
const result = await authResponse.json();
|
|
286
|
+
if (!authResponse.ok) {
|
|
287
|
+
return { success: false, error: result.message || t("lt.auth.passkeyFailed", "Passkey-Anmeldung fehlgeschlagen") };
|
|
288
|
+
}
|
|
289
|
+
if (result.user) {
|
|
290
|
+
setUser(result.user, "cookie");
|
|
291
|
+
switchToJwtMode().catch(() => {
|
|
292
|
+
});
|
|
293
|
+
} else if (result.session?.token) {
|
|
294
|
+
jwtToken.value = result.session.token;
|
|
295
|
+
if (authState.value) {
|
|
296
|
+
authState.value = { ...authState.value, authMode: "jwt" };
|
|
297
|
+
}
|
|
298
|
+
console.debug("[LtAuth] Passkey: Stored session token as JWT");
|
|
299
|
+
}
|
|
300
|
+
return { success: true, user: result.user, session: result.session };
|
|
301
|
+
} catch (err) {
|
|
302
|
+
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
303
|
+
return { success: false, error: t("lt.auth.passkeyAborted", "Passkey-Authentifizierung wurde abgebrochen") };
|
|
304
|
+
}
|
|
305
|
+
return { success: false, error: err instanceof Error ? err.message : t("lt.auth.passkeyFailed", "Passkey-Anmeldung fehlgeschlagen") };
|
|
306
|
+
} finally {
|
|
307
|
+
isLoading.value = false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function registerPasskey(name) {
|
|
311
|
+
isLoading.value = true;
|
|
312
|
+
try {
|
|
313
|
+
const apiBase = getApiBase();
|
|
314
|
+
const optionsResponse = await fetchWithAuth(`${apiBase}/passkey/generate-register-options`, {
|
|
315
|
+
method: "GET"
|
|
316
|
+
});
|
|
317
|
+
if (!optionsResponse.ok) {
|
|
318
|
+
const error = await optionsResponse.json().catch(() => ({}));
|
|
319
|
+
return { success: false, error: error.message || t("lt.auth.registerOptionsError", "Konnte Registrierungsoptionen nicht laden") };
|
|
320
|
+
}
|
|
321
|
+
const options = await optionsResponse.json();
|
|
322
|
+
const challengeBuffer = ltBase64UrlToUint8Array(options.challenge).buffer;
|
|
323
|
+
const userIdBuffer = ltBase64UrlToUint8Array(options.user.id).buffer;
|
|
324
|
+
const credential = await navigator.credentials.create({
|
|
325
|
+
publicKey: {
|
|
326
|
+
challenge: challengeBuffer,
|
|
327
|
+
rp: options.rp,
|
|
328
|
+
user: {
|
|
329
|
+
...options.user,
|
|
330
|
+
id: userIdBuffer
|
|
331
|
+
},
|
|
332
|
+
pubKeyCredParams: options.pubKeyCredParams,
|
|
333
|
+
timeout: options.timeout,
|
|
334
|
+
attestation: options.attestation,
|
|
335
|
+
authenticatorSelection: options.authenticatorSelection,
|
|
336
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
337
|
+
excludeCredentials: (options.excludeCredentials || []).map((cred) => ({
|
|
338
|
+
...cred,
|
|
339
|
+
id: ltBase64UrlToUint8Array(cred.id).buffer
|
|
340
|
+
}))
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
if (!credential) {
|
|
344
|
+
return { success: false, error: t("lt.auth.passkeyCreationAborted", "Passkey-Erstellung abgebrochen") };
|
|
345
|
+
}
|
|
346
|
+
const attestationResponse = credential.response;
|
|
347
|
+
const credentialBody = {
|
|
348
|
+
name,
|
|
349
|
+
// Include challengeId for JWT mode (database challenge storage)
|
|
350
|
+
challengeId: options.challengeId,
|
|
351
|
+
response: {
|
|
352
|
+
id: credential.id,
|
|
353
|
+
rawId: ltArrayBufferToBase64Url(credential.rawId),
|
|
354
|
+
type: credential.type,
|
|
355
|
+
response: {
|
|
356
|
+
attestationObject: ltArrayBufferToBase64Url(attestationResponse.attestationObject),
|
|
357
|
+
clientDataJSON: ltArrayBufferToBase64Url(attestationResponse.clientDataJSON),
|
|
358
|
+
transports: attestationResponse.getTransports?.() || []
|
|
359
|
+
},
|
|
360
|
+
clientExtensionResults: credential.getClientExtensionResults?.() || {}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
const registerResponse = await fetchWithAuth(`${apiBase}/passkey/verify-registration`, {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: { "Content-Type": "application/json" },
|
|
366
|
+
body: JSON.stringify(credentialBody)
|
|
367
|
+
});
|
|
368
|
+
const result = await registerResponse.json();
|
|
369
|
+
if (!registerResponse.ok) {
|
|
370
|
+
return { success: false, error: result.message || t("lt.auth.passkeyRegisterFailed", "Passkey-Registrierung fehlgeschlagen") };
|
|
371
|
+
}
|
|
372
|
+
return { success: true, passkey: result };
|
|
373
|
+
} catch (err) {
|
|
374
|
+
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
375
|
+
return { success: false, error: t("lt.auth.passkeyCreationAborted", "Passkey-Erstellung wurde abgebrochen") };
|
|
376
|
+
}
|
|
377
|
+
return { success: false, error: err instanceof Error ? err.message : t("lt.auth.passkeyRegisterFailed", "Passkey-Registrierung fehlgeschlagen") };
|
|
378
|
+
} finally {
|
|
379
|
+
isLoading.value = false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
// Auth state
|
|
384
|
+
authMode,
|
|
385
|
+
isAuthenticated,
|
|
386
|
+
isJwtMode,
|
|
387
|
+
isLoading: computed(() => isLoading.value),
|
|
388
|
+
user,
|
|
389
|
+
// User properties
|
|
390
|
+
is2FAEnabled,
|
|
391
|
+
isAdmin,
|
|
392
|
+
// Auth actions
|
|
393
|
+
authenticateWithPasskey,
|
|
394
|
+
changePassword: authClient.changePassword,
|
|
395
|
+
clearUser,
|
|
396
|
+
registerPasskey,
|
|
397
|
+
setUser,
|
|
398
|
+
signIn,
|
|
399
|
+
signOut,
|
|
400
|
+
signUp,
|
|
401
|
+
validateSession,
|
|
402
|
+
// JWT management
|
|
403
|
+
fetchWithAuth,
|
|
404
|
+
jwtToken,
|
|
405
|
+
refreshJwtToken,
|
|
406
|
+
switchToJwtMode,
|
|
407
|
+
// Better Auth client passthrough
|
|
408
|
+
passkey: authClient.passkey,
|
|
409
|
+
twoFactor: authClient.twoFactor
|
|
410
|
+
};
|
|
411
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
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';
|
|
@@ -0,0 +1,5 @@
|
|
|
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 } from "./use-lt-share.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Client Composable
|
|
3
|
+
*
|
|
4
|
+
* Provides access to the Better-Auth client singleton.
|
|
5
|
+
* Use this when you need direct access to the auth client API
|
|
6
|
+
* (e.g., for 2FA management, passkey operations).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const authClient = useLtAuthClient();
|
|
11
|
+
*
|
|
12
|
+
* // Access 2FA methods
|
|
13
|
+
* await authClient.twoFactor.enable({ password: 'test' });
|
|
14
|
+
*
|
|
15
|
+
* // Access passkey methods
|
|
16
|
+
* await authClient.passkey.listUserPasskeys();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import { type LtAuthClient } from '../lib/auth-client.js';
|
|
20
|
+
/**
|
|
21
|
+
* Returns the Better-Auth client singleton
|
|
22
|
+
*
|
|
23
|
+
* The client is created once and reused across all calls.
|
|
24
|
+
* Configuration is read from RuntimeConfig on first call.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useLtAuthClient(): LtAuthClient;
|
|
27
|
+
export declare const ltAuthClient: {
|
|
28
|
+
readonly instance: LtAuthClient;
|
|
29
|
+
};
|