| 1 | /** |
| 2 | * Regenerating a token: a new secret for it, everything else kept, the |
| 3 | * old secret refused from then on. For rotating a token without remaking |
| 4 | * it, or replacing one that may have leaked. The page's action answers |
| 5 | * `intent=regenerate` with `{ secret, token }`, shown once by `TokenCreated`. |
| 6 | */ |
| 7 | import { RefreshCw } from "lucide-react"; |
| 8 | import { useState } from "react"; |
| 9 | import { Form } from "react-router"; |
| 10 | |
| 11 | import { |
| 12 | AlertDialog, |
| 13 | AlertDialogAction, |
| 14 | AlertDialogCancel, |
| 15 | AlertDialogContent, |
| 16 | AlertDialogDescription, |
| 17 | AlertDialogFooter, |
| 18 | AlertDialogHeader, |
| 19 | AlertDialogTitle, |
| 20 | AlertDialogTrigger, |
| 21 | } from "./ui/alert-dialog"; |
| 22 | import { Button } from "./ui/button"; |
| 23 | import { SelectField } from "./ui/select"; |
| 24 | import { DEFAULT_EXPIRY_DAYS, EXPIRY_DAYS, describeDays } from "../lib/access-tokens"; |
| 25 | |
| 26 | export function RegenerateToken({ name }: { name: string }) { |
| 27 | const [expires, setExpires] = useState(String(DEFAULT_EXPIRY_DAYS)); |
| 28 | const options = [...EXPIRY_DAYS.map((days) => ({ value: String(days), label: describeDays(days) })), { value: "never", label: "No expiration" }]; |
| 29 | return ( |
| 30 | <section className="rounded-xl border border-line p-4"> |
| 31 | <h2 className="text-sm font-medium">Regenerate this token</h2> |
| 32 | <p className="mt-1 text-xs text-muted"> |
| 33 | A new secret, with the same name, permissions and reach. Anything using the old one stops working at once, until you give it the new |
| 34 | one. |
| 35 | </p> |
| 36 | <div className="mt-3 flex flex-wrap items-center gap-2"> |
| 37 | <SelectField aria-label="Expires" className="w-40" value={expires} onValueChange={setExpires} options={options} /> |
| 38 | <AlertDialog> |
| 39 | <AlertDialogTrigger asChild> |
| 40 | <Button variant="outline"> |
| 41 | <RefreshCw size={14} /> |
| 42 | Regenerate |
| 43 | </Button> |
| 44 | </AlertDialogTrigger> |
| 45 | <AlertDialogContent> |
| 46 | <Form method="post" className="grid gap-4"> |
| 47 | <input type="hidden" name="intent" value="regenerate" /> |
| 48 | <input type="hidden" name="expires" value={expires} /> |
| 49 | <AlertDialogHeader> |
| 50 | <AlertDialogTitle>Regenerate {name}?</AlertDialogTitle> |
| 51 | <AlertDialogDescription> |
| 52 | Its old secret stops working now. The new one is shown once, and {expires === "never" ? "does not expire" : `expires in ${describeDays(Number(expires))}`}. |
| 53 | </AlertDialogDescription> |
| 54 | </AlertDialogHeader> |
| 55 | <AlertDialogFooter> |
| 56 | <AlertDialogCancel>Cancel</AlertDialogCancel> |
| 57 | <AlertDialogAction asChild> |
| 58 | <button type="submit">Regenerate token</button> |
| 59 | </AlertDialogAction> |
| 60 | </AlertDialogFooter> |
| 61 | </Form> |
| 62 | </AlertDialogContent> |
| 63 | </AlertDialog> |
| 64 | </div> |
| 65 | </section> |
| 66 | ); |
| 67 | } |