Remove caller-managed CardDAV secret references
This commit is contained in:
@@ -450,7 +450,7 @@ export async function listAddressSyncSources(
|
|||||||
|
|
||||||
export function discoverCardDavAddressBooks(
|
export function discoverCardDavAddressBooks(
|
||||||
settings: ApiSettings,
|
settings: ApiSettings,
|
||||||
payload: { url: string; auth_type: "none" | "basic" | "bearer"; username?: string | null; password?: string | null; bearer_token?: string | null; credential_ref?: string | null }
|
payload: { url: string; auth_type: "none" | "basic" | "bearer"; username?: string | null; password?: string | null; bearer_token?: string | null }
|
||||||
): Promise<AddressCardDavAddressBook[]> {
|
): Promise<AddressCardDavAddressBook[]> {
|
||||||
return apiFetch<AddressCardDavDiscoveryResponse>(settings, "/api/v1/addresses/carddav/discover", {
|
return apiFetch<AddressCardDavDiscoveryResponse>(settings, "/api/v1/addresses/carddav/discover", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -468,7 +468,6 @@ export function createCardDavSyncSource(
|
|||||||
username?: string | null;
|
username?: string | null;
|
||||||
password?: string | null;
|
password?: string | null;
|
||||||
bearer_token?: string | null;
|
bearer_token?: string | null;
|
||||||
credential_ref?: string | null;
|
|
||||||
sync_direction: "read_only" | "import" | "export" | "two_way";
|
sync_direction: "read_only" | "import" | "export" | "two_way";
|
||||||
read_only?: boolean | null;
|
read_only?: boolean | null;
|
||||||
sync_token?: string | null;
|
sync_token?: string | null;
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import {
|
|||||||
ConfirmDialog,
|
ConfirmDialog,
|
||||||
Dialog,
|
Dialog,
|
||||||
DisabledActionTooltip,
|
DisabledActionTooltip,
|
||||||
|
DismissibleAlert,
|
||||||
ExplorerTree,
|
ExplorerTree,
|
||||||
fetchAuthGroups,
|
fetchAuthGroups,
|
||||||
FormField,
|
FormField,
|
||||||
LoadingFrame,
|
LoadingFrame,
|
||||||
|
PasswordField,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
ToggleSwitch,
|
ToggleSwitch,
|
||||||
hasScope,
|
hasScope,
|
||||||
@@ -141,7 +143,6 @@ type CardDavFormState = {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
bearer_token: string;
|
bearer_token: string;
|
||||||
credential_ref: string;
|
|
||||||
sync_direction: "read_only" | "import" | "export" | "two_way";
|
sync_direction: "read_only" | "import" | "export" | "two_way";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -198,7 +199,6 @@ const EMPTY_CARDDAV_FORM: CardDavFormState = {
|
|||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
bearer_token: "",
|
bearer_token: "",
|
||||||
credential_ref: "",
|
|
||||||
sync_direction: "read_only"
|
sync_direction: "read_only"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1528,8 +1528,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
|
|||||||
auth_type: cardDavForm.auth_type,
|
auth_type: cardDavForm.auth_type,
|
||||||
username: cardDavForm.username || null,
|
username: cardDavForm.username || null,
|
||||||
password: cardDavForm.password || null,
|
password: cardDavForm.password || null,
|
||||||
bearer_token: cardDavForm.bearer_token || null,
|
bearer_token: cardDavForm.bearer_token || null
|
||||||
credential_ref: cardDavForm.credential_ref || null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1570,7 +1569,6 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
|
|||||||
username: cardDavForm.username || null,
|
username: cardDavForm.username || null,
|
||||||
password: cardDavForm.password || null,
|
password: cardDavForm.password || null,
|
||||||
bearer_token: cardDavForm.bearer_token || null,
|
bearer_token: cardDavForm.bearer_token || null,
|
||||||
credential_ref: cardDavForm.credential_ref || null,
|
|
||||||
sync_direction: cardDavForm.sync_direction,
|
sync_direction: cardDavForm.sync_direction,
|
||||||
read_only: cardDavForm.sync_direction === "read_only" || cardDavForm.sync_direction === "import"
|
read_only: cardDavForm.sync_direction === "read_only" || cardDavForm.sync_direction === "import"
|
||||||
});
|
});
|
||||||
@@ -1927,8 +1925,8 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="workspace-data-page module-entry-page address-book-page address-book-fullscreen">
|
<div className="workspace-data-page module-entry-page address-book-page address-book-fullscreen">
|
||||||
{error && <div className="alert alert-error address-error">{error}</div>}
|
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||||
{notice && !error && <div className="alert alert-success address-error">{notice}</div>}
|
{notice && !error && <DismissibleAlert tone="success" resetKey={notice}>{notice}</DismissibleAlert>}
|
||||||
|
|
||||||
<LoadingFrame loading={loading} label="Loading address books..." className="address-workspace-frame">
|
<LoadingFrame loading={loading} label="Loading address books..." className="address-workspace-frame">
|
||||||
<div className="address-book-workspace">
|
<div className="address-book-workspace">
|
||||||
@@ -2273,18 +2271,15 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
|
|||||||
<input value={cardDavForm.username} onChange={(event) => setCardDavForm((current) => ({ ...current, username: event.target.value }))} />
|
<input value={cardDavForm.username} onChange={(event) => setCardDavForm((current) => ({ ...current, username: event.target.value }))} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="Password">
|
<FormField label="Password">
|
||||||
<input type="password" value={cardDavForm.password} onChange={(event) => setCardDavForm((current) => ({ ...current, password: event.target.value }))} />
|
<PasswordField value={cardDavForm.password} onValueChange={(value) => setCardDavForm((current) => ({ ...current, password: value }))} autoComplete="new-password" />
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{cardDavForm.auth_type === "bearer" &&
|
{cardDavForm.auth_type === "bearer" &&
|
||||||
<FormField label="Bearer token">
|
<FormField label="Bearer token">
|
||||||
<input type="password" value={cardDavForm.bearer_token} onChange={(event) => setCardDavForm((current) => ({ ...current, bearer_token: event.target.value }))} />
|
<PasswordField value={cardDavForm.bearer_token} onValueChange={(value) => setCardDavForm((current) => ({ ...current, bearer_token: value }))} autoComplete="new-password" />
|
||||||
</FormField>
|
</FormField>
|
||||||
}
|
}
|
||||||
<FormField label="Credential reference">
|
|
||||||
<input value={cardDavForm.credential_ref} onChange={(event) => setCardDavForm((current) => ({ ...current, credential_ref: event.target.value }))} placeholder="env:GOVOPLAN_CARDDAV_PASSWORD" />
|
|
||||||
</FormField>
|
|
||||||
{cardDavDiscovery.length > 0 &&
|
{cardDavDiscovery.length > 0 &&
|
||||||
<div className="address-sync-result-list">
|
<div className="address-sync-result-list">
|
||||||
{cardDavDiscovery.map((item) => (
|
{cardDavDiscovery.map((item) => (
|
||||||
@@ -2325,7 +2320,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
|
|||||||
{syncInspector.source.read_only && <StatusBadge status="read-only" />}
|
{syncInspector.source.read_only && <StatusBadge status="read-only" />}
|
||||||
</div>
|
</div>
|
||||||
<p className="muted">Last attempt: {formatDateTime(syncInspector.source.last_attempted_at)} · Last success: {formatDateTime(syncInspector.source.last_success_at)}</p>
|
<p className="muted">Last attempt: {formatDateTime(syncInspector.source.last_attempted_at)} · Last success: {formatDateTime(syncInspector.source.last_success_at)}</p>
|
||||||
{syncInspector.source.last_error && <p className="alert alert-error">{syncInspector.source.last_error}</p>}
|
{syncInspector.source.last_error && <DismissibleAlert tone="danger" resetKey={syncInspector.source.last_error}>{syncInspector.source.last_error}</DismissibleAlert>}
|
||||||
<div className="button-row compact-actions">
|
<div className="button-row compact-actions">
|
||||||
<ReasonedButton
|
<ReasonedButton
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2457,7 +2452,7 @@ export default function AddressBookPage({ settings, auth, onAuthChange }: Props)
|
|||||||
{conflictDialog.resolution && <StatusBadge status={conflictDialog.resolution} />}
|
{conflictDialog.resolution && <StatusBadge status={conflictDialog.resolution} />}
|
||||||
</div>
|
</div>
|
||||||
{conflictDialog.metadata?.message && <p className="muted">{String(conflictDialog.metadata.message)}</p>}
|
{conflictDialog.metadata?.message && <p className="muted">{String(conflictDialog.metadata.message)}</p>}
|
||||||
{!canApplyRemoteConflict(conflictDialog) && <p className="alert alert-warning">This conflict predates stored field payloads or came from a stale write. It can be marked resolved or ignored, but the remote value cannot be applied automatically.</p>}
|
{!canApplyRemoteConflict(conflictDialog) && <DismissibleAlert tone="warning" dismissible={false}>This conflict predates stored field payloads or came from a stale write. It can be marked resolved or ignored, but the remote value cannot be applied automatically.</DismissibleAlert>}
|
||||||
</div>
|
</div>
|
||||||
<div className="address-conflict-grid">
|
<div className="address-conflict-grid">
|
||||||
<div className="address-conflict-grid-header">Field</div>
|
<div className="address-conflict-grid-header">Field</div>
|
||||||
|
|||||||
@@ -617,37 +617,6 @@
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid.two {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid.three {
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid.four {
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-actions {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-actions .btn {
|
|
||||||
align-items: center;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 30px;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strong-link {
|
.strong-link {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -677,9 +646,6 @@
|
|||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
.address-book-workspace,
|
.address-book-workspace,
|
||||||
.form-grid.two,
|
|
||||||
.form-grid.three,
|
|
||||||
.form-grid.four,
|
|
||||||
.address-form-row-email,
|
.address-form-row-email,
|
||||||
.address-form-row-phone,
|
.address-form-row-phone,
|
||||||
.address-form-row-postal {
|
.address-form-row-postal {
|
||||||
|
|||||||
Reference in New Issue
Block a user