feat(hub): drop redundant /admin/org/:slug path + release v0.0.35

Silo hostname already carries tenancy. Admin SPA routes become /admin/...,
legacy /admin/org/:slug/* bookmarks redirect, login lands on /admin.
This commit is contained in:
2026-07-18 17:36:08 +00:00
parent eb0be43eac
commit 4e7b158ff9
25 changed files with 221 additions and 129 deletions
+6 -6
View File
@@ -170,7 +170,7 @@ describe("admin auth + org API guards", () => {
try {
const res = await app.inject({
method: "GET",
url: "/auth/feishu?returnTo=/admin/org/test-default",
url: "/auth/feishu?returnTo=/admin",
});
expect(res.statusCode).toBe(302);
const location = res.headers.location;
@@ -233,7 +233,7 @@ describe("admin auth + org API guards", () => {
try {
const nonce = "nonce-test-1";
const state = signOAuthState(
{ nonce, returnTo: "/admin/org/test-default" },
{ nonce, returnTo: "/admin" },
SESSION_SECRET,
);
const res = await app.inject({
@@ -242,7 +242,7 @@ describe("admin auth + org API guards", () => {
headers: { cookie: `${OAUTH_STATE_COOKIE_NAME}=${nonce}` },
});
expect(res.statusCode).toBe(302);
expect(res.headers.location).toBe("/admin/org/test-default");
expect(res.headers.location).toBe("/admin");
expect(JSON.stringify(res.headers["set-cookie"])).toContain("cph_session=");
const user = await prisma.user.findUnique({ where: { feishuOpenId: "ou_new" } });
@@ -293,7 +293,7 @@ describe("admin auth + org API guards", () => {
try {
const start = await app.inject({
method: "GET",
url: "/auth/feishu/test-default?returnTo=/admin/org/test-default/settings",
url: "/auth/feishu/test-default?returnTo=/admin/settings",
});
expect(start.statusCode).toBe(302);
const authorize = new URL(String(start.headers.location));
@@ -308,7 +308,7 @@ describe("admin auth + org API guards", () => {
headers: { cookie: nonceCookie },
});
expect(callback.statusCode).toBe(302);
expect(callback.headers.location).toBe("/admin/org/test-default/settings");
expect(callback.headers.location).toBe("/admin/settings");
const sessionCookie = cookiePair(callback.headers["set-cookie"], "cph_session");
const me = await app.inject({ method: "GET", url: "/api/me", headers: { cookie: sessionCookie } });
expect(me.statusCode).toBe(200);
@@ -346,7 +346,7 @@ describe("admin auth + org API guards", () => {
headers: { cookie: cookiePair(defaultStart.headers["set-cookie"], OAUTH_STATE_COOKIE_NAME) },
});
expect(defaultCallback.statusCode).toBe(302);
expect(defaultCallback.headers.location).toBe("/admin/org/test-default");
expect(defaultCallback.headers.location).toBe("/admin");
await connections.disable({ organizationId: DEFAULT_ORG_ID, actorUserId: "scoped-owner" });
const revoked = await app.inject({ method: "GET", url: "/api/me", headers: { cookie: sessionCookie } });
+9 -6
View File
@@ -68,14 +68,14 @@ describe("session cookie signing", () => {
describe("oauth state signing", () => {
it("round-trips state with returnTo", () => {
const token = signOAuthState(
{ nonce: "abc", returnTo: "/admin/org/acme" },
{ nonce: "abc", returnTo: "/admin" },
SECRET,
600,
1_700_000_000,
);
expect(verifyOAuthState(token, SECRET, 1_700_000_100)).toEqual({
nonce: "abc",
returnTo: "/admin/org/acme",
returnTo: "/admin",
exp: 1_700_000_600,
});
});
@@ -83,13 +83,13 @@ describe("oauth state signing", () => {
it("binds state to an Organization and connection as one inseparable scope", () => {
const token = signOAuthState({
nonce: "scoped",
returnTo: "/admin/org/acme",
returnTo: "/admin",
organizationId: "org-acme",
connectionId: "connection-acme",
}, SECRET, 600, 1_700_000_000);
expect(verifyOAuthState(token, SECRET, 1_700_000_100)).toEqual({
nonce: "scoped",
returnTo: "/admin/org/acme",
returnTo: "/admin",
organizationId: "org-acme",
connectionId: "connection-acme",
exp: 1_700_000_600,
@@ -98,8 +98,11 @@ describe("oauth state signing", () => {
});
describe("sanitizeReturnTo", () => {
it("allows admin paths", () => {
expect(sanitizeReturnTo("/admin/org/acme")).toBe("/admin/org/acme");
it("allows admin paths and rewrites legacy org slug prefixes", () => {
expect(sanitizeReturnTo("/admin")).toBe("/admin");
expect(sanitizeReturnTo("/admin/usage")).toBe("/admin/usage");
expect(sanitizeReturnTo("/admin/org/acme")).toBe("/admin");
expect(sanitizeReturnTo("/admin/org/acme/usage")).toBe("/admin/usage");
});
it("blocks open redirects", () => {
+1
View File
@@ -23,6 +23,7 @@ describe("isSiloHttpRateLimitExempt", () => {
expect(isSiloHttpRateLimitExempt("/favicon.svg")).toBe(true);
expect(isSiloHttpRateLimitExempt("/robots.txt")).toBe(true);
expect(isSiloHttpRateLimitExempt("/admin")).toBe(true);
expect(isSiloHttpRateLimitExempt("/admin/members")).toBe(true);
expect(isSiloHttpRateLimitExempt("/admin/org/para-26071100/members")).toBe(true);
});