@meistrari/auth-nuxt 3.18.0 → 3.19.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/dist/module.json +1 -1
- package/dist/runtime/composables/admin.d.ts +68 -3
- package/dist/runtime/composables/admin.js +45 -1
- package/dist/runtime/composables/application/auth.d.ts +14 -2
- package/dist/runtime/composables/application/auth.js +35 -16
- package/dist/runtime/helpers/client-cookies.d.ts +23 -0
- package/dist/runtime/helpers/client-cookies.js +28 -0
- package/dist/runtime/helpers/refresh-deps.d.ts +37 -0
- package/dist/runtime/helpers/refresh-deps.js +35 -0
- package/dist/runtime/helpers/refresh-orchestrator.d.ts +64 -0
- package/dist/runtime/helpers/refresh-orchestrator.js +85 -0
- package/dist/runtime/helpers/refresh-policy.d.ts +54 -0
- package/dist/runtime/helpers/refresh-policy.js +32 -0
- package/dist/runtime/helpers/refresh-scheduler.d.ts +39 -0
- package/dist/runtime/helpers/refresh-scheduler.js +90 -0
- package/dist/runtime/helpers/worker-timer.d.ts +27 -0
- package/dist/runtime/helpers/worker-timer.js +68 -0
- package/dist/runtime/plugins/application-token-refresh.js +58 -99
- package/dist/runtime/plugins/handshake.js +103 -31
- package/dist/runtime/server/routes/auth/refresh.d.ts +6 -0
- package/dist/runtime/server/routes/auth/refresh.js +55 -35
- package/dist/runtime/server/utils/refresh-upstream.d.ts +28 -0
- package/dist/runtime/server/utils/refresh-upstream.js +46 -0
- package/package.json +3 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createError, useRuntimeConfig } from "#imports";
|
|
2
2
|
import { defineEventHandler, deleteCookie, getCookie, setCookie } from "h3";
|
|
3
|
+
import { parseTokenExpiry } from "../../../helpers/token.js";
|
|
3
4
|
import { createNuxtAuthClient } from "../../../shared.js";
|
|
5
|
+
import { planRefreshFailure, shouldWriteTokens } from "../../utils/refresh-upstream.js";
|
|
4
6
|
export default defineEventHandler(async (event) => {
|
|
5
7
|
const config = useRuntimeConfig();
|
|
6
8
|
const authConfig = config.public.telaAuth;
|
|
@@ -12,44 +14,62 @@ export default defineEventHandler(async (event) => {
|
|
|
12
14
|
message: "No refresh token found"
|
|
13
15
|
});
|
|
14
16
|
}
|
|
17
|
+
const authClient = createNuxtAuthClient(
|
|
18
|
+
authConfig.apiUrl,
|
|
19
|
+
() => null,
|
|
20
|
+
() => refreshToken
|
|
21
|
+
);
|
|
22
|
+
let accessToken;
|
|
23
|
+
let newRefreshToken;
|
|
24
|
+
let user;
|
|
25
|
+
let organization;
|
|
26
|
+
let assurance;
|
|
15
27
|
try {
|
|
16
|
-
|
|
17
|
-
authConfig.apiUrl,
|
|
18
|
-
() => null,
|
|
19
|
-
() => refreshToken
|
|
20
|
-
);
|
|
21
|
-
const { accessToken, refreshToken: newRefreshToken, user, organization, assurance } = 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
|
-
assurance
|
|
44
|
-
};
|
|
28
|
+
({ accessToken, refreshToken: newRefreshToken, user, organization, assurance } = await authClient.application.refreshAccessToken(refreshToken));
|
|
45
29
|
} catch (error) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
const plan = planRefreshFailure(error);
|
|
31
|
+
console.error(`[Auth Refresh] Token refresh error (responding ${plan.statusCode}):`, error);
|
|
32
|
+
if (plan.deleteCookies) {
|
|
33
|
+
deleteCookie(event, "tela-access-token", { path: "/" });
|
|
34
|
+
deleteCookie(event, "tela-refresh-token", { path: "/" });
|
|
35
|
+
}
|
|
49
36
|
throw createError({
|
|
50
|
-
statusCode:
|
|
51
|
-
statusMessage:
|
|
52
|
-
message:
|
|
37
|
+
statusCode: plan.statusCode,
|
|
38
|
+
statusMessage: plan.statusMessage,
|
|
39
|
+
message: plan.message
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const currentAccessToken = getCookie(event, "tela-access-token");
|
|
43
|
+
if (!shouldWriteTokens({ accessToken, refreshToken: newRefreshToken, currentAccessToken })) {
|
|
44
|
+
console.error("[Auth Refresh] Upstream returned unusable tokens, refusing to overwrite cookies");
|
|
45
|
+
throw createError({
|
|
46
|
+
statusCode: 503,
|
|
47
|
+
statusMessage: "Service Unavailable",
|
|
48
|
+
message: "REFRESH_UPSTREAM_UNAVAILABLE"
|
|
53
49
|
});
|
|
54
50
|
}
|
|
51
|
+
setCookie(event, "tela-access-token", accessToken, {
|
|
52
|
+
secure: !import.meta.dev,
|
|
53
|
+
sameSite: "lax",
|
|
54
|
+
maxAge: 60 * 15,
|
|
55
|
+
// 15 minutes
|
|
56
|
+
priority: "high",
|
|
57
|
+
path: "/"
|
|
58
|
+
});
|
|
59
|
+
setCookie(event, "tela-refresh-token", newRefreshToken, {
|
|
60
|
+
secure: !import.meta.dev,
|
|
61
|
+
sameSite: "lax",
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
maxAge: 60 * 60 * 24 * 7,
|
|
64
|
+
// 7 days
|
|
65
|
+
priority: "high",
|
|
66
|
+
path: "/"
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
user,
|
|
71
|
+
organization,
|
|
72
|
+
assurance,
|
|
73
|
+
accessTokenExpiresAt: parseTokenExpiry(accessToken)
|
|
74
|
+
};
|
|
55
75
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** How the `/auth/refresh` route should respond to an upstream failure */
|
|
2
|
+
export interface RefreshFailurePlan {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
statusMessage: string;
|
|
5
|
+
message: string;
|
|
6
|
+
/** Only a definitive auth failure may destroy the session cookies */
|
|
7
|
+
deleteCookies: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Maps an upstream refresh failure to the HTTP response the route should
|
|
11
|
+
* send, so the browser can distinguish "session revoked" (401, cookies
|
|
12
|
+
* deleted) from "API briefly down" (429/503, cookies kept).
|
|
13
|
+
*/
|
|
14
|
+
export declare function planRefreshFailure(error: unknown): RefreshFailurePlan;
|
|
15
|
+
/**
|
|
16
|
+
* Guards cookie writes after a successful upstream refresh
|
|
17
|
+
*
|
|
18
|
+
* Rejects empty, undecodable, and already-expired tokens, plus access
|
|
19
|
+
* tokens strictly older than the one currently in the cookie — a delayed
|
|
20
|
+
* response must not overwrite a newer rotation. Grace-window reissues
|
|
21
|
+
* return the same token (equal `exp`), which stays writable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function shouldWriteTokens(input: {
|
|
24
|
+
accessToken: string | null | undefined;
|
|
25
|
+
refreshToken: string | null | undefined;
|
|
26
|
+
currentAccessToken?: string | null;
|
|
27
|
+
nowMs?: number;
|
|
28
|
+
}): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { RefreshTokenExpiredError } from "@meistrari/auth-core";
|
|
2
|
+
import { classifyRefreshStatus, extractHttpStatus } from "../../helpers/refresh-policy.js";
|
|
3
|
+
import { parseTokenExpiry } from "../../helpers/token.js";
|
|
4
|
+
export function planRefreshFailure(error) {
|
|
5
|
+
const status = extractHttpStatus(error);
|
|
6
|
+
const isAuthFailure = error instanceof RefreshTokenExpiredError || error?.code === "REFRESH_TOKEN_EXPIRED" || status !== void 0 && classifyRefreshStatus(status) === "auth";
|
|
7
|
+
if (isAuthFailure) {
|
|
8
|
+
return {
|
|
9
|
+
statusCode: 401,
|
|
10
|
+
statusMessage: "Unauthorized",
|
|
11
|
+
message: "REFRESH_TOKEN_EXPIRED",
|
|
12
|
+
deleteCookies: true
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (status === 429) {
|
|
16
|
+
return {
|
|
17
|
+
statusCode: 429,
|
|
18
|
+
statusMessage: "Too Many Requests",
|
|
19
|
+
message: "REFRESH_RATE_LIMITED",
|
|
20
|
+
deleteCookies: false
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
statusCode: 503,
|
|
25
|
+
statusMessage: "Service Unavailable",
|
|
26
|
+
message: "REFRESH_UPSTREAM_UNAVAILABLE",
|
|
27
|
+
deleteCookies: false
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function shouldWriteTokens(input) {
|
|
31
|
+
const { accessToken, refreshToken, currentAccessToken, nowMs = Date.now() } = input;
|
|
32
|
+
if (!accessToken || !refreshToken) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const expiry = parseTokenExpiry(accessToken);
|
|
36
|
+
if (expiry === null || expiry <= nowMs) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (currentAccessToken) {
|
|
40
|
+
const currentExpiry = parseTokenExpiry(currentAccessToken);
|
|
41
|
+
if (currentExpiry !== null && expiry < currentExpiry) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/auth-nuxt",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"docs": "nuxt-module-build prepare && typedoc"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@meistrari/auth-core": "1.
|
|
39
|
+
"@meistrari/auth-core": "1.29.0",
|
|
40
40
|
"jose": "6.1.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"nuxt": "^3.
|
|
43
|
+
"nuxt": "^3.10.0 || ^4.0.0",
|
|
44
44
|
"vue": "^3.0.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|