@peterbud/nuxt-aegis 1.1.0-alpha.2 → 1.1.0-alpha.3
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/dist/module.json +1 -1
- package/dist/module.mjs +7 -6
- package/dist/runtime/app/composables/useAuth.js +1 -6
- package/dist/runtime/app/plugins/api.client.js +79 -76
- package/dist/runtime/app/plugins/api.server.js +1 -1
- package/dist/runtime/app/plugins/ssr-state.server.js +12 -9
- package/dist/runtime/server/middleware/auth.js +32 -24
- package/dist/runtime/server/routes/refresh.post.js +24 -15
- package/dist/runtime/types/refresh.d.ts +2 -0
- package/package.json +8 -8
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -21,6 +21,8 @@ const module$1 = defineNuxtModule({
|
|
|
21
21
|
tokenRefresh: {
|
|
22
22
|
enabled: true,
|
|
23
23
|
automaticRefresh: true,
|
|
24
|
+
rotationEnabled: true,
|
|
25
|
+
// Enable refresh token rotation by default for security
|
|
24
26
|
cookie: {
|
|
25
27
|
cookieName: "nuxt-aegis-refresh",
|
|
26
28
|
maxAge: 60 * 60 * 24 * 7,
|
|
@@ -95,18 +97,18 @@ const module$1 = defineNuxtModule({
|
|
|
95
97
|
nuxtAegis: options
|
|
96
98
|
});
|
|
97
99
|
const runtimeConfig = nuxt.options.runtimeConfig;
|
|
100
|
+
const logger = useLogger("nuxt-aegis");
|
|
98
101
|
if (options.enableSSR && nuxt.options.ssr === false) {
|
|
99
|
-
|
|
100
|
-
logger2.warn(
|
|
102
|
+
logger.warn(
|
|
101
103
|
"nuxtAegis.enableSSR is true but Nuxt SSR is disabled. SSR authentication will not work. Set ssr: true in nuxt.config.ts or disable enableSSR."
|
|
102
104
|
);
|
|
103
105
|
}
|
|
104
106
|
const resolver = createResolver(import.meta.url);
|
|
105
107
|
if (options.tokenRefresh?.encryption?.enabled) {
|
|
106
|
-
const encryptionKey = options.tokenRefresh.encryption.key
|
|
108
|
+
const encryptionKey = options.tokenRefresh.encryption.key;
|
|
107
109
|
if (!encryptionKey) {
|
|
108
|
-
|
|
109
|
-
"[Nuxt Aegis] Encryption is enabled but no encryption key is configured. Please set tokenRefresh.encryption.key in nuxt.config.ts or
|
|
110
|
+
logger.warn(
|
|
111
|
+
"[Nuxt Aegis] Encryption is enabled but no encryption key is configured. The application will fail at runtime if encryption is attempted. Please set tokenRefresh.encryption.key in nuxt.config.ts or in the appropriate environment variable."
|
|
110
112
|
);
|
|
111
113
|
}
|
|
112
114
|
if (!runtimeConfig.nuxtAegis) {
|
|
@@ -211,7 +213,6 @@ const module$1 = defineNuxtModule({
|
|
|
211
213
|
addPlugin(resolver.resolve("./runtime/app/plugins/api.server"));
|
|
212
214
|
addPlugin(resolver.resolve("./runtime/app/plugins/ssr-state.server"));
|
|
213
215
|
}
|
|
214
|
-
const logger = useLogger("nuxt-aegis");
|
|
215
216
|
if (options.clientMiddleware?.enabled) {
|
|
216
217
|
const cm = options.clientMiddleware;
|
|
217
218
|
if (cm.global) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRuntimeConfig, navigateTo, useState, computed } from "#imports";
|
|
2
|
-
import { clearAccessToken } from "../utils/tokenStore.js";
|
|
2
|
+
import { clearAccessToken, setAccessToken, getAccessToken } from "../utils/tokenStore.js";
|
|
3
3
|
import { createLogger } from "../utils/logger.js";
|
|
4
4
|
import { validateRedirectPath } from "../utils/redirectValidation.js";
|
|
5
5
|
import { filterTimeSensitiveClaims } from "../utils/tokenUtils.js";
|
|
@@ -39,7 +39,6 @@ export function useAuth() {
|
|
|
39
39
|
method: "POST"
|
|
40
40
|
});
|
|
41
41
|
if (response?.accessToken) {
|
|
42
|
-
const { setAccessToken } = await import("../utils/tokenStore.js");
|
|
43
42
|
setAccessToken(response.accessToken);
|
|
44
43
|
const tokenParts = response.accessToken.split(".");
|
|
45
44
|
if (tokenParts[1]) {
|
|
@@ -95,7 +94,6 @@ export function useAuth() {
|
|
|
95
94
|
authState.value.error = null;
|
|
96
95
|
authState.value.isLoading = true;
|
|
97
96
|
logger.debug("Starting impersonation...", { targetUserId });
|
|
98
|
-
const { getAccessToken } = await import("../utils/tokenStore.js");
|
|
99
97
|
const currentToken = getAccessToken();
|
|
100
98
|
if (!currentToken) {
|
|
101
99
|
throw new Error("Must be authenticated to impersonate");
|
|
@@ -111,7 +109,6 @@ export function useAuth() {
|
|
|
111
109
|
}
|
|
112
110
|
});
|
|
113
111
|
if (response?.accessToken) {
|
|
114
|
-
const { setAccessToken } = await import("../utils/tokenStore.js");
|
|
115
112
|
setAccessToken(response.accessToken);
|
|
116
113
|
const tokenParts = response.accessToken.split(".");
|
|
117
114
|
if (tokenParts[1]) {
|
|
@@ -138,7 +135,6 @@ export function useAuth() {
|
|
|
138
135
|
authState.value.error = null;
|
|
139
136
|
authState.value.isLoading = true;
|
|
140
137
|
logger.debug("Stopping impersonation...");
|
|
141
|
-
const { getAccessToken } = await import("../utils/tokenStore.js");
|
|
142
138
|
const currentToken = getAccessToken();
|
|
143
139
|
if (!currentToken) {
|
|
144
140
|
throw new Error("No active session");
|
|
@@ -150,7 +146,6 @@ export function useAuth() {
|
|
|
150
146
|
}
|
|
151
147
|
});
|
|
152
148
|
if (response?.accessToken) {
|
|
153
|
-
const { setAccessToken } = await import("../utils/tokenStore.js");
|
|
154
149
|
setAccessToken(response.accessToken);
|
|
155
150
|
const tokenParts = response.accessToken.split(".");
|
|
156
151
|
if (tokenParts[1]) {
|
|
@@ -5,88 +5,91 @@ import { isRouteMatch } from "../utils/routeMatching.js";
|
|
|
5
5
|
import { createLogger } from "../utils/logger.js";
|
|
6
6
|
import { validateRedirectPath } from "../utils/redirectValidation.js";
|
|
7
7
|
const logger = createLogger("API:Client");
|
|
8
|
-
export default defineNuxtPlugin(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return refreshPromise;
|
|
31
|
-
}
|
|
32
|
-
const api = $fetch.create({
|
|
33
|
-
onRequest({ options }) {
|
|
34
|
-
const token = getAccessToken();
|
|
35
|
-
if (token) {
|
|
36
|
-
options.headers.set("Authorization", `Bearer ${token}`);
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
async onResponseError({ options, response }) {
|
|
40
|
-
if (response.status === 401 && autoRefreshEnabled) {
|
|
41
|
-
const newToken = await attemptTokenRefresh();
|
|
42
|
-
if (newToken) {
|
|
43
|
-
options.headers.set("Authorization", `Bearer ${newToken}`);
|
|
44
|
-
return;
|
|
8
|
+
export default defineNuxtPlugin({
|
|
9
|
+
name: "nuxt-aegis-api-client",
|
|
10
|
+
async setup(nuxtApp) {
|
|
11
|
+
let isRefreshing = false;
|
|
12
|
+
let refreshPromise = null;
|
|
13
|
+
let isInitialized = false;
|
|
14
|
+
const autoRefreshEnabled = nuxtApp.$config.public.nuxtAegis.tokenRefresh.automaticRefresh ?? true;
|
|
15
|
+
async function attemptTokenRefresh() {
|
|
16
|
+
if (isRefreshing) return refreshPromise;
|
|
17
|
+
logger.debug("Attempting token refresh...");
|
|
18
|
+
isRefreshing = true;
|
|
19
|
+
refreshPromise = nuxtApp.runWithContext(async () => {
|
|
20
|
+
const auth = useAuth();
|
|
21
|
+
try {
|
|
22
|
+
await auth.refresh();
|
|
23
|
+
return getAccessToken();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.error("Token refresh failed:", error);
|
|
26
|
+
return null;
|
|
27
|
+
} finally {
|
|
28
|
+
isRefreshing = false;
|
|
29
|
+
refreshPromise = null;
|
|
45
30
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const errorUrl = validateRedirectPath(config.public.nuxtAegis.redirect?.error || "/");
|
|
49
|
-
await nuxtApp.runWithContext(() => navigateTo(`${errorUrl}?error=token_refresh_failed&error_description=${encodeURIComponent("Session expired. Please log in again.")}`));
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
retry: autoRefreshEnabled ? 1 : 0,
|
|
53
|
-
retryStatusCodes: [401]
|
|
54
|
-
});
|
|
55
|
-
const result = {
|
|
56
|
-
provide: {
|
|
57
|
-
api
|
|
31
|
+
});
|
|
32
|
+
return refreshPromise;
|
|
58
33
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
await nuxtApp.runWithContext(async () => {
|
|
65
|
-
const callbackPath = nuxtApp.$config.public.nuxtAegis.callbackPath;
|
|
66
|
-
if (typeof window !== "undefined" && window.location.pathname === callbackPath) {
|
|
67
|
-
logger.debug("On auth callback page, skipping refresh");
|
|
68
|
-
return;
|
|
34
|
+
const api = $fetch.create({
|
|
35
|
+
onRequest({ options }) {
|
|
36
|
+
const token = getAccessToken();
|
|
37
|
+
if (token) {
|
|
38
|
+
options.headers.set("Authorization", `Bearer ${token}`);
|
|
69
39
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
40
|
+
},
|
|
41
|
+
async onResponseError({ options, response }) {
|
|
42
|
+
if (response.status === 401 && autoRefreshEnabled) {
|
|
43
|
+
const newToken = await attemptTokenRefresh();
|
|
44
|
+
if (newToken) {
|
|
45
|
+
options.headers.set("Authorization", `Bearer ${newToken}`);
|
|
75
46
|
return;
|
|
76
47
|
}
|
|
48
|
+
clearAccessToken();
|
|
49
|
+
const config = useRuntimeConfig();
|
|
50
|
+
const errorUrl = validateRedirectPath(config.public.nuxtAegis.redirect?.error || "/");
|
|
51
|
+
await nuxtApp.runWithContext(() => navigateTo(`${errorUrl}?error=token_refresh_failed&error_description=${encodeURIComponent("Session expired. Please log in again.")}`));
|
|
77
52
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
await useAuth().refresh();
|
|
85
|
-
} catch {
|
|
86
|
-
logger.debug("No valid refresh token found on startup");
|
|
87
|
-
}
|
|
88
|
-
});
|
|
53
|
+
},
|
|
54
|
+
retry: autoRefreshEnabled ? 1 : 0,
|
|
55
|
+
retryStatusCodes: [401]
|
|
89
56
|
});
|
|
57
|
+
const result = {
|
|
58
|
+
provide: {
|
|
59
|
+
api
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (!isInitialized && autoRefreshEnabled) {
|
|
63
|
+
isInitialized = true;
|
|
64
|
+
logger.debug("Initializing auth state on startup...");
|
|
65
|
+
nuxtApp.hook("app:mounted", async () => {
|
|
66
|
+
await nuxtApp.runWithContext(async () => {
|
|
67
|
+
const callbackPath = nuxtApp.$config.public.nuxtAegis.callbackPath;
|
|
68
|
+
if (typeof window !== "undefined" && window.location.pathname === callbackPath) {
|
|
69
|
+
logger.debug("On auth callback page, skipping refresh");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (typeof window !== "undefined") {
|
|
73
|
+
const publicRoutes = nuxtApp.$config.public.nuxtAegis?.clientMiddleware?.publicRoutes || [];
|
|
74
|
+
const currentPath = window.location.pathname;
|
|
75
|
+
if (isRouteMatch(currentPath, publicRoutes)) {
|
|
76
|
+
logger.debug("On public route, skipping refresh on startup");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const currentToken = getAccessToken();
|
|
81
|
+
if (currentToken) {
|
|
82
|
+
logger.debug("Access token already present, skipping refresh on startup");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
await useAuth().refresh();
|
|
87
|
+
} catch {
|
|
88
|
+
logger.debug("No valid refresh token found on startup");
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
90
94
|
}
|
|
91
|
-
return result;
|
|
92
95
|
});
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useState, useRequestEvent } from "#app";
|
|
2
2
|
import { filterTimeSensitiveClaims } from "../utils/tokenUtils.js";
|
|
3
|
-
export default defineNuxtPlugin(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
user
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
export default defineNuxtPlugin({
|
|
4
|
+
name: "nuxt-aegis-ssr-state",
|
|
5
|
+
async setup() {
|
|
6
|
+
const event = useRequestEvent();
|
|
7
|
+
if (event?.context.user) {
|
|
8
|
+
const user = event.context.user;
|
|
9
|
+
useState("auth-state", () => ({
|
|
10
|
+
user: filterTimeSensitiveClaims(user),
|
|
11
|
+
isLoading: false,
|
|
12
|
+
error: null
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
12
15
|
}
|
|
13
16
|
});
|
|
@@ -18,21 +18,20 @@ export default defineEventHandler(async (event) => {
|
|
|
18
18
|
const routeRules = await getRouteRules(event);
|
|
19
19
|
const authConfig = routeRules.nuxtAegis?.auth;
|
|
20
20
|
const shouldProtect = authConfig === true || authConfig === "required" || authConfig === "protected";
|
|
21
|
-
const shouldSkip = authConfig === false || authConfig === "public" || authConfig === "skip";
|
|
22
|
-
if (!shouldProtect || shouldSkip) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
21
|
let token;
|
|
26
22
|
const authHeader = getHeader(event, "authorization");
|
|
27
23
|
if (authHeader?.startsWith("Bearer ")) {
|
|
28
24
|
token = authHeader.substring(7);
|
|
29
25
|
}
|
|
30
26
|
if (!token) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
if (shouldProtect) {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 401,
|
|
30
|
+
statusMessage: "Unauthorized",
|
|
31
|
+
message: "Authentication required"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
36
35
|
}
|
|
37
36
|
if (!tokenConfig || !tokenConfig.secret) {
|
|
38
37
|
logger.error("Token configuration is missing");
|
|
@@ -45,29 +44,38 @@ export default defineEventHandler(async (event) => {
|
|
|
45
44
|
const payload = await verifyToken(token, tokenConfig.secret);
|
|
46
45
|
if (!payload) {
|
|
47
46
|
logger.debug("Token verification failed for path:", requestURL.pathname);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
if (shouldProtect) {
|
|
48
|
+
throw createError({
|
|
49
|
+
statusCode: 401,
|
|
50
|
+
statusMessage: "Unauthorized",
|
|
51
|
+
message: "Invalid or expired token"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
53
55
|
}
|
|
54
56
|
if (tokenConfig.issuer && payload.iss !== tokenConfig.issuer) {
|
|
55
57
|
logger.debug("Token issuer mismatch. Expected:", tokenConfig.issuer, "Got:", payload.iss);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if (shouldProtect) {
|
|
59
|
+
throw createError({
|
|
60
|
+
statusCode: 401,
|
|
61
|
+
statusMessage: "Unauthorized",
|
|
62
|
+
message: "Invalid token issuer"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
61
66
|
}
|
|
62
67
|
if (tokenConfig.audience && payload.aud) {
|
|
63
68
|
const audienceMatch = Array.isArray(payload.aud) ? payload.aud.includes(tokenConfig.audience) : payload.aud === tokenConfig.audience;
|
|
64
69
|
if (!audienceMatch) {
|
|
65
70
|
logger.debug("Token audience mismatch. Expected:", tokenConfig.audience, "Got:", payload.aud);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
if (shouldProtect) {
|
|
72
|
+
throw createError({
|
|
73
|
+
statusCode: 401,
|
|
74
|
+
statusMessage: "Unauthorized",
|
|
75
|
+
message: "Invalid token audience"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
const { iat, exp, iss, aud, ...userData } = payload;
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from "../utils/refreshToken.js";
|
|
9
9
|
import { setRefreshTokenCookie } from "../utils/cookies.js";
|
|
10
10
|
import { useRuntimeConfig } from "#imports";
|
|
11
|
+
import { createLogger } from "../utils/logger.js";
|
|
12
|
+
const logger = createLogger("Refresh");
|
|
11
13
|
export default defineEventHandler(async (event) => {
|
|
12
14
|
const config = useRuntimeConfig(event);
|
|
13
15
|
const cookieConfig = config.nuxtAegis?.tokenRefresh?.cookie;
|
|
@@ -65,22 +67,29 @@ export default defineEventHandler(async (event) => {
|
|
|
65
67
|
// Include provider name in JWT payload
|
|
66
68
|
};
|
|
67
69
|
const newToken = await generateToken(payload, tokenConfig, customClaims);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
const rotationEnabled = tokenRefreshConfig?.rotationEnabled ?? true;
|
|
71
|
+
if (rotationEnabled) {
|
|
72
|
+
logger.debug("Rotating refresh token for user:", payload.sub);
|
|
73
|
+
const newRefreshToken = await generateAndStoreRefreshToken(
|
|
74
|
+
providerUserInfo,
|
|
75
|
+
// RS-2: Store complete OAuth provider user data
|
|
76
|
+
provider,
|
|
77
|
+
// Store provider name
|
|
78
|
+
tokenRefreshConfig,
|
|
79
|
+
hashedRefreshToken,
|
|
80
|
+
// Pass previous token hash for rotation tracking
|
|
81
|
+
customClaims,
|
|
82
|
+
// Preserve custom claims in new refresh token
|
|
83
|
+
event
|
|
84
|
+
);
|
|
85
|
+
if (newRefreshToken) {
|
|
86
|
+
setRefreshTokenCookie(event, newRefreshToken, cookieConfig);
|
|
87
|
+
}
|
|
88
|
+
await revokeRefreshToken(hashedRefreshToken, event);
|
|
89
|
+
} else {
|
|
90
|
+
logger.debug("Reusing existing refresh token for user:", payload.sub);
|
|
91
|
+
setRefreshTokenCookie(event, refreshToken, cookieConfig);
|
|
82
92
|
}
|
|
83
|
-
await revokeRefreshToken(hashedRefreshToken, event);
|
|
84
93
|
return {
|
|
85
94
|
success: true,
|
|
86
95
|
message: "Token refreshed successfully",
|
|
@@ -50,6 +50,8 @@ export interface TokenRefreshConfig {
|
|
|
50
50
|
enabled?: boolean;
|
|
51
51
|
/** Automatically refresh tokens in the background (default: true) */
|
|
52
52
|
automaticRefresh?: boolean;
|
|
53
|
+
/** Enable refresh token rotation on every refresh (default: true) */
|
|
54
|
+
rotationEnabled?: boolean;
|
|
53
55
|
/** Refresh token cookie configuration */
|
|
54
56
|
cookie?: CookieConfig;
|
|
55
57
|
/** Encryption configuration for stored user data */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peterbud/nuxt-aegis",
|
|
3
|
-
"version": "1.1.0-alpha.
|
|
3
|
+
"version": "1.1.0-alpha.3",
|
|
4
4
|
"description": "Nuxt module for authentication with JWT token generation and session management.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@nuxt/kit": "^4.2.
|
|
55
|
+
"@nuxt/kit": "^4.2.2",
|
|
56
56
|
"consola": "^3.4.2",
|
|
57
57
|
"defu": "^6.1.4",
|
|
58
58
|
"jose": "^6.1.3",
|
|
@@ -60,17 +60,17 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@nuxt/devtools": "^3.1.1",
|
|
63
|
-
"@nuxt/eslint-config": "^1.
|
|
63
|
+
"@nuxt/eslint-config": "^1.12.1",
|
|
64
64
|
"@nuxt/module-builder": "^1.0.2",
|
|
65
|
-
"@nuxt/schema": "^4.2.
|
|
66
|
-
"@nuxt/test-utils": "^3.
|
|
65
|
+
"@nuxt/schema": "^4.2.2",
|
|
66
|
+
"@nuxt/test-utils": "^3.22.0",
|
|
67
67
|
"@types/node": "latest",
|
|
68
68
|
"changelogen": "^0.6.2",
|
|
69
|
-
"eslint": "^9.39.
|
|
70
|
-
"nuxt": "^4.2.
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
|
+
"nuxt": "^4.2.2",
|
|
71
71
|
"typescript": "~5.9.3",
|
|
72
72
|
"vitest": "^3.2.4",
|
|
73
|
-
"vue-tsc": "^3.1
|
|
73
|
+
"vue-tsc": "^3.2.1"
|
|
74
74
|
},
|
|
75
75
|
"pnpm": {
|
|
76
76
|
"overrides": {
|