@7365admin1/layer-common 4.118.9 → 4.118.10
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/CHANGELOG.md +10 -4
- package/middleware/01.auth.ts +1 -11
- package/middleware/terms-gate.global.ts +19 -0
- package/package.json +1 -1
- package/utils/terms-gate.ts +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.118.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5fd7f77: Accept Terms & Privacy gate now runs on every page (global middleware), not only pages that declare 01-auth.
|
|
8
|
+
|
|
3
9
|
## 4.118.9
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -3182,11 +3188,11 @@ Monthly report: a month that had patrols opens the list of that month's patrol r
|
|
|
3182
3188
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
3183
3189
|
own rule from two endpoints the console already calls, unprojected:
|
|
3184
3190
|
|
|
3185
|
-
|
|
3186
|
-
|
|
3191
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
3192
|
+
GET /api/roles/id/:role that membership's role document
|
|
3187
3193
|
|
|
3188
|
-
|
|
3189
|
-
|
|
3194
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
3195
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
3190
3196
|
|
|
3191
3197
|
`role.default` is the marker because it is the only property of a platform
|
|
3192
3198
|
staff role no API caller can set - `role.controller.ts` validates create and
|
package/middleware/01.auth.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { termsGateSkips } from "../utils/terms-gate";
|
|
2
|
-
|
|
3
1
|
export default defineNuxtRouteMiddleware(async (route) => {
|
|
4
2
|
const { cookieConfig } = useRuntimeConfig().public;
|
|
5
3
|
|
|
@@ -14,13 +12,5 @@ export default defineNuxtRouteMiddleware(async (route) => {
|
|
|
14
12
|
// Redirect to login page if no access token
|
|
15
13
|
return navigateTo({ name: "index" });
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
// Terms and Privacy Policy: ask once per account per published version.
|
|
19
|
-
// Fails open; print/PDF/preview/kiosk/public (plain layout) pages are skipped.
|
|
20
|
-
if (import.meta.client && !termsGateSkips(route, navigator.userAgent)) {
|
|
21
|
-
const { check } = useTermsGate();
|
|
22
|
-
if (await check()) {
|
|
23
|
-
return navigateTo({ path: "/accept-terms", query: { redirect: route.fullPath } });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
15
|
+
// Terms and Privacy gate: see terms-gate.global.ts (runs on every page).
|
|
26
16
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { termsGateRedirect } from "../utils/terms-gate";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "Accept Terms and Privacy Policy" gate for EVERY page in every app that
|
|
5
|
+
* extends layer-common. It was in the named `01-auth` middleware, so pages that
|
|
6
|
+
* did not declare it were never gated. Client-only, only with a `sid`, fails
|
|
7
|
+
* open, and reuses useTermsGate's cached answer (no extra call per navigation).
|
|
8
|
+
*/
|
|
9
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
10
|
+
const { cookieConfig } = useRuntimeConfig().public;
|
|
11
|
+
const redirect = await termsGateRedirect({
|
|
12
|
+
client: import.meta.client,
|
|
13
|
+
sid: useCookie("sid", cookieConfig).value,
|
|
14
|
+
to,
|
|
15
|
+
userAgent: import.meta.client ? navigator.userAgent : "",
|
|
16
|
+
check: () => useTermsGate().check(),
|
|
17
|
+
});
|
|
18
|
+
if (redirect) return navigateTo(redirect);
|
|
19
|
+
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.118.
|
|
5
|
+
"version": "4.118.10",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|
package/utils/terms-gate.ts
CHANGED
|
@@ -19,6 +19,17 @@ export function shouldPrompt(reply: TermsGateReply | null | undefined): boolean
|
|
|
19
19
|
|
|
20
20
|
const NEVER_GATED_LAYOUTS = ["plain", "plain-dark"];
|
|
21
21
|
|
|
22
|
+
// Pre-login and public pages. Most are also `plain` layout; listed by path as a
|
|
23
|
+
// second net so a public page can never loop through the gate.
|
|
24
|
+
const PUBLIC_PATHS = [
|
|
25
|
+
"/sign-in", "/sign-up", "/sign-up-invite", "/forgot-password", "/reset-password",
|
|
26
|
+
"/verify", "/logout", "/public-view", "/terms-and-conditions", "/privacy-policy",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function isPublicPath(path = ""): boolean {
|
|
30
|
+
return PUBLIC_PATHS.some((p) => path === p || path.startsWith(p + "/"));
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
/**
|
|
23
34
|
* Pages that must never be interrupted: print/PDF/preview/kiosk/public pages
|
|
24
35
|
* (they use the plain layouts), the accept page itself, an explicit opt-out,
|
|
@@ -29,6 +40,7 @@ export function termsGateSkips(
|
|
|
29
40
|
userAgent = "",
|
|
30
41
|
): boolean {
|
|
31
42
|
if (to.path === "/accept-terms") return true;
|
|
43
|
+
if (isPublicPath(to.path)) return true;
|
|
32
44
|
const meta = to.meta ?? {};
|
|
33
45
|
if (meta.skipTermsGate) return true;
|
|
34
46
|
if (typeof meta.layout === "string" && NEVER_GATED_LAYOUTS.includes(meta.layout)) return true;
|
|
@@ -43,3 +55,25 @@ export function safeRedirect(value: unknown): string {
|
|
|
43
55
|
}
|
|
44
56
|
return path;
|
|
45
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The global gate's decision: the path to redirect to, or null to let the page
|
|
61
|
+
* through. `check` (useTermsGate().check) already fails open and is cached, so
|
|
62
|
+
* this adds no API call per navigation.
|
|
63
|
+
*/
|
|
64
|
+
export async function termsGateRedirect(opts: {
|
|
65
|
+
client: boolean;
|
|
66
|
+
sid: unknown;
|
|
67
|
+
to: { path?: string; fullPath?: string; meta?: Record<string, unknown> };
|
|
68
|
+
userAgent?: string;
|
|
69
|
+
check: () => Promise<boolean>;
|
|
70
|
+
}): Promise<{ path: string; query: { redirect: string } } | null> {
|
|
71
|
+
if (!opts.client || !opts.sid || termsGateSkips(opts.to, opts.userAgent)) return null;
|
|
72
|
+
let must = false;
|
|
73
|
+
try {
|
|
74
|
+
must = await opts.check();
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return must ? { path: "/accept-terms", query: { redirect: opts.to.fullPath ?? opts.to.path ?? "/" } } : null;
|
|
79
|
+
}
|