vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 1h ago
4b8c9e1d
| 1 | {{define "account"}} |
| 2 | {{template "header" .}} |
| 3 | <div class="page-head"> |
| 4 | <div class="page-head-main"> |
| 5 | <h1>Account</h1> |
| 6 | <p class="page-sub"><strong>{{.Account.Name}}</strong> · your account name is permanent</p> |
| 7 | </div> |
| 8 | </div> |
| 9 | |
| 10 | <h2>Appearance</h2> |
| 11 | <div class="card form-card"> |
| 12 | <label>Theme |
| 13 | <select data-theme-select aria-label="Theme"> |
| 14 | <option value="system" selected>System</option> |
| 15 | <option value="everforest-dark-hard">Everforest Dark · Hard</option> |
| 16 | <option value="everforest-dark-medium">Everforest Dark · Medium</option> |
| 17 | <option value="everforest-dark-soft">Everforest Dark · Soft</option> |
| 18 | <option value="everforest-light-hard">Everforest Light · Hard</option> |
| 19 | <option value="everforest-light-medium">Everforest Light · Medium</option> |
| 20 | <option value="everforest-light-soft">Everforest Light · Soft</option> |
| 21 | <option value="catppuccin-mocha">Catppuccin Mocha</option> |
| 22 | <option value="catppuccin-latte">Catppuccin Latte</option> |
| 23 | <option value="nord">Nord</option> |
| 24 | <option value="solarized-light">Solarized Light</option> |
| 25 | <option value="rose-pine-moon">Rosé Pine Moon</option> |
| 26 | <option value="rose-pine-dawn">Rosé Pine Dawn</option> |
| 27 | <option value="kanagawa-wave">Kanagawa Wave</option> |
| 28 | <option value="dracula">Dracula</option> |
| 29 | <option value="tokyo-night">Tokyo Night</option> |
| 30 | <option value="ayu-light">Ayu Light</option> |
| 31 | {{range .InstanceThemes}}<option value="{{.ID}}">{{.Name}}</option>{{end}} |
| 32 | </select> |
| 33 | </label> |
| 34 | <label>Text size |
| 35 | <select data-font-scale-select aria-label="Text size"> |
| 36 | <option value="small">Small · 90%</option> |
| 37 | <option value="medium" selected>Medium · 100%</option> |
| 38 | <option value="large">Large · 110%</option> |
| 39 | </select> |
| 40 | </label> |
| 41 | <p class="muted">Both are stored in this browser, not on your account. System follows your operating system and uses the medium contrast. Text size scales labels, prose, and code.</p> |
| 42 | </div> |
| 43 | |
| 44 | <h2>SSH keys</h2> |
| 45 | {{if .Account.Keys}} |
| 46 | <div class="panel"> |
| 47 | <table> |
| 48 | <thead><tr><th>Fingerprint</th><th>Type</th><th>Comment</th><th></th></tr></thead> |
| 49 | <tbody> |
| 50 | {{range .Account.Keys}} |
| 51 | <tr> |
| 52 | <td class="sha">{{.Fingerprint}}</td> |
| 53 | <td class="muted">{{.Type}}</td> |
| 54 | <td class="muted">{{.Comment}}</td> |
| 55 | <td> |
| 56 | <form method="post" action="/-/account/keys/remove"> |
| 57 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> |
| 58 | <input type="hidden" name="fingerprint" value="{{.Fingerprint}}"> |
| 59 | <button class="link" type="submit">remove</button> |
| 60 | </form> |
| 61 | </td> |
| 62 | </tr> |
| 63 | {{end}} |
| 64 | </tbody> |
| 65 | </table> |
| 66 | </div> |
| 67 | {{else}} |
| 68 | <div class="empty-state"><p>No SSH keys yet.</p></div> |
| 69 | {{end}} |
| 70 | <form method="post" action="/-/account/keys" class="form-block"> |
| 71 | <input type="hidden" name="csrf" value="{{.CSRF}}"> |
| 72 | <label for="public-key">Add public key</label> |
| 73 | <div class="card form-card key-card"> |
| 74 | <textarea id="public-key" name="key" rows="3" placeholder="ssh-ed25519 AAAA... you@example.com" required></textarea> |
| 75 | </div> |
| 76 | <div class="form-actions"><button type="submit">{{icon "plus"}}Add SSH key</button></div> |
| 77 | </form> |
| 78 | |
| 79 | <h2>Passkeys</h2> |
| 80 | {{if .Credentials}} |
| 81 | <div class="panel"> |
| 82 | <table> |
| 83 | <thead><tr><th>Credential</th><th>Label</th><th>Created</th><th>Last used</th><th></th></tr></thead> |
| 84 | <tbody> |
| 85 | {{range .Credentials}} |
| 86 | <tr> |
| 87 | <td class="sha">{{credid .ID}}</td> |
| 88 | <td> |
| 89 | <form method="post" action="/-/account/passkeys/rename" class="inline"> |
| 90 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> |
| 91 | <input type="hidden" name="credential" value="{{credid .ID}}"> |
| 92 | <input type="text" name="label" value="{{.Label}}" maxlength="128" placeholder="passkey" aria-label="Passkey label"> |
| 93 | <button class="link" type="submit">rename</button> |
| 94 | </form> |
| 95 | </td> |
| 96 | <td class="muted">{{since .Created.Unix}}</td> |
| 97 | <td class="muted">{{if .LastUsed.IsZero}}never{{else}}{{since .LastUsed.Unix}}{{end}}</td> |
| 98 | <td> |
| 99 | <form method="post" action="/-/account/passkeys/remove" data-confirm="Remove this passkey? Sessions that signed in with it end now."> |
| 100 | <input type="hidden" name="csrf" value="{{$.CSRF}}"> |
| 101 | <input type="hidden" name="credential" value="{{credid .ID}}"> |
| 102 | <button class="link" type="submit">remove</button> |
| 103 | </form> |
| 104 | </td> |
| 105 | </tr> |
| 106 | {{end}} |
| 107 | </tbody> |
| 108 | </table> |
| 109 | </div> |
| 110 | <p class="muted">A label is only what you call a passkey; renaming one does not change what it can do. Removing one ends the sessions that signed in with it. Your last passkey cannot be removed while it is your only way into your account: register another passkey or add an SSH key first.</p> |
| 111 | {{else}} |
| 112 | <div class="empty-state"><p>No passkeys yet.</p></div> |
| 113 | {{end}} |
| 114 | |
| 115 | {{if .Enabled}} |
| 116 | <h2>Add a passkey</h2> |
| 117 | <form data-auth="passkey" class="form-block"> |
| 118 | <div class="card form-card"> |
| 119 | <label>Label <input type="text" name="label" placeholder="laptop, phone, security key"></label> |
| 120 | <p class="error" data-auth-error></p> |
| 121 | </div> |
| 122 | <div class="form-actions"><button type="submit">{{icon "plus"}}Register this device</button></div> |
| 123 | </form> |
| 124 | {{else}} |
| 125 | <p class="muted">{{.Reason}}</p> |
| 126 | {{end}} |
| 127 | |
| 128 | <h2>Sessions</h2> |
| 129 | {{if .Sessions}} |
| 130 | <div class="panel"> |
| 131 | <table> |
| 132 | <thead><tr><th>Created</th><th>Last seen</th><th>Expires</th><th>Client</th></tr></thead> |
| 133 | <tbody> |
| 134 | {{range .Sessions}} |
| 135 | <tr> |
| 136 | <td class="muted">{{since .Created.Unix}}</td> |
| 137 | <td class="muted">{{since .LastSeen.Unix}}</td> |
| 138 | <td class="muted">{{.Expires.Format "2006-01-02 15:04"}}</td> |
| 139 | <td class="muted">{{.UserAgent}}</td> |
| 140 | </tr> |
| 141 | {{end}} |
| 142 | </tbody> |
| 143 | </table> |
| 144 | </div> |
| 145 | {{else}} |
| 146 | <div class="empty-state"><p>No sessions.</p></div> |
| 147 | {{end}} |
| 148 | {{template "footer" .}} |
| 149 | {{end}} |
| 150 |