Skip to content

Commit

Permalink
feat(): support block pages by license blacklist
Browse files Browse the repository at this point in the history
  • Loading branch information
weareoutman committed Jan 9, 2025
1 parent b02e22e commit 8d6fd01
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 20 deletions.
3 changes: 3 additions & 0 deletions dll/editor-bricks-helper/manifest.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@
"i18nText",
"initI18n",
"instantiateModalStack",
"isBlockedHref",
"isBlockedPath",
"isBlockedUrl",
"isLoggedIn",
"logout",
"looseCheckIf",
Expand Down
10 changes: 10 additions & 0 deletions etc/brick-kit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { IllustrationProps } from '@next-core/illustrations';
import { InterceptorParams } from '@next-core/brick-types';
import type { LayoutType } from '@next-core/brick-types';
import { Location as Location_2 } from 'history';
import { LocationDescriptor } from 'history';
import { MagicBrickConfig } from '@next-core/brick-types';
import { MatchResult } from '@next-core/brick-types';
import { MenuBarBrick } from '@next-core/brick-types';
Expand Down Expand Up @@ -445,6 +446,15 @@ export const initI18n: () => void;
// @public (undocumented)
export function instantiateModalStack(initialIndex?: number): ModalStack;

// @public
export function isBlockedHref(href: string): boolean;

// @public
export function isBlockedPath(pathname: string): boolean;

// @public
export function isBlockedUrl(url: string | LocationDescriptor): boolean;

// @public
export function isLoggedIn(): boolean;

Expand Down
3 changes: 3 additions & 0 deletions packages/brick-dll/manifest.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -18676,6 +18676,9 @@
"i18nText",
"initI18n",
"instantiateModalStack",
"isBlockedHref",
"isBlockedPath",
"isBlockedUrl",
"isLoggedIn",
"logout",
"looseCheckIf",
Expand Down
2 changes: 1 addition & 1 deletion packages/brick-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@next-core/brick-types": "^2.99.0",
"@next-core/color-theme": "^0.4.8",
"@next-core/supply": "^1.3.0",
"@next-sdk/api-gateway-sdk": "^2.11.0",
"@next-sdk/api-gateway-sdk": "^2.12.0",
"@next-sdk/auth-sdk": "^1.0.0",
"@next-sdk/cmdb-sdk": "^2.1.1",
"@next-sdk/micro-app-sdk": "^2.6.1",
Expand Down
62 changes: 61 additions & 1 deletion packages/brick-kit/src/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { userAnalytics } from "@next-core/easyops-analytics";
import { authenticate, getAuth, logout, isLoggedIn } from "./auth";
import {
authenticate,
getAuth,
logout,
isLoggedIn,
isBlockedPath,
isBlockedHref,
isBlockedUrl,
} from "./auth";
import { resetPermissionPreChecks } from "./internal/checkPermissions";

jest.spyOn(userAnalytics, "initialized", "get").mockReturnValue(true);
Expand All @@ -8,6 +16,15 @@ jest.mock("./internal/checkPermissions");
const spyOnSetUserId = jest.spyOn(userAnalytics, "setUserId");

describe("auth", () => {
const base = document.createElement("base");
beforeAll(() => {
base.setAttribute("href", "/next/");
document.head.appendChild(base);
});
afterAll(() => {
document.head.removeChild(base);
});

it("should work", () => {
expect(getAuth()).toEqual({});
expect(isLoggedIn()).toEqual(false);
Expand All @@ -16,16 +33,59 @@ describe("auth", () => {
username: "mock-user",
userInstanceId: "abc",
accessRule: "cmdb",
license: {
blackList: ["/a", "/b/:id/c"],
},
});
expect(getAuth()).toEqual({
org: 8888,
username: "mock-user",
userInstanceId: "abc",
accessRule: "cmdb",
license: {
blackList: ["/a", "/b/:id/c"],
},
});
expect(isLoggedIn()).toEqual(true);
expect(spyOnSetUserId).toBeCalledWith("abc");

expect(isBlockedPath("/a")).toEqual(true);
expect(isBlockedPath("/a/123")).toEqual(true);
expect(isBlockedPath("/b")).toEqual(false);
expect(isBlockedPath("/b/123")).toEqual(false);
expect(isBlockedPath("/b/123/c")).toEqual(true);
expect(isBlockedPath("/b/123/c/d")).toEqual(true);
expect(isBlockedPath("/b/123/x")).toEqual(false);
expect(isBlockedPath("/c")).toEqual(false);

expect(isBlockedHref("/a")).toEqual(false);
expect(isBlockedHref("/next/a")).toEqual(true);
expect(isBlockedHref("a")).toEqual(true);
expect(isBlockedHref("http://localhost/a")).toEqual(false);
expect(isBlockedHref("http://localhost/next/a")).toEqual(true);
expect(isBlockedHref("http://example.com/a")).toEqual(false);
expect(isBlockedHref("http://example.com/next/a")).toEqual(false);

expect(isBlockedUrl("/a?q=1")).toEqual(true);
expect(isBlockedUrl("/next/a?q=1")).toEqual(false);
expect(
isBlockedUrl({
pathname: "/a",
search: "?q=1",
})
).toEqual(true);
expect(
isBlockedUrl({
pathname: "/next/a",
search: "?q=1",
})
).toEqual(false);
expect(
isBlockedUrl({
search: "?q=1",
})
).toEqual(false);

expect(resetPermissionPreChecks).not.toBeCalled();
logout();
expect(getAuth()).toEqual({});
Expand Down
47 changes: 41 additions & 6 deletions packages/brick-kit/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { AuthInfo } from "@next-core/brick-types";
import { matchPath } from "@next-core/brick-utils";
import { userAnalytics } from "@next-core/easyops-analytics";
import { createLocation, type LocationDescriptor } from "history";
import { resetPermissionPreChecks } from "./internal/checkPermissions";
import { getBasePath } from "./internal/getBasePath";

const auth: AuthInfo = {};

Expand Down Expand Up @@ -38,12 +41,9 @@ export function getAuth(): AuthInfo {

/** @internal */
export function logout(): void {
auth.org = undefined;
auth.username = undefined;
auth.userInstanceId = undefined;
auth.accessRule = undefined;
auth.isAdmin = undefined;
auth.csrfToken = undefined;
for (const key of Object.keys(auth) as (keyof AuthInfo)[]) {
auth[key] = undefined as never;
}
resetPermissionPreChecks();

// re-init analytics to clear user_id
Expand All @@ -58,3 +58,38 @@ export function logout(): void {
export function isLoggedIn(): boolean {
return auth.username !== undefined;
}

/**
* 判断一个内部 URL 路径是否被屏蔽。
*/
export function isBlockedPath(pathname: string): boolean {
return !!auth.license?.blackList?.some((path: string) =>
matchPath(pathname, { path })
);
}

/**
* 判断一个内部 URL 是否被屏蔽。
*/
export function isBlockedUrl(url: string | LocationDescriptor): boolean {
const pathname = (typeof url === "string" ? createLocation(url) : url)
.pathname;
if (typeof pathname !== "string") {
return false;
}
return isBlockedPath(pathname);
}

/**
* 判断一个 href 是否被屏蔽。
*/
export function isBlockedHref(href: string): boolean {
const basePath = getBasePath();
const url = new URL(href, `${location.origin}${basePath}`);
// 忽略外链地址
if (url.origin !== location.origin || !url.pathname.startsWith(basePath)) {
return false;
}
// 转换为内部路径
return isBlockedPath(url.pathname.substring(basePath.length - 1));
}
23 changes: 18 additions & 5 deletions packages/brick-kit/src/core/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
import { isUnauthenticatedError } from "../internal/isUnauthenticatedError";
import { RecentApps, RouterState } from "./interfaces";
import { resetAllInjected } from "../internal/injected";
import { getAuth, isLoggedIn } from "../auth";
import { getAuth, isBlockedPath, isLoggedIn } from "../auth";
import { devtoolsHookEmit } from "../internal/devtools";
import { afterMountTree } from "./reconciler";
import { constructMenu } from "../internal/menu";
Expand Down Expand Up @@ -246,6 +246,7 @@ export class Router {
private async render(location: PluginLocation): Promise<void> {
this.state = "initial";
const renderId = (this.renderId = uniqueId("render-id-"));
const blocked = isBlockedPath(location.pathname);

resetAllInjected();
clearPollTimeout();
Expand Down Expand Up @@ -278,9 +279,9 @@ export class Router {
return;
}

const storyboard = locationContext.matchStoryboard(
this.kernel.bootstrapData.storyboards
);
const storyboard = blocked
? undefined
: locationContext.matchStoryboard(this.kernel.bootstrapData.storyboards);

const currentAppId = storyboard?.app?.id;
// dynamically change the value of the APP variable, if it's union app
Expand Down Expand Up @@ -826,6 +827,14 @@ export class Router {
customTitle: i18next.t(`${NS_BRICK_KIT}:${K.PAGE_NOT_FOUND}`),
};

const blockedPageConfig = {
illustrationsConfig: {
name: "no-permission",
category: "easyops2",
},
customTitle: i18next.t(`${NS_BRICK_KIT}:${K.LICENSE_BLOCKED}`),
};

this.state = "ready-to-mount";

mountTree(
Expand All @@ -836,7 +845,11 @@ export class Router {
status: "illustrations",
useNewIllustration: true,
style: illustrationStyle,
...(storyboard ? notFoundPageConfig : notFoundAppConfig),
...(blocked
? blockedPageConfig
: storyboard
? notFoundPageConfig
: notFoundAppConfig),
},
children: [
{
Expand Down
1 change: 1 addition & 0 deletions packages/brick-kit/src/i18n/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum K {
PAGE_NOT_FOUND = "PAGE_NOT_FOUND",
APP_NOT_FOUND = "APP_NOT_FOUND",
LICENSE_EXPIRED = "LICENSE_EXPIRED",
LICENSE_BLOCKED = "LICENSE_BLOCKED",
NO_PERMISSION = "NO_PERMISSION",
OTHER_ERROR = "OTHER_ERROR",
GO_BACK_PREVIOUS_PAGE = "GO_BACK_PREVIOUS_PAGE",
Expand Down
2 changes: 2 additions & 0 deletions packages/brick-kit/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const locale: Locale = {
"App not found, maybe the URL is wrong or you don't have permission to access",
[K.LICENSE_EXPIRED]:
"The license authorization has expired, please contact the platform administrator",
[K.LICENSE_BLOCKED]:
"The page is not authorized, please contact the platform administrator",
[K.NO_PERMISSION]:
"Unauthorized access, unable to retrieve the required resources for this page",
[K.OTHER_ERROR]: "Oops! Something went wrong",
Expand Down
1 change: 1 addition & 0 deletions packages/brick-kit/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const locale: Locale = {
[K.PAGE_NOT_FOUND]: "请求的页面未找到,请确认 URL 是否正确",
[K.APP_NOT_FOUND]: "请求的微应用无法找到, 可能是 URL 错误或者无权限访问",
[K.LICENSE_EXPIRED]: "License 授权失效,请联系平台管理员",
[K.LICENSE_BLOCKED]: "该页面未授权,请联系平台管理员",
[K.NO_PERMISSION]: "没有权限,无法获取页面所需要的资源",
[K.OTHER_ERROR]: "糟糕!页面出现了一些问题",
[K.GO_BACK_PREVIOUS_PAGE]: "回到上一页",
Expand Down
16 changes: 14 additions & 2 deletions packages/brick-kit/src/internal/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getI18nNamespace } from "../i18n";
import i18next from "i18next";
import { validatePermissions } from "./checkPermissions";
import { pipes } from "@next-core/pipes";
import { isBlockedHref, isBlockedUrl } from "../auth";

const symbolAppId = Symbol("appId");
const symbolMenuI18nNamespace = Symbol("menuI18nNamespace");
Expand Down Expand Up @@ -356,8 +357,19 @@ function walkMenuItems(menuItems: RuntimeMenuItemRawData[]): SidebarMenuItem[] {
items: children,
defaultExpanded: item.defaultExpanded,
}
: (item as SidebarMenuSimpleItem);
});
: isMenuItemBlocked(item as SidebarMenuSimpleItem)
? null
: item;
})
.filter(Boolean) as SidebarMenuSimpleItem[];
}

function isMenuItemBlocked(item: SidebarMenuSimpleItem): boolean {
return item.href
? isBlockedHref(item.href)
: item.to
? isBlockedUrl(item.to)
: false;
}

export async function processMenu(
Expand Down
2 changes: 1 addition & 1 deletion v3/brick-kit-v3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@next-core/brick-types": "^2.99.0",
"@next-core/color-theme": "^0.4.8",
"@next-core/supply": "^1.3.0",
"@next-sdk/api-gateway-sdk": "^2.7.0",
"@next-sdk/api-gateway-sdk": "^2.12.0",
"@next-sdk/auth-sdk": "^1.0.0",
"@next-sdk/cmdb-sdk": "^2.1.1",
"@next-sdk/micro-app-sdk": "^2.5.0",
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2379,10 +2379,10 @@
dependencies:
"@next-core/styles-v3" "^0.3.5"

"@next-sdk/api-gateway-sdk@^2.11.0", "@next-sdk/api-gateway-sdk@^2.7.0":
version "2.11.0"
resolved "https://registry.npmjs.org/@next-sdk/api-gateway-sdk/-/api-gateway-sdk-2.11.0.tgz#e64706247191609181730fcfb40dc9b090f969c9"
integrity sha512-VP88tuDS19zsPnRJeNUEsVsh851YupxaNUNPbQdoMxGZDmXpA5Af16VHgYmXSwYJUeCGBvXgPJrS8sWrEzZhVA==
"@next-sdk/api-gateway-sdk@^2.12.0":
version "2.12.0"
resolved "https://registry.npmjs.org/@next-sdk/api-gateway-sdk/-/api-gateway-sdk-2.12.0.tgz#645165fba3c0dc47a2232558cfacb3a448ad4247"
integrity sha512-zW6QKvOdD8axrzq8/wDNw1FD4F2BiOFSH89hWGaf3DMWkWC91qwPfsO3yaVDovREHkD/ujD/j9aMA9qEqVe/yQ==

"@next-sdk/auth-sdk@^1.0.0":
version "1.0.1"
Expand Down

0 comments on commit 8d6fd01

Please sign in to comment.