@autional/shared 0.1.0-rc.32 → 0.1.0-rc.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autional/shared",
3
- "version": "0.1.0-rc.32",
3
+ "version": "0.1.0-rc.34",
4
4
  "description": "Autional shared layer — API client, auth, types, hooks, utils",
5
5
  "license": "AGPL-3.0",
6
6
  "main": "./src/index.ts",
@@ -29,7 +29,7 @@ interface AuditStatsOnlyProps {
29
29
  export function AuditStatsOnly({ title, description, children }: AuditStatsOnlyProps) {
30
30
  return (
31
31
  <div className="flex flex-col items-center justify-center min-h-[40vh] p-8">
32
- <div className="w-full max-w-2xl bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-gray-200 dark:border-slate-700 p-8">
32
+ <div className="w-full max-w-2xl bg-white dark:bg-slate-800 rounded-lg shadow-card border border-gray-200 dark:border-slate-700 p-8">
33
33
  <div className="flex flex-col items-center text-center space-y-4">
34
34
  {/* Lock icon via inline SVG (no external dependency) */}
35
35
  <svg
@@ -6,9 +6,9 @@
6
6
  *
7
7
  * - usePublicTenantSlugs: 调公开 tenants API(无认证端点),staleTime 5m 缓存,
8
8
  * retry 1,任何失败返回空数组(放行语义由 TenantIndexGuard 处理)。
9
- * - TenantIndexGuard: index 路由守卫(P0-2),防止未知路径被贪婪渲染为
10
- * Dashboard/首页。404 页由各 portal 通过 notFound prop 注入
11
- * (shared 组件不能 import 具体 app 的 not-found/page)。
9
+ * - TenantIndexGuard: 租户 slug 白名单守卫(P0-2),防止未知 slug 被贪婪渲染为
10
+ * 租户内容(index 首页与 /:slug/... 子路由通用)。404 页由各 portal 通过
11
+ * notFound prop 注入(shared 组件不能 import 具体 app 的 not-found/page)。
12
12
  *
13
13
  * ⚠️ 依赖约束: shared 包 dependencies 无 @autional/ui,禁止 import @autional/ui
14
14
  * (会引入未声明依赖/循环依赖)。loading 默认值用内联 div 骨架,
@@ -105,22 +105,19 @@ function DefaultLoadingSkeleton() {
105
105
  }
106
106
 
107
107
  /**
108
- * index 路由守卫(P0-2):防止未知路径被贪婪渲染为 Dashboard/首页。
109
- * 逻辑与 end-user-portal 现状一致(L94-111):
110
- * - URL 多段(如 /acme-corp/xyz):basename 已剥离 slug,内部单段必为未知路由段 → notFound
111
- * - URL 单段(如 /not-found):校验 slug 是否在公开租户列表 → 无效 → notFound
112
- * - URL 单段且 slug 有效(如 /acme-corp)→ children
108
+ * 租户 slug 白名单守卫(P0-2):防止未知 slug 被贪婪渲染为租户内容。
109
+ * - URL 首段经 extractSlugFromPath 取为 slug(保留段/已注册业务段返回 undefined,放行)。
110
+ * - slug 命中公开租户列表 → children;未命中 → notFound(/nonexistent-tenant[/...] 均 404)。
111
+ * - 只校验 slug 一段,不看路径深度 —— 子路径合法性(/acme-corp/terms 等)由路由表决定。
112
+ * 2026-10-09 移除「URL 段数 > 1 → notFound」检查:它出自已废弃的动态 basename 方案
113
+ * (那时 basename 剥掉 slug,内部多段 = 未知路由);basename 改为 '/' 后它对 index
114
+ * 路由从未生效,却在包裹子路由处误杀 —— auth 站 /:tenantSlug/terms|privacy 曾因此恒 404。
113
115
  * - 白名单为空数组时放行(public tenants API 挂掉时退化为不拦截,与 end-user 现状一致)
114
116
  */
115
117
  export function TenantIndexGuard({ children, notFound, loading }: TenantIndexGuardProps) {
116
118
  const { data: tenants, isLoading } = usePublicTenantSlugs();
117
119
  const slug = useTenantSlugFromUrl();
118
120
 
119
- if (typeof window !== 'undefined') {
120
- const fullSegs = window.location.pathname.split('/').filter(Boolean);
121
- if (fullSegs.length > 1) return <>{notFound}</>;
122
- }
123
-
124
121
  if (isLoading && !tenants) return <>{loading ?? <DefaultLoadingSkeleton />}</>;
125
122
 
126
123
  if (slug && tenants && tenants.length > 0 && !tenants.some((t) => (t.name || t.slug) === slug)) {
@@ -178,10 +178,19 @@ describe('TenantIndexGuard(P0-2 slug 白名单守卫)', () => {
178
178
  await waitFor(() => expect(screen.getByTestId('children')).toBeInTheDocument());
179
179
  });
180
180
 
181
- it('URL 多段路径 → 直接 notFound(basename 已剥离 slug,内部必为未知段)', () => {
182
- setPath('/acme-corp/xyz');
181
+ it('slug 有效 + 子路径(/acme-corp/terms)→ 渲染 children(回归:子路由此前被段数检查误杀)', async () => {
182
+ stubFetch([{ id: '1', name: 'acme-corp' }]);
183
+ setPath('/acme-corp/terms');
184
+ render(wrapper(<TenantIndexGuard notFound={notFound}>{children}</TenantIndexGuard>));
185
+ await waitFor(() => expect(screen.getByTestId('children')).toBeInTheDocument());
186
+ expect(screen.queryByTestId('notfound')).toBeNull();
187
+ });
188
+
189
+ it('slug 无效 + 子路径(/nonexistent-tenant/terms)→ 渲染 notFound', async () => {
190
+ stubFetch([{ id: '1', name: 'acme-corp' }]);
191
+ setPath('/nonexistent-tenant/terms');
183
192
  render(wrapper(<TenantIndexGuard notFound={notFound}>{children}</TenantIndexGuard>));
184
- expect(screen.getByTestId('notfound')).toBeInTheDocument();
193
+ await waitFor(() => expect(screen.getByTestId('notfound')).toBeInTheDocument());
185
194
  expect(screen.queryByTestId('children')).toBeNull();
186
195
  });
187
196