Skip to content

Commit 49b80fe

Browse files
authored
Add self-managed vault recovery option for lost secret key (#8619)
1 parent 5ca4d74 commit 49b80fe

1 file changed

Lines changed: 60 additions & 27 deletions

File tree

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/server-wallets/wallets/vault-recovery-card.client.tsx

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export function VaultRecoveryCard({
4545
const [dialogOpen, setDialogOpen] = useState(false);
4646
const [confirmed, setConfirmed] = useState(false);
4747
const [secretKeyInput, setSecretKeyInput] = useState("");
48+
// Option for managed vault users who lost their secret key
49+
const [manageKeysSelf, setManageKeysSelf] = useState(false);
4850
// For ejected vault key download flow
4951
const [keysConfirmed, setKeysConfirmed] = useState(false);
5052
const [keysDownloaded, setKeysDownloaded] = useState(false);
@@ -55,6 +57,9 @@ export function VaultRecoveryCard({
5557
);
5658
const wasManagedVault = !!engineCloudService?.encryptedAdminKey;
5759

60+
// Will create ejected vault if: wasn't managed, OR user chose to manage keys themselves
61+
const willCreateEjectedVault = !wasManagedVault || manageKeysSelf;
62+
5863
const isInsufficientScopeError =
5964
errorMessage.includes("AUTH_INSUFFICIENT_SCOPE") ||
6065
errorMessage.toLowerCase().includes("insufficient scope");
@@ -65,16 +70,16 @@ export function VaultRecoveryCard({
6570

6671
const result = await createVaultAccountAndAccessToken({
6772
project,
68-
// Only pass secret key if it was a managed vault and user provided one
69-
projectSecretKey: wasManagedVault ? secretKeyInput : undefined,
73+
// Only pass secret key if creating managed vault (not ejected)
74+
projectSecretKey: willCreateEjectedVault ? undefined : secretKeyInput,
7075
});
7176

7277
return result;
7378
},
7479
onSuccess: () => {
75-
// For managed vaults, reload immediately (keys are encrypted with secret key)
80+
// For managed vaults (with secret key), reload immediately
7681
// For ejected vaults, show the key download dialog first
77-
if (wasManagedVault) {
82+
if (!willCreateEjectedVault) {
7883
window.location.reload();
7984
}
8085
// For ejected vaults, we stay in the dialog to show the admin key
@@ -110,10 +115,11 @@ export function VaultRecoveryCard({
110115
window.location.reload();
111116
};
112117

113-
// For managed vaults, require secret key input
114-
const canProceed = wasManagedVault
115-
? confirmed && secretKeyInput.trim().length > 0
116-
: confirmed;
118+
// For managed vaults creating managed vault, require secret key input
119+
// For ejected vaults (or managed choosing to manage keys), just need confirmation
120+
const canProceed = willCreateEjectedVault
121+
? confirmed
122+
: confirmed && secretKeyInput.trim().length > 0;
117123

118124
if (!isInsufficientScopeError) {
119125
// Show standard error for non-scope errors
@@ -164,7 +170,7 @@ export function VaultRecoveryCard({
164170
</AlertDialogTrigger>
165171
<AlertDialogContent>
166172
{/* Show key download UI for ejected vaults after success */}
167-
{!wasManagedVault && regenerateMutation.data ? (
173+
{willCreateEjectedVault && regenerateMutation.data ? (
168174
<>
169175
<AlertDialogHeader>
170176
<AlertDialogTitle>Save your Vault Admin Key</AlertDialogTitle>
@@ -267,24 +273,50 @@ export function VaultRecoveryCard({
267273
</div>
268274

269275
{wasManagedVault && (
270-
<div className="space-y-2">
271-
<label
272-
htmlFor="secret-key-input"
273-
className="font-medium text-sm"
274-
>
275-
Enter your project Secret Key
276-
</label>
277-
<Input
278-
id="secret-key-input"
279-
type="password"
280-
placeholder="sk_..."
281-
value={secretKeyInput}
282-
onChange={(e) => setSecretKeyInput(e.target.value)}
283-
/>
284-
<p className="text-muted-foreground text-xs">
285-
Your secret key is required to create a managed
286-
vault.
287-
</p>
276+
<div className="space-y-3">
277+
{!manageKeysSelf && (
278+
<>
279+
<label
280+
htmlFor="secret-key-input"
281+
className="font-medium text-sm"
282+
>
283+
Enter your project Secret Key
284+
</label>
285+
<Input
286+
id="secret-key-input"
287+
type="password"
288+
placeholder="sk_..."
289+
value={secretKeyInput}
290+
onChange={(e) =>
291+
setSecretKeyInput(e.target.value)
292+
}
293+
/>
294+
<p className="text-muted-foreground text-xs">
295+
Your secret key is required to create a managed
296+
vault.
297+
</p>
298+
</>
299+
)}
300+
301+
<div className="flex items-start gap-2 border-t pt-3">
302+
<Checkbox
303+
id="manage-keys-self"
304+
checked={manageKeysSelf}
305+
onCheckedChange={(checked) => {
306+
setManageKeysSelf(checked === true);
307+
if (checked) {
308+
setSecretKeyInput("");
309+
}
310+
}}
311+
/>
312+
<label
313+
htmlFor="manage-keys-self"
314+
className="cursor-pointer text-sm leading-tight"
315+
>
316+
I lost my secret key and want to manage vault keys
317+
myself (advanced)
318+
</label>
319+
</div>
288320
</div>
289321
)}
290322

@@ -312,6 +344,7 @@ export function VaultRecoveryCard({
312344
onClick={() => {
313345
setConfirmed(false);
314346
setSecretKeyInput("");
347+
setManageKeysSelf(false);
315348
}}
316349
>
317350
Cancel

0 commit comments

Comments
 (0)