Skip to content

feat: PAT list and details views#1533

Open
rohanchkrabrty wants to merge 1 commit intomainfrom
worktree-feat-pat-tokens
Open

feat: PAT list and details views#1533
rohanchkrabrty wants to merge 1 commit intomainfrom
worktree-feat-pat-tokens

Conversation

@rohanchkrabrty
Copy link
Copy Markdown
Contributor

Summary

  • Add PAT list view with debounced search, token cells showing expiry and last used info, and create/revoke actions
  • Add PAT details view with general info section (org role, project role, scoped projects), expiry section with regenerate support
  • Add supporting dialog components: create/update form, regenerate expiry, revoke confirmation, and token display
  • Wire up PAT routes in client-demo app for development testing

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Apr 12, 2026 9:36pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Personal Access Tokens management feature in settings with dedicated pages for viewing token lists and individual token details
    • Added ability to create, update, regenerate, and revoke personal access tokens with configurable expiry periods
    • Added token metadata display showing creation dates, expiry information, and last used timestamps
    • Added search capability to filter tokens by name
    • Added real-time validation for token titles during creation and updates
    • Added toast notifications and error handling for all token operations

Walkthrough

This PR introduces personal access token (PAT) management functionality to the Frontier client-demo application and React SDK by adding new routes, page components, navigation items, and multiple PAT-related view and dialog components for creating, updating, revoking, and displaying PATs.

Changes

Cohort / File(s) Summary
Routing & Navigation
web/apps/client-demo/src/Router.tsx, web/apps/client-demo/src/pages/Settings.tsx
Added nested PAT routes (pats and pats/:patId) under settings and registered "Personal Access Tokens" navigation item in settings sidebar.
Client Demo Pages
web/apps/client-demo/src/pages/settings/Pats.tsx, web/apps/client-demo/src/pages/settings/PatDetails.tsx
Added two page wrapper components that read route parameters and integrate PAT views with navigation callbacks.
React SDK Exports
web/sdk/react/index.ts, web/sdk/react/views-new/pat/index.ts
Added named exports for PatsView and PATDetailsView components and related type exports from the views-new/pat module.
PAT List & Details Views
web/sdk/react/views-new/pat/pat-view.tsx, web/sdk/react/views-new/pat/pat-details-view.tsx
Added two primary view components: PatsView displays searchable PAT list with dialog flows for creation and revocation; PATDetailsView displays single PAT details with update, regenerate, and revoke actions.
PAT Dialog Components
web/sdk/react/views-new/pat/components/pat-form-dialog.tsx, web/sdk/react/views-new/pat/components/pat-created-dialog.tsx, web/sdk/react/views-new/pat/components/regenerate-pat-dialog.tsx, web/sdk/react/views-new/pat/components/revoke-pat-dialog.tsx
Added four dialog components: form dialog for creating/updating PATs with validation and expiry handling, created dialog displaying generated token, regenerate dialog for token refresh, and revoke confirmation dialog.
PAT UI Components
web/sdk/react/views-new/pat/components/token-cell.tsx
Added TokenCell component for rendering individual PAT entries in a list with expiry and last-used metadata.
Styling
web/sdk/react/views-new/pat/pat-view.module.css, web/sdk/react/views-new/pat/pat-details-view.module.css
Added CSS modules defining layout, spacing, and visual styles for PAT list and details views using design tokens.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • PR #1486 — Main PR adds pats routes and Settings NAV item that directly extend the Router and Settings structures from this PR.
  • PR #1503 — Both PRs modify Router.tsx to add nested /:orgId/settings routes for managing settings child pages.
  • PR #1511 — Both PRs modify Router.tsx and Settings.tsx by adding nested settings subroutes and corresponding navigation items.

Suggested reviewers

  • rohilsurana
  • rsbh
  • paanSinghCoder

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 24316973212

Coverage remained the same at 41.606%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 36442
Covered Lines: 15162
Line Coverage: 41.61%
Coverage Strength: 11.89 hits per line

💛 - Coveralls

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/apps/client-demo/src/pages/Settings.tsx (1)

53-56: ⚠️ Potential issue | 🟡 Minor

Keep “Personal Access Tokens” nav active on details pages.

With exact-path matching, /settings/pats/:patId won’t mark the PAT nav item active.

Proposed fix
               const fullPath = `/${orgId}/settings/${item.path}`;
-              const isActive = location.pathname === fullPath;
+              const isActive =
+                location.pathname === fullPath ||
+                location.pathname.startsWith(`${fullPath}/`);
🧹 Nitpick comments (1)
web/apps/client-demo/src/pages/settings/PatDetails.tsx (1)

13-14: De-duplicate identical navigation callbacks.

Both props pass the same function; extracting one callback keeps this component cleaner.

Proposed refactor
 export default function PatDetails() {
   const { orgId, patId } = useParams<{ orgId: string; patId: string }>();
   const navigate = useNavigate();
+  const navigateToPats = () => navigate(`/${orgId}/settings/pats`);
 
   if (!patId) return null;
 
   return (
     <PATDetailsView
       patId={patId}
-      onNavigateToPats={() => navigate(`/${orgId}/settings/pats`)}
-      onDeleteSuccess={() => navigate(`/${orgId}/settings/pats`)}
+      onNavigateToPats={navigateToPats}
+      onDeleteSuccess={navigateToPats}
     />
   );
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11dfd2ef-2ed3-4148-96eb-43c634d2c1de

📥 Commits

Reviewing files that changed from the base of the PR and between 73227f6 and 20252e5.

📒 Files selected for processing (15)
  • web/apps/client-demo/src/Router.tsx
  • web/apps/client-demo/src/pages/Settings.tsx
  • web/apps/client-demo/src/pages/settings/PatDetails.tsx
  • web/apps/client-demo/src/pages/settings/Pats.tsx
  • web/sdk/react/index.ts
  • web/sdk/react/views-new/pat/components/pat-created-dialog.tsx
  • web/sdk/react/views-new/pat/components/pat-form-dialog.tsx
  • web/sdk/react/views-new/pat/components/regenerate-pat-dialog.tsx
  • web/sdk/react/views-new/pat/components/revoke-pat-dialog.tsx
  • web/sdk/react/views-new/pat/components/token-cell.tsx
  • web/sdk/react/views-new/pat/index.ts
  • web/sdk/react/views-new/pat/pat-details-view.module.css
  • web/sdk/react/views-new/pat/pat-details-view.tsx
  • web/sdk/react/views-new/pat/pat-view.module.css
  • web/sdk/react/views-new/pat/pat-view.tsx

Comment on lines +29 to +37
<Dialog.Header>
<Dialog.Title>Success</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Flex direction="column" gap={7}>
<Text size="small">
You&apos;ve successfully added a new personal access token. Copy
the token now
</Text>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use neutral copy in the shared success dialog.

This component is also used after regenerate from web/sdk/react/views-new/pat/pat-details-view.tsx, so “successfully added a new personal access token” is inaccurate there. Neutral wording avoids misleading users about whether the token was created or rotated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. We need better copy.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paanSinghCoder, glad we're aligned! Here are two options depending on how much flexibility you want:


Option A — Neutral copy (simplest fix)

Change just the body text so it reads correctly in both the create and regenerate flows:

<Text size="small">
  Your personal access token is ready. Make sure to copy it now — it won&apos;t be shown again.
</Text>

Option B — Caller-supplied description prop (more flexible)

Add an optional description prop so each call-site can pass context-specific text, while falling back to neutral copy:

export interface PATCreatedDialogProps {
  handle: ReturnType<typeof Dialog.createHandle<string>>;
  onClose?: () => void;
  description?: string;
}

const DEFAULT_DESCRIPTION =
  "Your personal access token is ready. Make sure to copy it now — it won't be shown again.";

export function PATCreatedDialog({ handle, onClose, description }: PATCreatedDialogProps) {
  // ...
  <Text size="small">{description ?? DEFAULT_DESCRIPTION}</Text>
}

Then callers can be specific:

  • Create flow (pat-view.tsx): description="Your new personal access token has been created. Copy it now — it won't be shown again."
  • Regenerate flow (pat-details-view.tsx): description="Your personal access token has been regenerated. Copy it now — it won't be shown again."

Option B keeps the component reusable while still allowing precise messaging per context. Option A is the quickest path if a single neutral phrase is sufficient.


🧠 Learnings used
Learnt from: AmanGIT07
Repo: raystack/frontier PR: 1447
File: core/organization/service.go:326-328
Timestamp: 2026-03-12T12:10:03.670Z
Learning: In `raystack/frontier`, a PAT (Personal Access Token) belongs to exactly one org and `PAT.OrgID` is immutable after creation. For `ListByUser` in `core/organization/service.go`, using `PAT.OrgID` directly to intersect org results is intentional and safe; no SpiceDB `LookupResources` call for the PAT scope is needed here.

Learnt from: AmanGIT07
Repo: raystack/frontier PR: 1475
File: core/userpat/service.go:221-222
Timestamp: 2026-03-25T04:18:43.439Z
Learning: In `raystack/frontier` (`core/userpat/service.go`), `validateScopes` and `createProjectScopedPolicies` do not currently verify that caller-supplied project resource IDs belong to the PAT's org (`PAT.OrgID`). This is a known gap: a PAT for org A could be granted project policies on org B's projects. The fix (deferred to a separate PR) is to add a `ProjectService` interface dependency to the PAT service and validate that every project resource ID in a scope belongs to the PAT's org before any policy write.

Comment thread web/sdk/react/views-new/pat/components/pat-form-dialog.tsx
Comment on lines +184 to +205
const handleTitleBlur = useCallback(async () => {
const title = getValues('title');
if (!title || !orgId) return;

// In update mode, skip check if title is unchanged
if (isUpdateMode && title === initialData?.title) {
setTitleAvailable(true);
return;
}

setTitleChecking(true);
try {
const result = await checkTitle(
create(CheckCurrentUserPATTitleRequestSchema, { orgId, title })
);
setTitleAvailable(result?.available);
} catch {
// Ignore check failure — don't block the user
} finally {
setTitleChecking(false);
}
}, [getValues, orgId, checkTitle, isUpdateMode, initialData]);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden the async title-check state before gating submit on it.

A transient failure in checkCurrentUserPATTitle leaves titleAvailable non-true, and the submit button stays disabled even though the comment says “don't block the user”. This same flow can also apply a stale availability result from an older blur to the current title.

💡 Suggested direction
   const handleTitleBlur = useCallback(async () => {
     const title = getValues('title');
     if (!title || !orgId) return;
 
     // In update mode, skip check if title is unchanged
     if (isUpdateMode && title === initialData?.title) {
       setTitleAvailable(true);
       return;
     }
 
+    const checkedTitle = title;
     setTitleChecking(true);
     try {
       const result = await checkTitle(
         create(CheckCurrentUserPATTitleRequestSchema, { orgId, title })
       );
-      setTitleAvailable(result?.available);
+      if (getValues('title') === checkedTitle) {
+        setTitleAvailable(result?.available ?? true);
+      }
     } catch {
-      // Ignore check failure — don't block the user
+      if (getValues('title') === checkedTitle) {
+        setTitleAvailable(true);
+      }
     } finally {
       setTitleChecking(false);
     }
   }, [getValues, orgId, checkTitle, isUpdateMode, initialData]);

Also applies to: 500-505

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rohanchkrabrty Please check this if valid.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread web/sdk/react/views-new/pat/components/pat-form-dialog.tsx
Comment thread web/sdk/react/views-new/pat/components/regenerate-pat-dialog.tsx
Comment thread web/sdk/react/views-new/pat/components/revoke-pat-dialog.tsx
Comment thread web/sdk/react/views-new/pat/components/token-cell.tsx
Comment thread web/sdk/react/views-new/pat/components/token-cell.tsx
Comment thread web/sdk/react/views-new/pat/pat-details-view.tsx
Comment thread web/sdk/react/views-new/pat/pat-view.tsx
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the UI with new design changes in Figma.

@@ -0,0 +1,35 @@
.section {
border: 1px solid var(--rs-color-border-base-primary);
border-radius: 4px;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace 4px with var(--rs-radius-2).


.detailRow {
display: flex;
gap: 8px;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace 8px with --rs-space-3


.detailLabel {
flex-shrink: 0;
min-width: 120px;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace 120px with --rs-space-17

.chipGroup {
display: flex;
flex-wrap: wrap;
gap: 4px;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace 4px with var(--rs-radius-2).


.tokenIcon {
width: 48px;
height: 48px;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace height and width with --rs-space-11

</>
)}

<PATFormDialog
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix in Apsara: If the menu item text is too long, the UI is breaking. We will have to add an ellipsis and a fixed max-width for the popup.

Image

onRevoked={() => refetch()}
/>
</ViewContainer>
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few design updates in Figma (Projects in Create PAT dialog). Please update.

Image

} from '@raystack/apsara-v1';
import { useFrontier } from '../../../contexts/FrontierContext';
import { DEFAULT_DATE_FORMAT } from '../../../utils/constants';
import { handleConnectError } from '~/utils/error';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only handleConnectError imported with ~/utils/error and everything else in the same file uses relative paths. We can make this consistent,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants