Compare commits
No commits in common. "455d130f747b5f9d811739044e63b1f6071e8b48" and "e88512edade234dd21c8dca806ab72db2039830c" have entirely different histories.
455d130f74
...
e88512edad
@ -117,35 +117,11 @@ forwardAuthRoutes.get("/api/forward-auth", async (c) => {
|
|||||||
WHERE id = ${auth.userId}
|
WHERE id = ${auth.userId}
|
||||||
`.then((res: any) => res[0]);
|
`.then((res: any) => res[0]);
|
||||||
|
|
||||||
if (
|
if (!user || user.account_status !== "active") {
|
||||||
!user ||
|
|
||||||
(user.account_status !== "active" && user.account_status !== "guest")
|
|
||||||
) {
|
|
||||||
return c.text("Forbidden: Account inactive", 403);
|
return c.text("Forbidden: Account inactive", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Resolve Grants and Roles
|
// 3. Resolve Grants and Roles
|
||||||
let scopes = "";
|
|
||||||
|
|
||||||
if (
|
|
||||||
user.account_status === "guest" ||
|
|
||||||
(auth.customScopes && auth.customScopes.length > 0)
|
|
||||||
) {
|
|
||||||
// Guest or delegated session handling
|
|
||||||
const customScopes = auth.customScopes || [];
|
|
||||||
const hasAppScope = customScopes.includes(`app:${appRecord.name}`) ||
|
|
||||||
customScopes.includes("*");
|
|
||||||
|
|
||||||
if (!hasAppScope) {
|
|
||||||
return c.text(
|
|
||||||
"Forbidden: Access denied to this application (Guest/Delegated)",
|
|
||||||
403,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
scopes = customScopes.filter(Boolean).join(",") || "viewer";
|
|
||||||
} else {
|
|
||||||
// Standard user handling
|
|
||||||
const globalAdmin = await isGlobalAdmin(auth.userId);
|
const globalAdmin = await isGlobalAdmin(auth.userId);
|
||||||
const grantRole = await getUserGrant(auth.userId, appRecord.id);
|
const grantRole = await getUserGrant(auth.userId, appRecord.id);
|
||||||
|
|
||||||
@ -153,10 +129,9 @@ forwardAuthRoutes.get("/api/forward-auth", async (c) => {
|
|||||||
return c.text("Forbidden: Access denied to this application", 403);
|
return c.text("Forbidden: Access denied to this application", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
scopes = [
|
const scopes = [
|
||||||
...new Set([grantRole, globalAdmin ? "admin" : null].filter(Boolean)),
|
...new Set([grantRole, globalAdmin ? "admin" : null].filter(Boolean)),
|
||||||
].join(",");
|
].join(",");
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Inject Headers
|
// 4. Inject Headers
|
||||||
c.header("X-Forwarded-User", user.username);
|
c.header("X-Forwarded-User", user.username);
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import {
|
|||||||
getCookieDomain,
|
getCookieDomain,
|
||||||
hasScope,
|
hasScope,
|
||||||
} from "../auth-session.ts";
|
} from "../auth-session.ts";
|
||||||
import { getClientIp } from "../middleware.ts";
|
|
||||||
import { auditWrapper } from "../audit.ts";
|
|
||||||
import { EventJoinPage } from "../../ui/components/EventJoinPage.tsx";
|
import { EventJoinPage } from "../../ui/components/EventJoinPage.tsx";
|
||||||
import { EventSplashPage } from "../../ui/components/EventSplashPage.tsx";
|
import { EventSplashPage } from "../../ui/components/EventSplashPage.tsx";
|
||||||
|
|
||||||
@ -245,13 +243,6 @@ eventRoutes.post("/api/join", async (c) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
auditWrapper.auditLog(guestUuid, "event_seat_claimed", event.id, {
|
|
||||||
slug: event.slug,
|
|
||||||
name: event.name,
|
|
||||||
seatNumber: event.seats_claimed,
|
|
||||||
method: "web",
|
|
||||||
}, getClientIp(c));
|
|
||||||
|
|
||||||
deleteCookie(c, "session_id", { path: "/" });
|
deleteCookie(c, "session_id", { path: "/" });
|
||||||
const rpID = Deno.env.get("RP_ID");
|
const rpID = Deno.env.get("RP_ID");
|
||||||
const cookieDomain = getCookieDomain(rpID);
|
const cookieDomain = getCookieDomain(rpID);
|
||||||
@ -352,13 +343,6 @@ eventRoutes.get("/join/:slug", async (c) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
auditWrapper.auditLog(guestUuid, "event_seat_claimed", event.id, {
|
|
||||||
slug: event.slug,
|
|
||||||
name: event.name,
|
|
||||||
seatNumber: event.seats_claimed,
|
|
||||||
method: "cli",
|
|
||||||
}, getClientIp(c));
|
|
||||||
|
|
||||||
if (format === "env") {
|
if (format === "env") {
|
||||||
return c.text(
|
return c.text(
|
||||||
`export AUTH_YES_TOKEN="${sessionId}"\nexport AUTH_YES_USER="${username}"\n`,
|
`export AUTH_YES_TOKEN="${sessionId}"\nexport AUTH_YES_USER="${username}"\n`,
|
||||||
|
|||||||
@ -93,27 +93,10 @@ Deno.test("Multi-Claim Event Passes & Join Endpoints", async (t) => {
|
|||||||
() => Promise.resolve("OK" as any),
|
() => Promise.resolve("OK" as any),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { auditWrapper } = await import("../audit.ts");
|
|
||||||
let auditCalled = false;
|
|
||||||
let auditPayload: any = null;
|
|
||||||
const auditStub = stub(
|
|
||||||
auditWrapper,
|
|
||||||
"auditLog",
|
|
||||||
(_userId, action, resource, metadata, _ip) => {
|
|
||||||
if (action === "event_seat_claimed") {
|
|
||||||
auditCalled = true;
|
|
||||||
auditPayload = { resource, metadata };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await app.request("/api/join", {
|
const res = await app.request("/api/join", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Forwarded-For": "192.168.1.1",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ code: "749-123" }),
|
body: JSON.stringify({ code: "749-123" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -126,15 +109,9 @@ Deno.test("Multi-Claim Event Passes & Join Endpoints", async (t) => {
|
|||||||
const cookies = res.headers.get("set-cookie");
|
const cookies = res.headers.get("set-cookie");
|
||||||
assertExists(cookies);
|
assertExists(cookies);
|
||||||
assert(cookies.includes(`session_id=${json.token};`));
|
assert(cookies.includes(`session_id=${json.token};`));
|
||||||
|
|
||||||
assert(auditCalled);
|
|
||||||
assertEquals(auditPayload.resource, "event-uuid-1");
|
|
||||||
assertEquals(auditPayload.metadata.slug, "deno-lab");
|
|
||||||
assertEquals(auditPayload.metadata.method, "web");
|
|
||||||
} finally {
|
} finally {
|
||||||
sqlWrapper.sql = originalSql;
|
sqlWrapper.sql = originalSql;
|
||||||
valkeySetexStub.restore();
|
valkeySetexStub.restore();
|
||||||
auditStub.restore();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -168,39 +145,18 @@ Deno.test("Multi-Claim Event Passes & Join Endpoints", async (t) => {
|
|||||||
() => Promise.resolve("OK" as any),
|
() => Promise.resolve("OK" as any),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { auditWrapper } = await import("../audit.ts");
|
|
||||||
let auditCalled = false;
|
|
||||||
let auditPayload: any = null;
|
|
||||||
const auditStub = stub(
|
|
||||||
auditWrapper,
|
|
||||||
"auditLog",
|
|
||||||
(_userId, action, resource, metadata, _ip) => {
|
|
||||||
if (action === "event_seat_claimed") {
|
|
||||||
auditCalled = true;
|
|
||||||
auditPayload = { resource, metadata };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await app.request("/join/deno-lab?format=env", {
|
const res = await app.request("/join/deno-lab?format=env", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { "X-Forwarded-For": "192.168.1.2" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(res.status, 200);
|
assertEquals(res.status, 200);
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
assert(text.includes('export AUTH_YES_TOKEN="ay_sess_'));
|
assert(text.includes('export AUTH_YES_TOKEN="ay_sess_'));
|
||||||
assert(text.includes('export AUTH_YES_USER="guest_deno-lab_2"'));
|
assert(text.includes('export AUTH_YES_USER="guest_deno-lab_2"'));
|
||||||
|
|
||||||
assert(auditCalled);
|
|
||||||
assertEquals(auditPayload.resource, "event-uuid-1");
|
|
||||||
assertEquals(auditPayload.metadata.slug, "deno-lab");
|
|
||||||
assertEquals(auditPayload.metadata.method, "cli");
|
|
||||||
} finally {
|
} finally {
|
||||||
sqlWrapper.sql = originalSql;
|
sqlWrapper.sql = originalSql;
|
||||||
valkeySetexStub.restore();
|
valkeySetexStub.restore();
|
||||||
auditStub.restore();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -268,94 +268,6 @@ Deno.test("Tier 1 & 2: GET /api/forward-auth - Dynamic Bypass (bypass_paths)", a
|
|||||||
restoreMockSql();
|
restoreMockSql();
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("Tier 1 & 2: GET /api/forward-auth - Guest session with app scope allowed", async () => {
|
|
||||||
const mockUser = {
|
|
||||||
id: "guest-id-1",
|
|
||||||
username: "guest_event_1",
|
|
||||||
account_status: "guest",
|
|
||||||
};
|
|
||||||
|
|
||||||
setMockSql(() => Promise.resolve([mockUser]));
|
|
||||||
|
|
||||||
const valkeyStub = stub(valkey, "get", (key: any) => {
|
|
||||||
const k = String(key);
|
|
||||||
if (k.startsWith("auth:app_by_host:")) {
|
|
||||||
return Promise.resolve(
|
|
||||||
JSON.stringify({ id: "app-id-1", name: "test-app" }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Promise.resolve(
|
|
||||||
JSON.stringify({
|
|
||||||
uuid: "guest-id-1",
|
|
||||||
username: "guest_event_1",
|
|
||||||
account_status: "guest",
|
|
||||||
customScopes: ["app:test-app", "viewer"],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = new Request("http://localhost/api/forward-auth", {
|
|
||||||
headers: {
|
|
||||||
Cookie: "session_id=mock-guest-session",
|
|
||||||
"X-Forwarded-Host": "test.app.local",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await app.request(req);
|
|
||||||
assertEquals(res.status, 200);
|
|
||||||
assertEquals(res.headers.get("X-Forwarded-User"), "guest_event_1");
|
|
||||||
assertEquals(res.headers.get("X-Forwarded-User-Id"), "guest-id-1");
|
|
||||||
assertEquals(res.headers.get("X-Forwarded-Scopes"), "app:test-app,viewer");
|
|
||||||
assertEquals(res.headers.get("X-Forwarded-App-Id"), "app-id-1");
|
|
||||||
|
|
||||||
restoreMockSql();
|
|
||||||
valkeyStub.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("Tier 1 & 2: GET /api/forward-auth - Guest session without app scope rejected (403)", async () => {
|
|
||||||
const mockUser = {
|
|
||||||
id: "guest-id-1",
|
|
||||||
username: "guest_event_1",
|
|
||||||
account_status: "guest",
|
|
||||||
};
|
|
||||||
|
|
||||||
setMockSql(() => Promise.resolve([mockUser]));
|
|
||||||
|
|
||||||
const valkeyStub = stub(valkey, "get", (key: any) => {
|
|
||||||
const k = String(key);
|
|
||||||
if (k.startsWith("auth:app_by_host:")) {
|
|
||||||
return Promise.resolve(
|
|
||||||
JSON.stringify({ id: "app-id-1", name: "test-app" }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Promise.resolve(
|
|
||||||
JSON.stringify({
|
|
||||||
uuid: "guest-id-1",
|
|
||||||
username: "guest_event_1",
|
|
||||||
account_status: "guest",
|
|
||||||
customScopes: ["app:other-app", "viewer"],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const req = new Request("http://localhost/api/forward-auth", {
|
|
||||||
headers: {
|
|
||||||
Cookie: "session_id=mock-guest-session",
|
|
||||||
"X-Forwarded-Host": "test.app.local",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await app.request(req);
|
|
||||||
assertEquals(res.status, 403);
|
|
||||||
assertEquals(
|
|
||||||
await res.text(),
|
|
||||||
"Forbidden: Access denied to this application (Guest/Delegated)",
|
|
||||||
);
|
|
||||||
|
|
||||||
restoreMockSql();
|
|
||||||
valkeyStub.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("Tier 1 & 2: POST /api/guests/sandbox - Creates guest session", async () => {
|
Deno.test("Tier 1 & 2: POST /api/guests/sandbox - Creates guest session", async () => {
|
||||||
const { valkey } = await import("../valkey.ts");
|
const { valkey } = await import("../valkey.ts");
|
||||||
// Mock valkey.setex to prevent connection errors during tests
|
// Mock valkey.setex to prevent connection errors during tests
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import { sql } from "../server/db.ts";
|
import { sql } from "../server/db.ts";
|
||||||
|
|
||||||
export async function getDashboardApps(
|
export async function getDashboardApps(userId: string, isAdmin: boolean) {
|
||||||
userId: string,
|
|
||||||
isAdmin: boolean,
|
|
||||||
customScopes?: string[],
|
|
||||||
) {
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
return await sql`
|
return await sql`
|
||||||
SELECT id, name, description, domain, 'Admin' as role
|
SELECT id, name, description, domain, 'Admin' as role
|
||||||
@ -12,23 +8,6 @@ export async function getDashboardApps(
|
|||||||
WHERE domain IS NOT NULL
|
WHERE domain IS NOT NULL
|
||||||
ORDER BY name ASC
|
ORDER BY name ASC
|
||||||
` as any[];
|
` as any[];
|
||||||
} else {
|
|
||||||
const appNames = (customScopes || [])
|
|
||||||
.filter((s) => s.startsWith("app:"))
|
|
||||||
.map((s) => s.split(":")[1]);
|
|
||||||
|
|
||||||
if (appNames.length > 0) {
|
|
||||||
return await sql`
|
|
||||||
SELECT a.id, a.name, a.description, a.domain, g.role
|
|
||||||
FROM apps a
|
|
||||||
JOIN grants g ON a.id = g.app_id
|
|
||||||
WHERE g.user_id = ${userId} AND a.domain IS NOT NULL
|
|
||||||
UNION
|
|
||||||
SELECT id, name, description, domain, 'Guest (Viewer)' as role
|
|
||||||
FROM apps
|
|
||||||
WHERE domain IS NOT NULL AND name = ANY(${appNames}::text[])
|
|
||||||
ORDER BY name ASC
|
|
||||||
` as any[];
|
|
||||||
} else {
|
} else {
|
||||||
return await sql`
|
return await sql`
|
||||||
SELECT a.id, a.name, a.description, a.domain, g.role
|
SELECT a.id, a.name, a.description, a.domain, g.role
|
||||||
@ -38,7 +17,6 @@ export async function getDashboardApps(
|
|||||||
ORDER BY a.name ASC
|
ORDER BY a.name ASC
|
||||||
` as any[];
|
` as any[];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSessionApps() {
|
export async function getSessionApps() {
|
||||||
|
|||||||
@ -151,7 +151,7 @@ uiApp.get("/dashboard", async (c) => {
|
|||||||
if (authRes instanceof Response) return authRes;
|
if (authRes instanceof Response) return authRes;
|
||||||
const { auth, isAdmin } = authRes;
|
const { auth, isAdmin } = authRes;
|
||||||
|
|
||||||
const apps = await getDashboardApps(auth.userId, isAdmin, auth.customScopes);
|
const apps = await getDashboardApps(auth.userId, isAdmin);
|
||||||
|
|
||||||
return c.html(AppLaunchpadPage({ apps: apps as any, isAdmin }));
|
return c.html(AppLaunchpadPage({ apps: apps as any, isAdmin }));
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user