Merge commit from fork

* fix: add `Scopes` to `Request` interface so that scopes can be validated on all requests

* feat: assert org from scope exists when authorizing requests

* fix: check all scopes

* comments

* check org on callback creation

* fix tests

* clarifications

* fix scope marshaling

* fix: enfore organization for authrequest in initiation

* fix: filter sessions on the accounts page by organization scope

* add integration tests

---------

Co-authored-by: Livio Spring <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
Wim Van Laer
2026-03-17 12:54:27 +01:00
committed by GitHub
co-authored by Livio Spring Max Peintner
parent aa4432dc99
commit c3a6bdb0bb
21 changed files with 729 additions and 115 deletions
+19 -4
View File
@@ -17,12 +17,27 @@ export async function generateMetadata(): Promise<Metadata> {
return { title: t("title") };
}
async function loadSessions({ serviceConfig }: { serviceConfig: ServiceConfig }) {
async function loadSessions({
serviceConfig,
organization,
}: {
serviceConfig: ServiceConfig;
organization?: string;
}) {
const cookieIds = await getAllSessionCookieIds();
if (cookieIds && cookieIds.length) {
const response = await listSessions({ serviceConfig, ids: cookieIds.filter((id) => !!id) as string[] });
return response?.sessions ?? [];
const response = await listSessions({
serviceConfig,
ids: cookieIds.filter((id) => !!id) as string[],
});
let sessions = response?.sessions ?? [];
if (organization) {
sessions = sessions.filter((s) => s.factors?.user?.organizationId === organization);
}
return sessions;
} else {
console.info("No session cookie found.");
return [];
@@ -46,7 +61,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
}
}
let sessions = await loadSessions({ serviceConfig });
let sessions = await loadSessions({ serviceConfig, organization });
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
+2 -2
View File
@@ -268,7 +268,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr
} else if (authRequest.prompt.includes(Prompt.NONE)) {
const securitySettings = await getSecuritySettings({ serviceConfig });
const selectedSession = await findValidSession({ serviceConfig, sessions, authRequest });
const selectedSession = await findValidSession({ serviceConfig, sessions, authRequest, organization });
const noSessionResponse = NextResponse.json({ error: "No active session found" }, { status: 400 });
setCSPHeaders(noSessionResponse, serviceConfig, securitySettings);
@@ -304,7 +304,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr
return callbackResponse;
} else {
let selectedSession = await findValidSession({ serviceConfig, sessions, authRequest });
let selectedSession = await findValidSession({ serviceConfig, sessions, authRequest, organization });
if (!selectedSession || !selectedSession.id) {
return gotoAccounts({
+7 -1
View File
@@ -142,13 +142,15 @@ export async function findValidSession({
sessions,
authRequest,
samlRequest,
organization,
}: {
serviceConfig: ServiceConfig;
sessions: Session[];
authRequest?: AuthRequest;
samlRequest?: SAMLRequest;
organization?: string;
}): Promise<Session | undefined> {
const sessionsWithHint = sessions.filter((s) => {
let sessionsWithHint = sessions.filter((s) => {
if (authRequest && authRequest.hintUserId) {
return s.factors?.user?.id === authRequest.hintUserId;
}
@@ -163,6 +165,10 @@ export async function findValidSession({
return true;
});
if (organization) {
sessionsWithHint = sessionsWithHint.filter((s) => s.factors?.user?.organizationId === organization);
}
if (sessionsWithHint.length === 0) {
return undefined;
}