mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
Canonicalize API token settings copy
Route API token reveal and rotation guidance through the shared API token presentation helper so settings copy points to API Access instead of legacy Security token wording.
This commit is contained in:
@@ -723,6 +723,10 @@ entry.
|
||||
|
||||
The API layer already uses contract tests in many places, but every major live
|
||||
contract should continue moving toward canonical-only runtime shapes.
|
||||
The shared API-token presentation helper also owns API token management-location
|
||||
copy for Settings surfaces. Token reveal and rotation guidance must point
|
||||
operators to `Settings → API Access` and must not revive legacy
|
||||
`Security → API tokens` wording.
|
||||
That same shared `internal/api/` boundary now also keeps ephemeral auth flow
|
||||
state and request correlation fail-closed. OIDC authorization state storage
|
||||
must cap abandoned entries and evict the earliest-expiring state before
|
||||
|
||||
@@ -204,6 +204,10 @@ That shared token-management boundary now also includes
|
||||
`frontend-modern/src/utils/apiTokenPresentation.ts`, so API-token load,
|
||||
generate, and revoke errors stay on one governed customer-facing wording path
|
||||
instead of drifting back into hook-local notification strings.
|
||||
That same API-token presentation helper also owns API token management-location
|
||||
copy for Settings surfaces. Token reveal and rotation guidance must point
|
||||
operators to `Settings → API Access` and must not revive legacy
|
||||
`Security → API tokens` wording.
|
||||
That same token-management boundary must also treat top-level TrueNAS
|
||||
appliances as canonical agent-scope resources through the shared agent-facet
|
||||
helper. Security surfaces may consume compatibility-normalized
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Dialog } from '@/components/shared/Dialog';
|
||||
import { SectionHeader } from '@/components/shared/SectionHeader';
|
||||
import { controlClass, formField, formHelpText, labelClass } from '@/components/shared/Form';
|
||||
import type { SecurityStatus as SecurityStatusInfo } from '@/types/config';
|
||||
import { getAPITokenManagementLocationMessage } from '@/utils/apiTokenPresentation';
|
||||
|
||||
interface BackupTransferDialogsProps {
|
||||
securityStatus: Accessor<SecurityStatusInfo | null>;
|
||||
@@ -202,9 +203,7 @@ export const BackupTransferDialogs: Component<BackupTransferDialogsProps> = (pro
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-muted rounded p-2">
|
||||
<p class="font-semibold mb-1">
|
||||
Create or rotate API tokens in Settings → Security → API tokens.
|
||||
</p>
|
||||
<p class="font-semibold mb-1">{getAPITokenManagementLocationMessage()}</p>
|
||||
<p>
|
||||
Tokens are managed in the UI and stored in <code>api_tokens.json</code>.
|
||||
</p>
|
||||
|
||||
@@ -157,6 +157,7 @@ describe('APITokenManager', () => {
|
||||
name: 'Container automation',
|
||||
scopes: [DOCKER_MANAGE_SCOPE, DOCKER_REPORT_SCOPE],
|
||||
}),
|
||||
note: 'Copy this token now. You can reopen this dialog from Settings → API Access while this page stays open.',
|
||||
}),
|
||||
);
|
||||
expect(notificationSuccessMock).toHaveBeenCalledWith(
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { Resource } from '@/types/resource';
|
||||
import { formatRelativeTime } from '@/utils/format';
|
||||
import {
|
||||
getAPITokenGenerateErrorMessage,
|
||||
getAPITokenRevealSettingsNote,
|
||||
getAPITokensLoadErrorMessage,
|
||||
getAPITokenRevokeErrorMessage,
|
||||
} from '@/utils/apiTokenPresentation';
|
||||
@@ -196,7 +197,7 @@ export const useAPITokenManagerState = (props: APITokenManagerProps) => {
|
||||
token,
|
||||
record,
|
||||
source: 'security',
|
||||
note: 'Copy this token now. You can reopen this dialog from Security → API tokens while this page stays open.',
|
||||
note: getAPITokenRevealSettingsNote(),
|
||||
});
|
||||
notificationStore.success(
|
||||
'New API token generated. Copy it below while it is still visible.',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
getAPITokenGenerateErrorMessage,
|
||||
getAPITokenManagementLocationMessage,
|
||||
getAPITokenRevealSettingsNote,
|
||||
getAPITokensLoadErrorMessage,
|
||||
getAPITokenRevokeErrorMessage,
|
||||
} from '@/utils/apiTokenPresentation';
|
||||
@@ -12,6 +14,15 @@ describe('apiTokenPresentation', () => {
|
||||
expect(getAPITokenRevokeErrorMessage()).toBe('Unable to revoke the API token.');
|
||||
});
|
||||
|
||||
it('returns canonical API token settings location copy', () => {
|
||||
expect(getAPITokenManagementLocationMessage()).toBe(
|
||||
'Create or rotate API tokens in Settings → API Access.',
|
||||
);
|
||||
expect(getAPITokenRevealSettingsNote()).toBe(
|
||||
'Copy this token now. You can reopen this dialog from Settings → API Access while this page stays open.',
|
||||
);
|
||||
});
|
||||
|
||||
it('surfaces token scope denial copy for generate failures', () => {
|
||||
const error = Object.assign(
|
||||
new Error('Cannot grant scope "monitoring:read": your token does not have this scope'),
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
export function getAPITokensLoadErrorMessage(): string {
|
||||
return 'Unable to load API tokens.';
|
||||
}
|
||||
|
||||
import { API_SCOPE_LABELS } from '@/constants/apiScopes';
|
||||
|
||||
type APITokenErrorShape = {
|
||||
@@ -10,6 +6,18 @@ type APITokenErrorShape = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export function getAPITokensLoadErrorMessage(): string {
|
||||
return 'Unable to load API tokens.';
|
||||
}
|
||||
|
||||
export function getAPITokenManagementLocationMessage(): string {
|
||||
return 'Create or rotate API tokens in Settings → API Access.';
|
||||
}
|
||||
|
||||
export function getAPITokenRevealSettingsNote(): string {
|
||||
return 'Copy this token now. You can reopen this dialog from Settings → API Access while this page stays open.';
|
||||
}
|
||||
|
||||
export function getAPITokenGenerateErrorMessage(error?: unknown): string {
|
||||
if (error && typeof error === 'object') {
|
||||
const typedError = error as APITokenErrorShape;
|
||||
|
||||
Reference in New Issue
Block a user