You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/10/02 01:39:19 UTC

(airavata-custos) branch bug-fix-web created (now 8978a4602)

This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a change to branch bug-fix-web
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git


      at 8978a4602 A user with no privileges can login to the portal and see the allocations and projects

This branch includes the following new commits:

     new 8978a4602 A user with no privileges can login to the portal and see the allocations and projects

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(airavata-custos) 01/01: A user with no privileges can login to the portal and see the allocations and projects

Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch bug-fix-web
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit 8978a46020214299b6a450299060996f6f5a5441
Author: lahiruj <la...@gmail.com>
AuthorDate: Thu Oct 1 21:39:10 2026 -0400

    A user with no privileges can login to the portal and see the allocations and projects
---
 web/src/app/(portal)/layout.tsx          |  6 +++---
 web/src/shared/auth/auth.ts              |  3 ++-
 web/src/shared/auth/next-auth.d.ts       |  2 ++
 web/src/shared/layout/NoAccessNotice.tsx |  7 +++----
 web/tests/allocations-list.e2e.ts        | 10 ++++++++++
 web/tests/fixtures/auth.ts               |  5 ++++-
 6 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/web/src/app/(portal)/layout.tsx b/web/src/app/(portal)/layout.tsx
index c5a673cef..fe7ec4bee 100644
--- a/web/src/app/(portal)/layout.tsx
+++ b/web/src/app/(portal)/layout.tsx
@@ -23,8 +23,8 @@ import { PortalLayout } from "@/shared/layout/PortalLayout";
 export default async function PortalRoutesLayout({ children }: { children: React.ReactNode }) {
   const session = await auth();
   if (!session?.user) redirect("/sign-in");
-  // A signed-in user with no privileges stays in the shell and sees the notice,
-  // not a redirect: sending them to a gated home route would loop.
-  const hasAccess = (session.privileges ?? []).length > 0;
+  // A user with no privileges gets in, since their pages are limited by membership.
+  // A sign-in with no Custos user sees the notice here, as a redirect would loop.
+  const hasAccess = Boolean(session.custosUserId);
   return <PortalLayout>{hasAccess ? children : <NoAccessNotice />}</PortalLayout>;
 }
diff --git a/web/src/shared/auth/auth.ts b/web/src/shared/auth/auth.ts
index 4d60d5e55..741d80d59 100644
--- a/web/src/shared/auth/auth.ts
+++ b/web/src/shared/auth/auth.ts
@@ -82,7 +82,7 @@ export const authConfig: NextAuthConfig = {
             t.privileges = body.privileges ?? [];
           }
         } catch {
-          // Leave token as-is; the portal shows the no-access notice for the empty case.
+          // The token stays without a Custos user, so the portal shows the no-access notice.
         }
       }
       if (account?.id_token) {
@@ -102,6 +102,7 @@ export const authConfig: NextAuthConfig = {
       if (t.accessToken) session.accessToken = t.accessToken;
       if (t.idToken) session.idToken = t.idToken;
       session.privileges = t.privileges ?? [];
+      session.custosUserId = t.custosUserId;
       if (session.user) {
         // Prefer the backend-resolved Custos user_id over the OIDC sub so
         // downstream API callers get a value that matches users.id.
diff --git a/web/src/shared/auth/next-auth.d.ts b/web/src/shared/auth/next-auth.d.ts
index 32b68502b..0419fccdb 100644
--- a/web/src/shared/auth/next-auth.d.ts
+++ b/web/src/shared/auth/next-auth.d.ts
@@ -22,6 +22,8 @@ declare module "next-auth" {
     accessToken?: string | null;
     idToken?: string | null;
     privileges?: Privilege[];
+    // Unset when the sign-in matches no Custos user.
+    custosUserId?: string;
   }
   interface User {
     privileges?: Privilege[];
diff --git a/web/src/shared/layout/NoAccessNotice.tsx b/web/src/shared/layout/NoAccessNotice.tsx
index 9666a8938..a0f77f71f 100644
--- a/web/src/shared/layout/NoAccessNotice.tsx
+++ b/web/src/shared/layout/NoAccessNotice.tsx
@@ -22,11 +22,10 @@ import * as React from "react";
 import { toast } from "sonner";
 
 const HEADING = "No portal access";
-const BODY = "Your account has no privileges yet. Ask an administrator to grant you a role.";
+const BODY = "No account was found for this sign-in. Ask an administrator for access.";
 
-// Shown in place of the portal content when the signed-in user holds no
-// privileges. Announces the reason as a toast and keeps a durable inline
-// message, so the state does not vanish with the toast.
+// Shown when the sign-in matches no Custos user. The message also stays on
+// the page, since a toast alone disappears.
 export function NoAccessNotice() {
   React.useEffect(() => {
     toast.error(HEADING, { description: BODY });
diff --git a/web/tests/allocations-list.e2e.ts b/web/tests/allocations-list.e2e.ts
index 7eaedc5e4..c3bc94073 100644
--- a/web/tests/allocations-list.e2e.ts
+++ b/web/tests/allocations-list.e2e.ts
@@ -31,6 +31,16 @@ test.describe("allocations list", () => {
     await expect(page).toHaveURL(/\/allocations\/alloc-/);
   });
 
+  // Make sure a user with no privileges can open their allocations.
+  test("a user with no privileges sees the allocations list", async ({ page }) => {
+    await signInAs(page, "researcher");
+    await page.goto("/allocations");
+    await expect(page.getByRole("heading", { name: /^Allocations$/ })).toBeVisible();
+    await expect(page.locator('a[href^="/allocations/alloc-"]').first()).toBeVisible({
+      timeout: 15_000,
+    });
+  });
+
   test("status filter updates URL state", async ({ page }) => {
     await signInAs(page, "admin");
     await page.goto("/allocations");
diff --git a/web/tests/fixtures/auth.ts b/web/tests/fixtures/auth.ts
index 1a64424a5..0ff65439e 100644
--- a/web/tests/fixtures/auth.ts
+++ b/web/tests/fixtures/auth.ts
@@ -21,12 +21,13 @@
 import type { Page } from "@playwright/test";
 import { encode } from "next-auth/jwt";
 
-export type Persona = "viewer" | "manager" | "admin";
+export type Persona = "researcher" | "viewer" | "manager" | "admin";
 
 // Real privilege keys so browser abilities (PRIVILEGE_ABILITY_MAP) and the nav
 // gates resolve per persona. The MSW /user/privileges handler reads the
 // matching test-privileges cookie set below.
 const PRIVILEGES: Record<Persona, string[]> = {
+  researcher: [],
   viewer: [
     "core:users:read",
     "core:organizations:read",
@@ -68,6 +69,7 @@ const PRIVILEGES: Record<Persona, string[]> = {
 };
 
 const NAMES: Record<Persona, string> = {
+  researcher: "Test Researcher",
   viewer: "Test Viewer",
   manager: "Test Manager",
   admin: "Test Admin",
@@ -84,6 +86,7 @@ export async function signInAs(page: Page, persona: Persona = "admin") {
     secret,
     token: {
       sub: `test-${persona}`,
+      custosUserId: `test-${persona}`,
       name: NAMES[persona],
       email: `${persona}@custos.local`,
       privileges: PRIVILEGES[persona],