Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| An access token can be regenerated: a new secret, its name, permissions, repositories and reach kept, and the old secret refused at once. Its page (yours under Settings, a workspace's under its settings for owners) has Regenerate with a new expiry, within the workspace's rules on lifetime, and shows the secret once; identity's regenerate_token logs it to the security log or the workspace's audit log, and refuses a revoked token. The token form's quick permissions have All, every resource at its highest level. A project's Observability settings no longer list the token g1t gives its apps on g1t.page: it is g1t's, and the page says apps there need none. | 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 | } |