@veap/plugins 0.1.1 → 0.1.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.
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { checkSecurity, getAuthRoutes, getCurrentSession } from "@veap/auth";
|
|
2
|
-
import { AppError, verifySameOrigin } from "@veap/kernel";
|
|
3
|
-
import { headers } from "next/headers";
|
|
4
|
-
import { redirect } from "next/navigation";
|
|
2
|
+
import { AppError, app, REQUEST_CONTEXT, verifySameOrigin, } from "@veap/kernel";
|
|
5
3
|
export const EnsuredGuest = async (ctx, next) => {
|
|
6
4
|
const { user, session } = await getCurrentSession();
|
|
7
5
|
if (user && session) {
|
|
8
|
-
const
|
|
9
|
-
const referer =
|
|
6
|
+
const requestContext = await app(REQUEST_CONTEXT);
|
|
7
|
+
const referer = await requestContext.getHeader("referer");
|
|
10
8
|
if (referer && !referer.includes(ctx.path)) {
|
|
11
|
-
return redirect(referer);
|
|
9
|
+
return requestContext.redirect(referer);
|
|
12
10
|
}
|
|
13
|
-
return redirect("/");
|
|
11
|
+
return requestContext.redirect("/");
|
|
14
12
|
}
|
|
15
13
|
return await next();
|
|
16
14
|
};
|
|
@@ -18,7 +16,8 @@ export const EnsuredUser = async (_ctx, next) => {
|
|
|
18
16
|
const { user, session } = await getCurrentSession();
|
|
19
17
|
if (!user || !session) {
|
|
20
18
|
const authRoutes = getAuthRoutes();
|
|
21
|
-
|
|
19
|
+
const requestContext = await app(REQUEST_CONTEXT);
|
|
20
|
+
return requestContext.redirect(authRoutes.signIn);
|
|
22
21
|
}
|
|
23
22
|
return await next();
|
|
24
23
|
};
|
|
@@ -38,11 +37,13 @@ export const EnsuredAuth = async (ctx, next) => {
|
|
|
38
37
|
const { user, session } = await getCurrentSession();
|
|
39
38
|
const authRoutes = getAuthRoutes();
|
|
40
39
|
if (!user || !session) {
|
|
41
|
-
|
|
40
|
+
const requestContext = await app(REQUEST_CONTEXT);
|
|
41
|
+
return requestContext.redirect(authRoutes.signIn);
|
|
42
42
|
}
|
|
43
43
|
const security = await checkSecurity(session, user, ctx.roles, ctx.permissions, undefined, ctx.path);
|
|
44
44
|
if (!security.satisfied) {
|
|
45
|
-
|
|
45
|
+
const requestContext = await app(REQUEST_CONTEXT);
|
|
46
|
+
return requestContext.redirect(security.redirect || authRoutes.signIn);
|
|
46
47
|
}
|
|
47
48
|
return await next();
|
|
48
49
|
};
|
|
@@ -71,12 +72,27 @@ export const ApiEnsuredAuth = async (_request, context, next) => {
|
|
|
71
72
|
* Throws AppError.Forbidden if request originates from an untrusted origin.
|
|
72
73
|
*/
|
|
73
74
|
export const SameOrigin = async (_ctx, next) => {
|
|
74
|
-
const
|
|
75
|
-
const origin =
|
|
76
|
-
const secFetchSite =
|
|
77
|
-
const secFetchMode =
|
|
75
|
+
const requestContext = await app(REQUEST_CONTEXT);
|
|
76
|
+
const origin = await requestContext.getHeader("origin");
|
|
77
|
+
const secFetchSite = await requestContext.getHeader("sec-fetch-site");
|
|
78
|
+
const secFetchMode = await requestContext.getHeader("sec-fetch-mode");
|
|
78
79
|
if (origin || (secFetchSite && secFetchMode !== "navigate")) {
|
|
79
|
-
|
|
80
|
+
const host = await requestContext.getHeader("host");
|
|
81
|
+
const xForwardedHost = await requestContext.getHeader("x-forwarded-host");
|
|
82
|
+
const headersMap = new Map([
|
|
83
|
+
["origin", origin],
|
|
84
|
+
["sec-fetch-site", secFetchSite],
|
|
85
|
+
["sec-fetch-mode", secFetchMode],
|
|
86
|
+
["host", host],
|
|
87
|
+
["x-forwarded-host", xForwardedHost],
|
|
88
|
+
]);
|
|
89
|
+
const requestLike = {
|
|
90
|
+
headers: {
|
|
91
|
+
get: (name) => headersMap.get(name.toLowerCase()) ?? null,
|
|
92
|
+
},
|
|
93
|
+
method: "POST",
|
|
94
|
+
};
|
|
95
|
+
if (!verifySameOrigin(requestLike)) {
|
|
80
96
|
throw AppError.Forbidden("Cross-origin request rejected");
|
|
81
97
|
}
|
|
82
98
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
export
|
|
2
|
+
export interface SoftNavigationInterceptorProps {
|
|
3
3
|
children: React.ReactNode;
|
|
4
|
-
|
|
4
|
+
onNavigate?: (href: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function SoftNavigationInterceptor({ children, onNavigate, }: SoftNavigationInterceptorProps): React.JSX.Element;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useRouter } from "next/navigation";
|
|
4
3
|
import * as React from "react";
|
|
5
|
-
export function SoftNavigationInterceptor({ children, }) {
|
|
6
|
-
const router = useRouter();
|
|
4
|
+
export function SoftNavigationInterceptor({ children, onNavigate, }) {
|
|
7
5
|
React.useEffect(() => {
|
|
8
6
|
const handleClick = (e) => {
|
|
9
7
|
// Ignore clicks with modifiers
|
|
@@ -22,13 +20,22 @@ export function SoftNavigationInterceptor({ children, }) {
|
|
|
22
20
|
// Only intercept internal links
|
|
23
21
|
if (href?.startsWith("/") &&
|
|
24
22
|
!href.startsWith("//") &&
|
|
25
|
-
anchor.target !== "_blank"
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
anchor.target !== "_blank" &&
|
|
24
|
+
!anchor.hasAttribute("download") &&
|
|
25
|
+
anchor.getAttribute("rel") !== "external") {
|
|
26
|
+
if (onNavigate) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
onNavigate(href);
|
|
29
|
+
}
|
|
30
|
+
else if (typeof window !== "undefined") {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
window.history.pushState(null, "", href);
|
|
33
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
};
|
|
30
37
|
document.addEventListener("click", handleClick);
|
|
31
38
|
return () => document.removeEventListener("click", handleClick);
|
|
32
|
-
}, [
|
|
39
|
+
}, [onNavigate]);
|
|
33
40
|
return _jsx(_Fragment, { children: children });
|
|
34
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { app, REQUEST_CONTEXT } from "@veap/kernel";
|
|
3
3
|
import { buildRouteTree } from "../discovery";
|
|
4
4
|
import { VeapRouter } from "./renderer";
|
|
5
5
|
export function withRouter(Component, configOrPath) {
|
|
@@ -10,8 +10,13 @@ export function withRouter(Component, configOrPath) {
|
|
|
10
10
|
: configOrPath || {};
|
|
11
11
|
let routePath = config.path;
|
|
12
12
|
if (!routePath) {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
try {
|
|
14
|
+
const requestContext = await app(REQUEST_CONTEXT);
|
|
15
|
+
routePath = (await requestContext.getHeader("x-invoke-path")) || "/";
|
|
16
|
+
}
|
|
17
|
+
catch (_e) {
|
|
18
|
+
routePath = "/";
|
|
19
|
+
}
|
|
15
20
|
}
|
|
16
21
|
const tree = await buildRouteTree(true);
|
|
17
22
|
return (_jsx(VeapRouter, { tree: tree, path: routePath || "/", searchParams: searchParams, CustomPageComponent: Component, customConfig: config }));
|
|
@@ -24,7 +24,33 @@
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
import type { UserPermission, UserRole } from "@veap/auth";
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Framework-agnostic metadata shape for route nodes.
|
|
29
|
+
* Compatible with Next.js Metadata and standard HTML meta tags.
|
|
30
|
+
*/
|
|
31
|
+
export interface VeapMetadata {
|
|
32
|
+
title?: string | {
|
|
33
|
+
default: string;
|
|
34
|
+
template?: string;
|
|
35
|
+
absolute?: string;
|
|
36
|
+
};
|
|
37
|
+
description?: string;
|
|
38
|
+
keywords?: string | string[];
|
|
39
|
+
authors?: Array<{
|
|
40
|
+
name?: string;
|
|
41
|
+
url?: string;
|
|
42
|
+
}>;
|
|
43
|
+
creator?: string;
|
|
44
|
+
publisher?: string;
|
|
45
|
+
robots?: any;
|
|
46
|
+
icons?: any;
|
|
47
|
+
openGraph?: any;
|
|
48
|
+
twitter?: any;
|
|
49
|
+
alternates?: any;
|
|
50
|
+
other?: Record<string, string | number | (string | number)[]>;
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
}
|
|
53
|
+
export type Metadata = VeapMetadata;
|
|
28
54
|
import type * as React from "react";
|
|
29
55
|
import type { ApiMiddleware, BreadcrumbValue, VeapMiddleware } from "../common/types";
|
|
30
56
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veap/plugins",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Plugin lifecycle, extension points, and virtual router for Veap",
|
|
5
5
|
"bugs": "https://github.com/veapjs/veap-plugins/issues",
|
|
6
6
|
"repository": {
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"@dnd-kit/utilities": "^3.2.2",
|
|
59
59
|
"@veap/auth": "0.1.1",
|
|
60
60
|
"@veap/database": "0.1.1",
|
|
61
|
-
"@veap/intl": "0.1.
|
|
62
|
-
"@veap/kernel": "0.1.
|
|
61
|
+
"@veap/intl": "0.1.2",
|
|
62
|
+
"@veap/kernel": "0.1.2",
|
|
63
63
|
"chalk": "^5.3.0",
|
|
64
64
|
"ora": "^8.1.1",
|
|
65
65
|
"zod": "^4.6.5"
|
|
@@ -68,7 +68,6 @@
|
|
|
68
68
|
"@iconify/react": "^6.0.2",
|
|
69
69
|
"@veap/ui": "^0.1.0",
|
|
70
70
|
"lucide-react": "^1.55.0",
|
|
71
|
-
"next": "^16",
|
|
72
71
|
"react": "^19",
|
|
73
72
|
"react-dom": "^19",
|
|
74
73
|
"sonner": "^2.0.8"
|
|
@@ -83,9 +82,6 @@
|
|
|
83
82
|
"lucide-react": {
|
|
84
83
|
"optional": true
|
|
85
84
|
},
|
|
86
|
-
"next": {
|
|
87
|
-
"optional": true
|
|
88
|
-
},
|
|
89
85
|
"react": {
|
|
90
86
|
"optional": true
|
|
91
87
|
},
|