@meistrari/auth-nuxt 2.1.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -30
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +34 -11
- package/dist/runtime/components/tela-role.vue +6 -6
- package/dist/runtime/composables/application-auth.d.ts +11 -1
- package/dist/runtime/composables/application-auth.js +37 -112
- package/dist/runtime/composables/organization.d.ts +1 -1
- package/dist/runtime/composables/organization.js +2 -1
- package/dist/runtime/composables/state.d.ts +56 -22
- package/dist/runtime/plugins/application-token-refresh.js +19 -22
- package/dist/runtime/plugins/auth-guard.js +4 -4
- package/dist/runtime/plugins/handshake.js +4 -4
- package/dist/runtime/server/middleware/auth.js +2 -2
- package/dist/runtime/server/routes/auth/callback.d.ts +12 -0
- package/dist/runtime/server/routes/auth/callback.js +49 -0
- package/dist/runtime/server/routes/auth/login.d.ts +18 -0
- package/dist/runtime/server/routes/auth/login.js +41 -0
- package/dist/runtime/server/routes/auth/logout.d.ts +4 -0
- package/dist/runtime/server/routes/auth/logout.js +26 -0
- package/dist/runtime/server/routes/auth/organizations.d.ts +16 -0
- package/dist/runtime/server/routes/auth/organizations.js +40 -0
- package/dist/runtime/server/routes/auth/refresh.d.ts +39 -0
- package/dist/runtime/server/routes/auth/refresh.js +54 -0
- package/dist/runtime/server/routes/auth/switch-organization.d.ts +40 -0
- package/dist/runtime/server/routes/auth/switch-organization.js +59 -0
- package/dist/runtime/server/utils/require-auth.js +2 -2
- package/dist/runtime/types/page-meta.d.ts +7 -8
- package/package.json +51 -51
- package/dist/runtime/pages/callback.d.vue.ts +0 -2
- package/dist/runtime/pages/callback.vue +0 -35
- package/dist/runtime/pages/callback.vue.d.ts +0 -2
|
@@ -5,14 +5,14 @@ export default defineNuxtPlugin(() => {
|
|
|
5
5
|
const authConfig = config.public.telaAuth;
|
|
6
6
|
const loginPath = authConfig.application?.loginPath ?? "/login";
|
|
7
7
|
const unauthorizedPath = authConfig.application?.unauthorizedPath ?? "/unauthorized";
|
|
8
|
-
addRouteMiddleware("auth-guard", (to) => {
|
|
8
|
+
addRouteMiddleware("auth-guard", async (to) => {
|
|
9
9
|
const authMeta = to.meta?.auth;
|
|
10
10
|
if (!authMeta || authMeta.required !== true) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
const token = useCookie("tela-access-token");
|
|
14
14
|
if (!token.value) {
|
|
15
|
-
return navigateTo({
|
|
15
|
+
return await navigateTo({
|
|
16
16
|
path: loginPath,
|
|
17
17
|
query: { redirect: to.fullPath }
|
|
18
18
|
});
|
|
@@ -22,14 +22,14 @@ export default defineNuxtPlugin(() => {
|
|
|
22
22
|
const payload = decodeJwt(token.value);
|
|
23
23
|
const userRole = payload.user?.role;
|
|
24
24
|
if (!userRole || !authMeta.roles.includes(userRole)) {
|
|
25
|
-
return navigateTo({
|
|
25
|
+
return await navigateTo({
|
|
26
26
|
path: unauthorizedPath,
|
|
27
27
|
query: { redirect: to.fullPath }
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
} catch (error) {
|
|
31
31
|
console.error("Failed to decode token:", error);
|
|
32
|
-
return navigateTo({
|
|
32
|
+
return await navigateTo({
|
|
33
33
|
path: loginPath,
|
|
34
34
|
query: { redirect: to.fullPath }
|
|
35
35
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { defineNuxtPlugin, useCookie,
|
|
1
|
+
import { defineNuxtPlugin, useCookie, useRequestURL, useRuntimeConfig } from "#app";
|
|
2
|
+
import { navigateTo, useRoute } from "#imports";
|
|
2
3
|
import { watch } from "vue";
|
|
3
4
|
import { useTelaSession } from "../composables/session.js";
|
|
4
5
|
import { createNuxtAuthClient } from "../shared.js";
|
|
5
|
-
import { useRoute, navigateTo } from "#imports";
|
|
6
6
|
export default defineNuxtPlugin({
|
|
7
7
|
name: "tela-auth-handshake",
|
|
8
8
|
enforce: "pre",
|
|
@@ -71,9 +71,9 @@ export default defineNuxtPlugin({
|
|
|
71
71
|
if (token) {
|
|
72
72
|
tokenCookie.value = token;
|
|
73
73
|
}
|
|
74
|
-
tokenRefreshInterval = window.setTimeout(createTokenRefreshInterval, 6e4);
|
|
74
|
+
tokenRefreshInterval = window.setTimeout(() => void createTokenRefreshInterval(), 6e4);
|
|
75
75
|
}
|
|
76
|
-
createTokenRefreshInterval();
|
|
76
|
+
await createTokenRefreshInterval();
|
|
77
77
|
}
|
|
78
78
|
}, { immediate: true });
|
|
79
79
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { extractTokenPayload, validateToken } from "@meistrari/auth-core";
|
|
2
2
|
import { defineEventHandler, getCookie } from "h3";
|
|
3
|
-
import { useRuntimeConfig } from "nitropack/runtime
|
|
3
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
4
|
async function setAuthContext(event) {
|
|
5
5
|
event.context.auth = {
|
|
6
6
|
user: null,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server route handler for OAuth 2.0 PKCE callback
|
|
3
|
+
*
|
|
4
|
+
* This route:
|
|
5
|
+
* 1. Receives the authorization code and state from the OAuth provider
|
|
6
|
+
* 2. Retrieves the code verifier from a secure cookie
|
|
7
|
+
* 3. Exchanges the code for access and refresh tokens
|
|
8
|
+
* 4. Sets authentication cookies
|
|
9
|
+
* 5. Redirects to the home page or login page on error
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineEventHandler, deleteCookie, getCookie, getQuery, sendRedirect, setCookie } from "h3";
|
|
3
|
+
import { createNuxtAuthClient } from "../../../shared.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const query = getQuery(event);
|
|
6
|
+
const code = query.code;
|
|
7
|
+
const state = query.state;
|
|
8
|
+
const config = useRuntimeConfig();
|
|
9
|
+
const authConfig = config.public.telaAuth;
|
|
10
|
+
const loginPath = authConfig.application?.loginPath ?? "/login";
|
|
11
|
+
if (!code || !state) {
|
|
12
|
+
console.error("[Auth Callback] Missing code or state parameter");
|
|
13
|
+
return await sendRedirect(event, `${loginPath}?error=invalid_callback`);
|
|
14
|
+
}
|
|
15
|
+
const codeVerifier = getCookie(event, `tela-verifier-${state}`);
|
|
16
|
+
if (!codeVerifier) {
|
|
17
|
+
console.error("[Auth Callback] Code verifier not found in cookies");
|
|
18
|
+
return await sendRedirect(event, `${loginPath}?error=verifier_missing`);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const authClient = createNuxtAuthClient(
|
|
22
|
+
authConfig.apiUrl,
|
|
23
|
+
() => null,
|
|
24
|
+
() => null
|
|
25
|
+
);
|
|
26
|
+
const { accessToken, refreshToken } = await authClient.application.completeAuthorizationFlow(code, codeVerifier);
|
|
27
|
+
setCookie(event, "tela-access-token", accessToken, {
|
|
28
|
+
secure: !import.meta.dev,
|
|
29
|
+
sameSite: "lax",
|
|
30
|
+
maxAge: 60 * 15,
|
|
31
|
+
// 15 minutes
|
|
32
|
+
path: "/"
|
|
33
|
+
});
|
|
34
|
+
setCookie(event, "tela-refresh-token", refreshToken, {
|
|
35
|
+
secure: !import.meta.dev,
|
|
36
|
+
sameSite: "lax",
|
|
37
|
+
httpOnly: true,
|
|
38
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
39
|
+
// 7 days
|
|
40
|
+
path: "/"
|
|
41
|
+
});
|
|
42
|
+
deleteCookie(event, `tela-verifier-${state}`, { path: "/" });
|
|
43
|
+
return await sendRedirect(event, "/");
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("[Auth Callback] OAuth flow error:", error);
|
|
46
|
+
deleteCookie(event, `tela-verifier-${state}`, { path: "/" });
|
|
47
|
+
return await sendRedirect(event, `${loginPath}?error=auth_failed`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server route handler for OAuth 2.0 PKCE initialization
|
|
3
|
+
*
|
|
4
|
+
* This route:
|
|
5
|
+
* 1. Generates a cryptographically secure state key
|
|
6
|
+
* 2. Generates a code verifier for PKCE
|
|
7
|
+
* 3. Generates a code challenge from the verifier
|
|
8
|
+
* 4. Sets the verifier in a secure HTTP-only cookie
|
|
9
|
+
* 5. Returns only the state and challenge in the response body
|
|
10
|
+
*
|
|
11
|
+
* The client never has access to the verifier - it only receives the challenge
|
|
12
|
+
* and state to use in the authorization URL.
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
15
|
+
state: string;
|
|
16
|
+
challenge: string;
|
|
17
|
+
}>>;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineEventHandler, setCookie } from "h3";
|
|
2
|
+
function hexEncode(bytes) {
|
|
3
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
4
|
+
}
|
|
5
|
+
function generateStateKey() {
|
|
6
|
+
const array = new Uint8Array(8);
|
|
7
|
+
crypto.getRandomValues(array);
|
|
8
|
+
return hexEncode(array);
|
|
9
|
+
}
|
|
10
|
+
function generateCodeVerifier() {
|
|
11
|
+
const array = new Uint8Array(32);
|
|
12
|
+
crypto.getRandomValues(array);
|
|
13
|
+
return base64UrlEncode(array);
|
|
14
|
+
}
|
|
15
|
+
function base64UrlEncode(bytes) {
|
|
16
|
+
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
17
|
+
}
|
|
18
|
+
async function generateCodeChallenge(verifier) {
|
|
19
|
+
const encoder = new TextEncoder();
|
|
20
|
+
const data = encoder.encode(verifier);
|
|
21
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
22
|
+
return base64UrlEncode(new Uint8Array(digest));
|
|
23
|
+
}
|
|
24
|
+
export default defineEventHandler(async (event) => {
|
|
25
|
+
const state = generateStateKey();
|
|
26
|
+
const verifier = generateCodeVerifier();
|
|
27
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
28
|
+
setCookie(event, `tela-verifier-${state}`, verifier, {
|
|
29
|
+
secure: !import.meta.dev,
|
|
30
|
+
sameSite: "lax",
|
|
31
|
+
httpOnly: true,
|
|
32
|
+
// Server-only access
|
|
33
|
+
maxAge: 60 * 10,
|
|
34
|
+
// 10 minutes - just long enough for OAuth flow
|
|
35
|
+
path: "/"
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
state,
|
|
39
|
+
challenge
|
|
40
|
+
};
|
|
41
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineEventHandler, deleteCookie, getCookie } from "h3";
|
|
3
|
+
import { createNuxtAuthClient } from "../../../shared.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const authConfig = useRuntimeConfig().public.telaAuth;
|
|
6
|
+
const refreshToken = getCookie(event, "tela-refresh-token");
|
|
7
|
+
deleteCookie(event, "tela-access-token", { path: "/" });
|
|
8
|
+
deleteCookie(event, "tela-refresh-token", { path: "/" });
|
|
9
|
+
try {
|
|
10
|
+
const authClient = createNuxtAuthClient(
|
|
11
|
+
authConfig.apiUrl,
|
|
12
|
+
() => null
|
|
13
|
+
);
|
|
14
|
+
if (refreshToken) {
|
|
15
|
+
await authClient.application.logout(refreshToken);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
success: true
|
|
19
|
+
};
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error("[Auth Logout] Failed to logout:", error);
|
|
22
|
+
return {
|
|
23
|
+
success: true
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server route handler for listing available organizations
|
|
3
|
+
*
|
|
4
|
+
* This route:
|
|
5
|
+
* 1. Retrieves the access token from the cookie
|
|
6
|
+
* 2. Calls the auth API to get the list of organizations the user can switch to
|
|
7
|
+
* 3. Returns the organizations data
|
|
8
|
+
*
|
|
9
|
+
* Returns only organizations that are entitled to the application and where
|
|
10
|
+
* the entitlement's access rules permit the current user.
|
|
11
|
+
*/
|
|
12
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
organizations: import("@meistrari/auth-core").Organization[];
|
|
15
|
+
}>>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createError, useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineEventHandler, getCookie } from "h3";
|
|
3
|
+
import { createNuxtAuthClient } from "../../../shared.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const config = useRuntimeConfig();
|
|
6
|
+
const authConfig = config.public.telaAuth;
|
|
7
|
+
const accessToken = getCookie(event, "tela-access-token");
|
|
8
|
+
if (!accessToken) {
|
|
9
|
+
throw createError({
|
|
10
|
+
statusCode: 401,
|
|
11
|
+
statusMessage: "Unauthorized",
|
|
12
|
+
message: "No access token found"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (!authConfig.application?.applicationId) {
|
|
16
|
+
throw createError({
|
|
17
|
+
statusCode: 500,
|
|
18
|
+
statusMessage: "Internal Server Error",
|
|
19
|
+
message: "Application ID is not configured"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const authClient = createNuxtAuthClient(
|
|
24
|
+
authConfig.apiUrl,
|
|
25
|
+
() => accessToken
|
|
26
|
+
);
|
|
27
|
+
const { organizations } = await authClient.application.listCandidateOrganizations(authConfig.application.applicationId);
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
organizations
|
|
31
|
+
};
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("[Auth Orgs] Failed to list organizations:", error);
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 500,
|
|
36
|
+
statusMessage: "Internal Server Error",
|
|
37
|
+
message: "Failed to list organizations"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server route handler for token refresh
|
|
3
|
+
*
|
|
4
|
+
* This route:
|
|
5
|
+
* 1. Retrieves the refresh token from the secure httponly cookie
|
|
6
|
+
* 2. Calls the auth API to exchange it for new tokens
|
|
7
|
+
* 3. Sets the new access and refresh tokens in secure cookies
|
|
8
|
+
* 4. Returns the user and organization data
|
|
9
|
+
*
|
|
10
|
+
* This keeps the refresh token secure by never exposing it to the client.
|
|
11
|
+
*/
|
|
12
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
user: {
|
|
15
|
+
id: string;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
updatedAt: Date;
|
|
18
|
+
email: string;
|
|
19
|
+
emailVerified: boolean;
|
|
20
|
+
name: string;
|
|
21
|
+
image?: string | null | undefined;
|
|
22
|
+
twoFactorEnabled: boolean | null | undefined;
|
|
23
|
+
banned: boolean | null | undefined;
|
|
24
|
+
role?: string | null | undefined;
|
|
25
|
+
banReason?: string | null | undefined;
|
|
26
|
+
banExpires?: Date | null | undefined;
|
|
27
|
+
lastActiveAt?: Date | null | undefined;
|
|
28
|
+
};
|
|
29
|
+
organization: {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
slug: string | null;
|
|
33
|
+
avatarUrl: string | null;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
metadata: string | null;
|
|
36
|
+
settings: unknown;
|
|
37
|
+
};
|
|
38
|
+
}>>;
|
|
39
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createError, useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineEventHandler, deleteCookie, getCookie, setCookie } from "h3";
|
|
3
|
+
import { createNuxtAuthClient } from "../../../shared.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const config = useRuntimeConfig();
|
|
6
|
+
const authConfig = config.public.telaAuth;
|
|
7
|
+
const refreshToken = getCookie(event, "tela-refresh-token");
|
|
8
|
+
if (!refreshToken) {
|
|
9
|
+
throw createError({
|
|
10
|
+
statusCode: 401,
|
|
11
|
+
statusMessage: "Unauthorized",
|
|
12
|
+
message: "No refresh token found"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const authClient = createNuxtAuthClient(
|
|
17
|
+
authConfig.apiUrl,
|
|
18
|
+
() => null,
|
|
19
|
+
() => refreshToken
|
|
20
|
+
);
|
|
21
|
+
const { accessToken, refreshToken: newRefreshToken, user, organization } = await authClient.application.refreshAccessToken(refreshToken);
|
|
22
|
+
setCookie(event, "tela-access-token", accessToken, {
|
|
23
|
+
secure: !import.meta.dev,
|
|
24
|
+
sameSite: "lax",
|
|
25
|
+
maxAge: 60 * 15,
|
|
26
|
+
// 15 minutes
|
|
27
|
+
priority: "high",
|
|
28
|
+
path: "/"
|
|
29
|
+
});
|
|
30
|
+
setCookie(event, "tela-refresh-token", newRefreshToken, {
|
|
31
|
+
secure: !import.meta.dev,
|
|
32
|
+
sameSite: "lax",
|
|
33
|
+
httpOnly: true,
|
|
34
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
35
|
+
// 7 days
|
|
36
|
+
priority: "high",
|
|
37
|
+
path: "/"
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
user,
|
|
42
|
+
organization
|
|
43
|
+
};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("[Auth Refresh] Token refresh error:", error);
|
|
46
|
+
deleteCookie(event, "tela-access-token", { path: "/" });
|
|
47
|
+
deleteCookie(event, "tela-refresh-token", { path: "/" });
|
|
48
|
+
throw createError({
|
|
49
|
+
statusCode: 401,
|
|
50
|
+
statusMessage: "Unauthorized",
|
|
51
|
+
message: "Failed to refresh access token"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server route handler for switching organizations
|
|
3
|
+
*
|
|
4
|
+
* This route:
|
|
5
|
+
* 1. Retrieves the access token from the cookie
|
|
6
|
+
* 2. Calls the auth API to switch to the specified organization
|
|
7
|
+
* 3. Sets the new access and refresh tokens in secure cookies
|
|
8
|
+
* 4. Returns the user and organization data
|
|
9
|
+
*
|
|
10
|
+
* The organization must be entitled to the application and the entitlement's
|
|
11
|
+
* access rules must allow the current user.
|
|
12
|
+
*/
|
|
13
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
14
|
+
success: boolean;
|
|
15
|
+
user: {
|
|
16
|
+
id: string;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
updatedAt: Date;
|
|
19
|
+
email: string;
|
|
20
|
+
emailVerified: boolean;
|
|
21
|
+
name: string;
|
|
22
|
+
image?: string | null | undefined;
|
|
23
|
+
twoFactorEnabled: boolean | null | undefined;
|
|
24
|
+
banned: boolean | null | undefined;
|
|
25
|
+
role?: string | null | undefined;
|
|
26
|
+
banReason?: string | null | undefined;
|
|
27
|
+
banExpires?: Date | null | undefined;
|
|
28
|
+
lastActiveAt?: Date | null | undefined;
|
|
29
|
+
};
|
|
30
|
+
organization: {
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
slug: string | null;
|
|
34
|
+
avatarUrl: string | null;
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
metadata: string | null;
|
|
37
|
+
settings: unknown;
|
|
38
|
+
};
|
|
39
|
+
}>>;
|
|
40
|
+
export default _default;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createError, useRuntimeConfig } from "#imports";
|
|
2
|
+
import { defineEventHandler, getCookie, readBody, setCookie } from "h3";
|
|
3
|
+
import { createNuxtAuthClient } from "../../../shared.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const config = useRuntimeConfig();
|
|
6
|
+
const authConfig = config.public.telaAuth;
|
|
7
|
+
const accessToken = getCookie(event, "tela-access-token");
|
|
8
|
+
if (!accessToken) {
|
|
9
|
+
throw createError({
|
|
10
|
+
statusCode: 401,
|
|
11
|
+
statusMessage: "Unauthorized",
|
|
12
|
+
message: "No access token found"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const body = await readBody(event);
|
|
16
|
+
if (!body?.organizationId) {
|
|
17
|
+
throw createError({
|
|
18
|
+
statusCode: 400,
|
|
19
|
+
statusMessage: "Bad Request",
|
|
20
|
+
message: "Organization ID is required"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const authClient = createNuxtAuthClient(
|
|
25
|
+
authConfig.apiUrl,
|
|
26
|
+
() => accessToken
|
|
27
|
+
);
|
|
28
|
+
const { accessToken: newAccessToken, refreshToken: newRefreshToken, user, organization } = await authClient.application.switchOrganization(body.organizationId, accessToken);
|
|
29
|
+
setCookie(event, "tela-access-token", newAccessToken, {
|
|
30
|
+
secure: !import.meta.dev,
|
|
31
|
+
sameSite: "lax",
|
|
32
|
+
maxAge: 60 * 15,
|
|
33
|
+
// 15 minutes
|
|
34
|
+
priority: "high",
|
|
35
|
+
path: "/"
|
|
36
|
+
});
|
|
37
|
+
setCookie(event, "tela-refresh-token", newRefreshToken, {
|
|
38
|
+
secure: !import.meta.dev,
|
|
39
|
+
sameSite: "lax",
|
|
40
|
+
httpOnly: true,
|
|
41
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
42
|
+
// 7 days
|
|
43
|
+
priority: "high",
|
|
44
|
+
path: "/"
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
user,
|
|
49
|
+
organization
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("[Auth Switch Org] Failed to switch organization:", error);
|
|
53
|
+
throw createError({
|
|
54
|
+
statusCode: 500,
|
|
55
|
+
statusMessage: "Internal Server Error",
|
|
56
|
+
message: "Failed to switch organization"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
1
|
import { createError, defineEventHandler, getCookie } from "h3";
|
|
3
2
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
3
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
4
|
export function requireAuth(handler, options) {
|
|
5
5
|
return defineEventHandler(async (event) => {
|
|
6
6
|
if (event.context.auth?.user && event.context.auth?.token) {
|
|
@@ -54,7 +54,7 @@ export function requireAuth(handler, options) {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
event.context.auth = { user: { ...payload.user, email: payload.email }, workspace: payload.workspace, token };
|
|
57
|
-
return handler(event);
|
|
57
|
+
return await handler(event);
|
|
58
58
|
} catch (error) {
|
|
59
59
|
if (error && typeof error === "object" && "statusCode" in error) {
|
|
60
60
|
throw error;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
declare type Roles = 'meistrari:admin' | (string & {})
|
|
2
2
|
declare module '#app' {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
interface PageMeta {
|
|
4
|
+
auth?: {
|
|
5
|
+
required: false
|
|
6
|
+
} | {
|
|
7
|
+
required: true
|
|
8
|
+
roles?: Roles[]
|
|
9
|
+
}
|
|
9
10
|
}
|
|
10
|
-
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// It is always important to ensure you import/export something when augmenting a type
|
|
14
14
|
export { }
|
|
15
|
-
|
package/package.json
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"./server/middleware/auth": {
|
|
11
|
-
"types": "./dist/runtime/server/middleware/auth.d.ts",
|
|
12
|
-
"import": "./dist/runtime/server/middleware/auth.js"
|
|
13
|
-
},
|
|
14
|
-
"./core": {
|
|
15
|
-
"types": "./dist/core.d.mts",
|
|
16
|
-
"import": "./dist/core.mjs"
|
|
17
|
-
}
|
|
2
|
+
"name": "@meistrari/auth-nuxt",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/types.d.mts",
|
|
8
|
+
"import": "./dist/module.mjs"
|
|
18
9
|
},
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
".": [
|
|
23
|
-
"./dist/types.d.mts"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
10
|
+
"./server/middleware/auth": {
|
|
11
|
+
"types": "./dist/runtime/server/middleware/auth.d.ts",
|
|
12
|
+
"import": "./dist/runtime/server/middleware/auth.js"
|
|
26
13
|
},
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"nuxt": "^3.0.0 || ^4.0.0",
|
|
39
|
-
"vue": "^3.0.0"
|
|
40
|
-
},
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@nuxt/devtools": "2.6.3",
|
|
43
|
-
"@nuxt/eslint-config": "1.9.0",
|
|
44
|
-
"@nuxt/kit": "4.0.3",
|
|
45
|
-
"@nuxt/module-builder": "1.0.2",
|
|
46
|
-
"@nuxt/schema": "4.0.3",
|
|
47
|
-
"@nuxt/test-utils": "3.19.2",
|
|
48
|
-
"@types/node": "latest",
|
|
49
|
-
"changelogen": "0.6.2",
|
|
50
|
-
"nuxt": "4.0.3",
|
|
51
|
-
"typescript": "5.9.2",
|
|
52
|
-
"unbuild": "3.6.1",
|
|
53
|
-
"vitest": "3.2.4",
|
|
54
|
-
"vue-tsc": "3.0.6"
|
|
14
|
+
"./core": {
|
|
15
|
+
"types": "./dist/core.d.mts",
|
|
16
|
+
"import": "./dist/core.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/module.mjs",
|
|
20
|
+
"typesVersions": {
|
|
21
|
+
"*": {
|
|
22
|
+
".": [
|
|
23
|
+
"./dist/types.d.mts"
|
|
24
|
+
]
|
|
55
25
|
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@meistrari/auth-core": "1.8.0",
|
|
35
|
+
"jose": "6.1.3"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"nuxt": "^3.0.0 || ^4.0.0",
|
|
39
|
+
"vue": "^3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@nuxt/devtools": "2.6.3",
|
|
43
|
+
"@nuxt/eslint-config": "1.9.0",
|
|
44
|
+
"@nuxt/kit": "4.0.3",
|
|
45
|
+
"@nuxt/module-builder": "1.0.2",
|
|
46
|
+
"@nuxt/schema": "4.0.3",
|
|
47
|
+
"@nuxt/test-utils": "3.19.2",
|
|
48
|
+
"@types/node": "latest",
|
|
49
|
+
"changelogen": "0.6.2",
|
|
50
|
+
"nuxt": "4.0.3",
|
|
51
|
+
"typescript": "5.9.2",
|
|
52
|
+
"unbuild": "3.6.1",
|
|
53
|
+
"vitest": "3.2.4",
|
|
54
|
+
"vue-tsc": "3.0.6"
|
|
55
|
+
}
|
|
56
56
|
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
-
export default _default;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { navigateTo, useRuntimeConfig } from "#app";
|
|
3
|
-
import { onMounted } from "vue";
|
|
4
|
-
import { useTelaApplicationAuth } from "../composables/application-auth";
|
|
5
|
-
const { initSession } = useTelaApplicationAuth();
|
|
6
|
-
const config = useRuntimeConfig();
|
|
7
|
-
const authConfig = config.public.telaAuth;
|
|
8
|
-
const loginPath = authConfig.application?.loginPath ?? "/login";
|
|
9
|
-
onMounted(async () => {
|
|
10
|
-
try {
|
|
11
|
-
await initSession();
|
|
12
|
-
navigateTo("/");
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error("Session initialization failed:", error);
|
|
15
|
-
navigateTo({
|
|
16
|
-
path: loginPath,
|
|
17
|
-
query: { error: "session_failed" }
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<template>
|
|
24
|
-
<div class="callback-screen">
|
|
25
|
-
<div class="callback-square">
|
|
26
|
-
<div class="callback-border-mask">
|
|
27
|
-
<div class="callback-border-traveler" />
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<style scoped>
|
|
34
|
-
.callback-screen{align-items:center;background:#fff;display:flex;height:100dvh;inset:0;justify-content:center;position:fixed;width:100dvw;z-index:9999}.callback-square{background:#fff;border:4px solid #9ca1a9;height:2rem;position:relative;width:2rem}.callback-border-mask{border:4px solid transparent;inset:-4px;mask-clip:padding-box,border-box;-webkit-mask-clip:padding-box,border-box;-webkit-mask-composite:source-in,xor;mask-composite:intersect;-webkit-mask-composite:source-in;mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000);-webkit-mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000);position:absolute}.callback-border-traveler{animation:border-travel .8s ease-in-out infinite;aspect-ratio:1;background:#000;height:1rem;offset-path:rect(0 auto auto 0);position:absolute;width:40px}@keyframes border-travel{0%{offset-distance:0}to{offset-distance:100%}}
|
|
35
|
-
</style>
|