Keep client secrets in add-on storage; host only verifies and serves the local API.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"use strict";
|
||||
|
||||
const CLIENT_NAME_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
||||
const TOKEN_PREFIX = "fab_";
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
||||
}
|
||||
|
||||
async function sha256Hex(text) {
|
||||
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
||||
return Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
function publicClient(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
created: row.created,
|
||||
last_used: row.last_used || null,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadClients() {
|
||||
const data = await browser.storage.local.get("clients");
|
||||
return Array.isArray(data.clients) ? data.clients : [];
|
||||
}
|
||||
|
||||
async function saveClients(clients) {
|
||||
await browser.storage.local.set({ clients });
|
||||
}
|
||||
|
||||
async function listClients() {
|
||||
return (await loadClients()).map(publicClient);
|
||||
}
|
||||
|
||||
async function createClient(name) {
|
||||
name = String(name || "").trim();
|
||||
if (!CLIENT_NAME_RE.test(name)) {
|
||||
throw new Error("Name must be 1–64 characters: A-Za-z0-9._-");
|
||||
}
|
||||
const clients = await loadClients();
|
||||
if (clients.some((row) => row.name === name)) {
|
||||
throw new Error("A client with that name already exists");
|
||||
}
|
||||
const raw = new Uint8Array(32);
|
||||
crypto.getRandomValues(raw);
|
||||
const token = TOKEN_PREFIX + Array.from(raw, (b) => b.toString(16).padStart(2, "0")).join("");
|
||||
const idBytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(idBytes);
|
||||
const id = Array.from(idBytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
||||
clients.push({
|
||||
id,
|
||||
name,
|
||||
token_hash: await sha256Hex(token),
|
||||
created: nowIso(),
|
||||
last_used: null,
|
||||
});
|
||||
await saveClients(clients);
|
||||
return { id, name, token };
|
||||
}
|
||||
|
||||
async function revokeClient(id) {
|
||||
const clients = await loadClients();
|
||||
const next = clients.filter((row) => row.id !== id && row.name !== id);
|
||||
if (next.length === clients.length) {
|
||||
throw new Error("No such client");
|
||||
}
|
||||
await saveClients(next);
|
||||
return id;
|
||||
}
|
||||
|
||||
async function findClientByTokenHash(tokenHash) {
|
||||
const clients = await loadClients();
|
||||
const row = clients.find((item) => item.token_hash === tokenHash);
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
row.last_used = nowIso();
|
||||
await saveClients(clients);
|
||||
return publicClient(row);
|
||||
}
|
||||
Reference in New Issue
Block a user