Extension: request host permission dynamically instead of relying on CORS

Backend CORS is disabled by default (origin: false), and an unpacked
extension's ID isn't fixed/predictable per install anyway, so allowlisting
it server-side would be awkward. options.js now calls
chrome.permissions.request() for the entered base URL on Save — once
granted, the extension's own fetches bypass CORS entirely for that origin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-09-01 18:49:28 +00:00
parent 4d3774ef0d
commit 28d3db8039
2 changed files with 20 additions and 0 deletions

View file

@ -11,6 +11,8 @@
"https://login.newbook.cloud/*" "https://login.newbook.cloud/*"
], ],
"optional_host_permissions": ["http://*/*", "https://*/*"],
"background": { "background": {
"service_worker": "background.js" "service_worker": "background.js"
}, },

View file

@ -9,9 +9,27 @@ async function load() {
// not the popup) so the value can be corrected without regenerating the key. // not the popup) so the value can be corrected without regenerating the key.
} }
// tablet-checkin's own base URL isn't known in advance (dev today, a
// different production URL later), so it can't be pre-declared in the
// manifest's host_permissions — Chrome would need republishing/reloading
// every time the URL changed. Requesting it dynamically here instead (needs
// a user gesture, which this Save click provides) means the extension's own
// fetches bypass CORS entirely for that origin once granted, rather than
// depending on the server sending back Access-Control-Allow-Origin for an
// extension origin ID that isn't fixed/predictable per install anyway.
async function save() { async function save() {
const baseUrl = baseUrlInput.value.trim().replace(/\/$/, ''); const baseUrl = baseUrlInput.value.trim().replace(/\/$/, '');
const apiKey = apiKeyInput.value.trim(); const apiKey = apiKeyInput.value.trim();
if (baseUrl) {
const granted = await chrome.permissions.request({ origins: [`${baseUrl}/*`] });
if (!granted) {
statusEl.textContent = 'Not saved — permission for that URL was denied. This extension needs it to talk to tablet-checkin.';
statusEl.style.color = '#dc2626';
return;
}
}
await chrome.storage.local.set({ baseUrl, apiKey }); await chrome.storage.local.set({ baseUrl, apiKey });
statusEl.textContent = 'Saved.'; statusEl.textContent = 'Saved.';
statusEl.style.color = '#16a34a'; statusEl.style.color = '#16a34a';