@meistrari/auth-nuxt 2.3.0 → 2.4.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 +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/composables/application-auth.d.ts +1 -0
- package/dist/runtime/composables/application-auth.js +10 -1
- package/dist/runtime/composables/state.d.ts +2 -6
- package/dist/runtime/helpers/token.d.ts +15 -0
- package/dist/runtime/helpers/token.js +19 -0
- package/dist/runtime/plugins/application-token-refresh.js +15 -24
- package/dist/runtime/server/routes/auth/whoami.d.ts +20 -0
- package/dist/runtime/server/routes/auth/whoami.js +31 -0
- package/dist/runtime/server/utils/require-auth.js +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,6 +8,13 @@ A Nuxt module that provides comprehensive authentication, organization managemen
|
|
|
8
8
|
npm install @meistrari/auth-nuxt
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
Before setting up the SDK, make sure the following are configured in the Auth API:
|
|
14
|
+
|
|
15
|
+
1. **Allowed Origins**: Your application URL (e.g., `https://your-app.com`) must be added to the allowed origins list in the Auth API. This ensures the Auth API accepts requests from your application.
|
|
16
|
+
2. **Allowed Email Domains**: The email domains of users who will access the application must be added to the allowed email domains list in the Auth API. For example, if your users sign in with `@company.com` emails, that domain must be whitelisted.
|
|
17
|
+
|
|
11
18
|
## Setup
|
|
12
19
|
|
|
13
20
|
Add the module to your `nuxt.config.ts`:
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -66,6 +66,11 @@ const module$1 = defineNuxtModule({
|
|
|
66
66
|
handler: resolver.resolve("./runtime/server/routes/auth/switch-organization"),
|
|
67
67
|
method: "post"
|
|
68
68
|
});
|
|
69
|
+
addServerHandler({
|
|
70
|
+
route: "/auth/whoami",
|
|
71
|
+
handler: resolver.resolve("./runtime/server/routes/auth/whoami"),
|
|
72
|
+
method: "get"
|
|
73
|
+
});
|
|
69
74
|
addPlugin(resolver.resolve("./runtime/plugins/application-token-refresh"));
|
|
70
75
|
addPlugin(resolver.resolve("./runtime/plugins/auth-guard"));
|
|
71
76
|
addPlugin(resolver.resolve("./runtime/plugins/directives"));
|
|
@@ -40,6 +40,7 @@ export declare function useTelaApplicationAuth(): {
|
|
|
40
40
|
getAvailableOrganizations: () => Promise<FullOrganization[]>;
|
|
41
41
|
switchOrganization: (organizationId: string) => Promise<void>;
|
|
42
42
|
refreshToken: () => Promise<void>;
|
|
43
|
+
getToken: () => Promise<string | null | undefined>;
|
|
43
44
|
user: import("vue").Ref<{
|
|
44
45
|
id: string;
|
|
45
46
|
createdAt: Date;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { navigateTo, useCookie, useRuntimeConfig } from "#app";
|
|
2
2
|
import { AuthorizationFlowError, isTokenExpired, RefreshTokenExpiredError, UserNotLoggedInError } from "@meistrari/auth-core";
|
|
3
3
|
import { useApplicationSessionState } from "./state.js";
|
|
4
|
+
import { willTokenExpireIn } from "../helpers/token.js";
|
|
4
5
|
const FIFTEEN_MINUTES = 60 * 15;
|
|
5
6
|
const ONE_MINUTE = 60 * 1e3;
|
|
6
7
|
export function useTelaApplicationAuth() {
|
|
@@ -87,6 +88,13 @@ export function useTelaApplicationAuth() {
|
|
|
87
88
|
throw error;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
91
|
+
async function getToken() {
|
|
92
|
+
const shouldRefresh = accessTokenCookie.value ? willTokenExpireIn(accessTokenCookie.value, ONE_MINUTE * 2) : true;
|
|
93
|
+
if (shouldRefresh) {
|
|
94
|
+
await refreshToken();
|
|
95
|
+
}
|
|
96
|
+
return accessTokenCookie.value;
|
|
97
|
+
}
|
|
90
98
|
return {
|
|
91
99
|
...state,
|
|
92
100
|
login,
|
|
@@ -94,6 +102,7 @@ export function useTelaApplicationAuth() {
|
|
|
94
102
|
initSession,
|
|
95
103
|
getAvailableOrganizations,
|
|
96
104
|
switchOrganization,
|
|
97
|
-
refreshToken
|
|
105
|
+
refreshToken,
|
|
106
|
+
getToken
|
|
98
107
|
};
|
|
99
108
|
}
|
|
@@ -105,30 +105,26 @@ export declare function useOrganizationState(): {
|
|
|
105
105
|
role: "org:admin" | "org:member" | "org:reviewer";
|
|
106
106
|
createdAt: Date;
|
|
107
107
|
userId: string;
|
|
108
|
-
teamId?: string | undefined | undefined;
|
|
108
|
+
teamId?: string | undefined | undefined | undefined;
|
|
109
109
|
user: {
|
|
110
110
|
id: string;
|
|
111
111
|
email: string;
|
|
112
112
|
name: string;
|
|
113
113
|
image?: string | undefined;
|
|
114
114
|
};
|
|
115
|
-
deletedAt?: Date | undefined;
|
|
116
|
-
lastActiveAt?: Date | undefined;
|
|
117
115
|
} | null, {
|
|
118
116
|
id: string;
|
|
119
117
|
organizationId: string;
|
|
120
118
|
role: "org:admin" | "org:member" | "org:reviewer";
|
|
121
119
|
createdAt: Date;
|
|
122
120
|
userId: string;
|
|
123
|
-
teamId?: string | undefined | undefined;
|
|
121
|
+
teamId?: string | undefined | undefined | undefined;
|
|
124
122
|
user: {
|
|
125
123
|
id: string;
|
|
126
124
|
email: string;
|
|
127
125
|
name: string;
|
|
128
126
|
image?: string | undefined;
|
|
129
127
|
};
|
|
130
|
-
deletedAt?: Date | undefined;
|
|
131
|
-
lastActiveAt?: Date | undefined;
|
|
132
128
|
} | null>;
|
|
133
129
|
};
|
|
134
130
|
export declare function useApplicationSessionState(): {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a JWT token to extract its expiration time
|
|
3
|
+
*
|
|
4
|
+
* @param token - The JWT token string
|
|
5
|
+
* @returns The expiration timestamp in milliseconds, or null if parsing fails
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseTokenExpiry(token: string): number | null;
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a JWT token will expire within a certain time window, or if it is already expired
|
|
10
|
+
*
|
|
11
|
+
* @param token - The JWT token string
|
|
12
|
+
* @param timeWindow - The time window in milliseconds
|
|
13
|
+
* @returns True if the token will expire within the time window, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
export declare function willTokenExpireIn(token: string, timeWindow: number): boolean | 0;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { decodeJwt } from "jose";
|
|
2
|
+
export function parseTokenExpiry(token) {
|
|
3
|
+
try {
|
|
4
|
+
const payload = decodeJwt(token);
|
|
5
|
+
if (!payload.exp)
|
|
6
|
+
return null;
|
|
7
|
+
return payload.exp * 1e3;
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function willTokenExpireIn(token, timeWindow) {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const expiry = parseTokenExpiry(token);
|
|
15
|
+
if (expiry === null) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return expiry && expiry - timeWindow <= now;
|
|
19
|
+
}
|
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useCookie, useRuntimeConfig } from "#app";
|
|
2
2
|
import { isTokenExpired } from "@meistrari/auth-core";
|
|
3
|
-
import { decodeJwt } from "jose";
|
|
4
3
|
import { useTelaApplicationAuth } from "../composables/application-auth.js";
|
|
5
4
|
import { useApplicationSessionState } from "../composables/state.js";
|
|
6
5
|
import { createNuxtAuthClient } from "../shared.js";
|
|
6
|
+
import { parseTokenExpiry } from "../helpers/token.js";
|
|
7
7
|
const SEVEN_DAYS = 60 * 60 * 24 * 7;
|
|
8
8
|
const FIFTEEN_MINUTES = 60 * 15;
|
|
9
9
|
const TWO_MINUTES = 2 * 60 * 1e3;
|
|
10
|
-
function parseTokenExpiry(token) {
|
|
11
|
-
try {
|
|
12
|
-
const payload = decodeJwt(token);
|
|
13
|
-
if (!payload.exp)
|
|
14
|
-
return null;
|
|
15
|
-
return payload.exp * 1e3;
|
|
16
|
-
} catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function mapOrganization(organization) {
|
|
21
|
-
return {
|
|
22
|
-
name: organization.title,
|
|
23
|
-
id: organization.id,
|
|
24
|
-
createdAt: organization.createdAt,
|
|
25
|
-
logo: organization.avatarUrl,
|
|
26
|
-
metadata: organization.metadata,
|
|
27
|
-
slug: organization.slug ?? ""
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
10
|
export default defineNuxtPlugin({
|
|
31
11
|
name: "tela-application-token-refresh",
|
|
32
12
|
enforce: "post",
|
|
@@ -62,14 +42,14 @@ export default defineNuxtPlugin({
|
|
|
62
42
|
accessTokenCookie.value = accessToken;
|
|
63
43
|
refreshTokenCookie.value = refreshToken2;
|
|
64
44
|
state.user.value = user2;
|
|
65
|
-
state.activeOrganization.value =
|
|
45
|
+
state.activeOrganization.value = organization2;
|
|
66
46
|
return;
|
|
67
47
|
}
|
|
68
48
|
const { user, organization } = await $fetch("/auth/refresh", {
|
|
69
49
|
method: "POST"
|
|
70
50
|
});
|
|
71
51
|
state.user.value = user;
|
|
72
|
-
state.activeOrganization.value =
|
|
52
|
+
state.activeOrganization.value = organization;
|
|
73
53
|
} catch {
|
|
74
54
|
await sdkLogout();
|
|
75
55
|
if (import.meta.client) {
|
|
@@ -107,7 +87,7 @@ export default defineNuxtPlugin({
|
|
|
107
87
|
try {
|
|
108
88
|
const data = await authClient.application.whoAmI(accessTokenCookie.value);
|
|
109
89
|
state.user.value = data.user;
|
|
110
|
-
state.activeOrganization.value =
|
|
90
|
+
state.activeOrganization.value = data.organization;
|
|
111
91
|
} catch (error) {
|
|
112
92
|
console.error("[Tela Auth SDK] Failed to get user and organization:", error.message);
|
|
113
93
|
if (!refreshTokenCookie.value) {
|
|
@@ -135,6 +115,17 @@ export default defineNuxtPlugin({
|
|
|
135
115
|
return;
|
|
136
116
|
}
|
|
137
117
|
if (import.meta.client) {
|
|
118
|
+
if (!state.user.value && accessTokenCookie.value) {
|
|
119
|
+
try {
|
|
120
|
+
const { user, organization } = await $fetch("/auth/whoami", {
|
|
121
|
+
method: "GET"
|
|
122
|
+
});
|
|
123
|
+
state.user.value = user;
|
|
124
|
+
state.activeOrganization.value = organization;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("[Tela Auth SDK] Failed to load user info on client startup:", error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
138
129
|
void scheduleTokenRefresh();
|
|
139
130
|
}
|
|
140
131
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
2
|
+
success: boolean;
|
|
3
|
+
user: {
|
|
4
|
+
id: string;
|
|
5
|
+
createdAt: Date;
|
|
6
|
+
updatedAt: Date;
|
|
7
|
+
email: string;
|
|
8
|
+
emailVerified: boolean;
|
|
9
|
+
name: string;
|
|
10
|
+
image?: string | null | undefined;
|
|
11
|
+
twoFactorEnabled: boolean | null | undefined;
|
|
12
|
+
banned: boolean | null | undefined;
|
|
13
|
+
role?: string | null | undefined;
|
|
14
|
+
banReason?: string | null | undefined;
|
|
15
|
+
banExpires?: Date | null | undefined;
|
|
16
|
+
lastActiveAt?: Date | null | undefined;
|
|
17
|
+
};
|
|
18
|
+
organization: import("@meistrari/auth-core").FullOrganization;
|
|
19
|
+
}>>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,31 @@
|
|
|
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 authClient = createNuxtAuthClient(authConfig.apiUrl, () => null, () => null);
|
|
8
|
+
const accessToken = getCookie(event, "tela-access-token");
|
|
9
|
+
if (!accessToken) {
|
|
10
|
+
throw createError({
|
|
11
|
+
statusCode: 401,
|
|
12
|
+
statusMessage: "Unauthorized",
|
|
13
|
+
message: "No access token found"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const { user, organization } = await authClient.application.whoAmI(accessToken);
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
user,
|
|
21
|
+
organization
|
|
22
|
+
};
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error("[Auth WhoAmI] Failed to get user and organization:", error);
|
|
25
|
+
throw createError({
|
|
26
|
+
statusCode: 401,
|
|
27
|
+
statusMessage: "Unauthorized",
|
|
28
|
+
message: "Failed to get user and organization"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
@@ -3,7 +3,14 @@ import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
|
3
3
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
4
|
export function requireAuth(handler, options) {
|
|
5
5
|
return defineEventHandler(async (event) => {
|
|
6
|
+
const moduleOptions = useRuntimeConfig(event).public.telaAuth;
|
|
7
|
+
if (!moduleOptions.skipServerMiddleware && !options?.roles && import.meta.dev) {
|
|
8
|
+
console.warn("You have enabled the global server middleware, meaning you only need to use requireAuth() on routes that require specific roles.", `Triggered at ${event.path}`);
|
|
9
|
+
}
|
|
6
10
|
if (event.context.auth?.user && event.context.auth?.token) {
|
|
11
|
+
if (import.meta.dev) {
|
|
12
|
+
console.debug("Using existing auth context from global server middleware");
|
|
13
|
+
}
|
|
7
14
|
const user = event.context.auth.user;
|
|
8
15
|
if (options?.roles && !options.roles.includes(user.role ?? "")) {
|
|
9
16
|
throw createError({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/auth-nuxt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@meistrari/auth-core": "1.11.
|
|
34
|
+
"@meistrari/auth-core": "1.11.2",
|
|
35
35
|
"jose": "6.1.3"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|