Skip to content

Commit 9131c0d

Browse files
committed
feat(webapp): show a PAT's maximum role on the tokens page
Add a "Maximum role" column to the Personal Access Tokens list showing the role a token is capped to. The column only appears when an RBAC plugin is installed and reads "-" for tokens with no cap. The header tooltip reuses the same explanation shown when creating a token.
1 parent 015106d commit 9131c0d

1 file changed

Lines changed: 29 additions & 7 deletions

File tree

  • apps/webapp/app/routes/account.tokens

apps/webapp/app/routes/account.tokens/route.tsx

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ export const meta: MetaFunction = () => {
5656
];
5757
};
5858

59+
// Shared between the create-token panel hint and the listing column
60+
// header tooltip so the cap is explained identically in both places.
61+
const MAX_ROLE_EXPLANATION =
62+
"The token can act with up to this role. Your current role in each org is the actual ceiling. The token never grants permissions that are beyond your own user role.";
63+
5964
// PATs aren't org-scoped, but the RBAC plugin's allRoles is org-keyed
6065
// (a plugin may also expose org-defined custom roles alongside the
6166
// global system roles). The picker shows the assignable system role
@@ -128,10 +133,25 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
128133
const defaultRoleId =
129134
userRoleId && assignableIds.has(userRoleId) ? userRoleId : lowestAssignable;
130135

136+
// The "Maximum role" column is a plugin concept — the OSS fallback
137+
// has no TokenRole store, so only surface it when a plugin is
138+
// installed. Tokens without a cap (legacy, or created when no
139+
// plugin was present) render as "-".
140+
const showMaxRole = await rbac.isUsingPlugin();
141+
const tokensWithMaxRole = showMaxRole
142+
? await Promise.all(
143+
personalAccessTokens.map(async (pat) => ({
144+
...pat,
145+
maxRole: (await rbac.getTokenRole(pat.id))?.name ?? null,
146+
}))
147+
)
148+
: personalAccessTokens.map((pat) => ({ ...pat, maxRole: null as string | null }));
149+
131150
return typedjson({
132-
personalAccessTokens,
151+
personalAccessTokens: tokensWithMaxRole,
133152
roles,
134153
defaultRoleId,
154+
showMaxRole,
135155
});
136156
} catch (error) {
137157
if (error instanceof Response) {
@@ -225,7 +245,8 @@ export const action: ActionFunction = async ({ request }) => {
225245
};
226246

227247
export default function Page() {
228-
const { personalAccessTokens, roles, defaultRoleId } = useTypedLoaderData<typeof loader>();
248+
const { personalAccessTokens, roles, defaultRoleId, showMaxRole } =
249+
useTypedLoaderData<typeof loader>();
229250

230251
return (
231252
<PageContainer>
@@ -258,6 +279,9 @@ export default function Page() {
258279
<TableRow>
259280
<TableHeaderCell>Name</TableHeaderCell>
260281
<TableHeaderCell>Token</TableHeaderCell>
282+
{showMaxRole && (
283+
<TableHeaderCell tooltip={MAX_ROLE_EXPLANATION}>Maximum role</TableHeaderCell>
284+
)}
261285
<TableHeaderCell>Created</TableHeaderCell>
262286
<TableHeaderCell>Last accessed</TableHeaderCell>
263287
<TableHeaderCell hiddenLabel>Delete</TableHeaderCell>
@@ -270,6 +294,7 @@ export default function Page() {
270294
<TableRow key={personalAccessToken.id} className="group">
271295
<TableCell>{personalAccessToken.name}</TableCell>
272296
<TableCell>{personalAccessToken.obfuscatedToken}</TableCell>
297+
{showMaxRole && <TableCell>{personalAccessToken.maxRole ?? "-"}</TableCell>}
273298
<TableCell>
274299
<DateTime date={personalAccessToken.createdAt} />
275300
</TableCell>
@@ -288,7 +313,7 @@ export default function Page() {
288313
);
289314
})
290315
) : (
291-
<TableBlankRow colSpan={5}>
316+
<TableBlankRow colSpan={showMaxRole ? 6 : 5}>
292317
<Paragraph
293318
variant="base/bright"
294319
className="flex items-center justify-center py-8"
@@ -400,10 +425,7 @@ function CreatePersonalAccessToken({
400425
))
401426
}
402427
</Select>
403-
<Hint>
404-
The token can act with up to this role. Your current role in each org is the
405-
actual ceiling — the token never grants more than you have.
406-
</Hint>
428+
<Hint>{MAX_ROLE_EXPLANATION}</Hint>
407429
</InputGroup>
408430
)}
409431

0 commit comments

Comments
 (0)