New token form: only templates within what the token's user may do
A token never does more than its user, so offering wider templates only misled. On the Users page the templates follow the token's user, not the administrator making it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5cd22f52d4
commit
0e663ab36c
3 files changed
+21
-5
No files matched your search
@@ -9,7 +9,7 @@ export async function render(el) {
|
||||
const tokensEl = h("div");
|
||||
const drawTokens = async () => {
|
||||
const tokens = await get("/me/tokens");
|
||||
mount(tokensEl, tokenTable(tokens, drawTokens), newTokenForm("/me/tokens", grants, drawTokens));
|
||||
mount(tokensEl, tokenTable(tokens, drawTokens), newTokenForm("/me/tokens", grants, drawTokens, { admin: me.admin, grants: me.user.grants }));
|
||||
};
|
||||
const cur = input({ type: "password", autocomplete: "current-password" });
|
||||
const pw1 = input({ type: "password", autocomplete: "new-password" });
|
||||
|
||||
@@ -104,7 +104,7 @@ export async function tokensDialog(u, grants, own) {
|
||||
const base = own ? "/me/tokens" : "/users/" + u.id + "/tokens";
|
||||
const draw = async () => {
|
||||
const tokens = await get(base);
|
||||
mount(box, tokenTable(tokens, draw), newTokenForm(base, grants, draw));
|
||||
mount(box, tokenTable(tokens, draw), newTokenForm(base, grants, draw, u));
|
||||
};
|
||||
await draw();
|
||||
await modal("API tokens of " + u.username, box, [{ label: "Close", value: null }], { wide: true });
|
||||
@@ -185,13 +185,26 @@ async function rotateDialog(t, redraw) {
|
||||
await modal("Rotate token " + t.name, box, [{ label: "Close", value: null }], { sticky: true });
|
||||
}
|
||||
|
||||
export function newTokenForm(base, grants, redraw) {
|
||||
// coveredBy reports whether a user (admin, or grants) may do what a grant
|
||||
// string ("records:write names=..." ) allows, by resource and access. A
|
||||
// filter on either side only narrows the token further, so it is ignored.
|
||||
function coveredBy(user, grant) {
|
||||
if (!user || user.admin) return true;
|
||||
const [res, acc] = grant.split(/\s+/)[0].split(":");
|
||||
return (user.grants || []).some((g) => (g.resource === "*" || g.resource === res) && (g.access === "write" || acc === "read"));
|
||||
}
|
||||
|
||||
// newTokenForm makes a token for owner (the user it belongs to). Its
|
||||
// templates are only the roles within what owner may do: a token never
|
||||
// does more than its user, so offering more would only mislead.
|
||||
export function newTokenForm(base, grants, redraw, owner) {
|
||||
const err = h("div");
|
||||
const out = h("div");
|
||||
const name = input({ placeholder: "e.g. certbot on web01" });
|
||||
const exp = select([["", "never expires"], ["30d", "30 days"], ["90d", "90 days"], ["365d", "1 year"]], "90d");
|
||||
const gr = textarea("", { class: "mono", rows: 3, placeholder: "optional restriction, e.g.\nrecords:write zones=example.com names=_acme-challenge.* types=TXT" });
|
||||
const tmpl = select([["", "Insert a template…"], ...Object.keys(grants.roles).map((r) => [r, r])], "");
|
||||
const roles = Object.keys(grants.roles).filter((r) => grants.roles[r].every((g) => coveredBy(owner, g)));
|
||||
const tmpl = select([["", "Insert a template…"], ...roles.map((r) => [r, r])], "");
|
||||
tmpl.addEventListener("change", () => {
|
||||
if (!tmpl.value) return;
|
||||
gr.value = (gr.value.trim() ? gr.value.trim() + "\n" : "") + grants.roles[tmpl.value].join("\n");
|
||||
@@ -203,7 +216,7 @@ export function newTokenForm(base, grants, redraw) {
|
||||
field("Name", name),
|
||||
field("Expires", exp),
|
||||
field("Restrict to", gr, "Leave empty for all of the user's permissions. A token can never do more than its user."),
|
||||
h("span"), tmpl),
|
||||
roles.length ? [h("span"), tmpl] : null),
|
||||
err,
|
||||
h("div", { class: "actions", style: { marginTop: "8px" } }, h("button", { class: "primary", onclick: async () => {
|
||||
mount(err); mount(out);
|
||||
|
||||
Reference in new issue
Block a user