@gjermundgaraba/clankerauth-dev 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ data: `)}\n`,t+`
37
37
  <button class="danger" data-resource-delete="${$(e.identifier)}">Delete resource</button>`}</article>`,sN=()=>`<h2>Add resource</h2><form id="resource-create"><label>Name<input name="name" required maxlength="100" placeholder="Notes MCP"></label><label>HTTP or HTTPS identifier<input name="identifier" type="url" required placeholder="https://notes.internal/mcp"></label><p class="help">The identifier is the token audience and cannot be changed later. Use the canonical form a client sends back, such as the application\u2019s origin root with its trailing slash.</p><label>Scopes<input name="scopes" placeholder="notes:read notes:write"></label><p class="help">${QM}</p><button>Add resource +</button></form>`,cN=(e,t)=>`<article class="resource"><h3>${$(e.name)}</h3><p>${e.enabled?`Enabled`:`Disabled`} · ${e.expiresAt?`Expires ${$(ch(e.expiresAt,{dateStyle:`medium`,timeStyle:`short`}))}`:`Valid until revoked`}</p>${Object.entries(e.permissions).map(([e,t])=>`<code>${$(e)}</code><p class="help">${$(t.join(` · `))}</p>`).join(``)}
38
38
  <details><summary>Rename key</summary><form data-key-rename="${$(e.keyId)}"><label>Name<input name="name" required maxlength="100" value="${$(e.name)}"></label><button>Save name</button></form></details>
39
39
  <details><summary>Edit grants</summary><form data-key-grants="${$(e.keyId)}">${aN(t,e.permissions)}<p class="help">Saving replaces all grants, including any not currently available, with the selected scopes.</p><button>Save grants</button></form></details>
40
- <div class="actions"><button class="secondary" data-key-toggle="${$(e.keyId)}" data-enabled="${e.enabled}">${e.enabled?`Disable key`:`Enable key`}</button><button class="danger" data-key-delete="${$(e.keyId)}">Delete key</button></div></article>`,lN=e=>`<h2>Create API key</h2><form id="key-create"><label>Name<input name="name" required maxlength="100" placeholder="Backup script"></label>${aN(e,{})}<label>Expiry (optional)<input name="expiry" type="datetime-local"></label><p class="help">Blank means valid until revoked. The key is shown once.</p><button ${e.length?``:`disabled`}>Create API key</button></form>`,uN=e=>$(ch(e,{dateStyle:`medium`,timeStyle:`short`})),dN=(e,t)=>{let n=t.find(t=>t.identifier===e.resource),r=[e.approvedAt?`Approved ${uN(e.approvedAt)}`:`First party, no consent`,e.refreshedAt?`last token ${uN(e.refreshedAt)}`:`no refresh token`].join(` · `);return`<div class="allowed-resource connection"><strong>${$(n?.name??e.resource)}</strong><code>${$(e.resource)}</code><span class="muted">${$(e.scopes.join(` · `)||`No Resource scopes`)}</span><p class="help">${r}</p><button class="secondary" data-disconnect="${$(e.client_id)}" data-resource="${$(e.resource)}">Revoke</button></div>`},fN=(e,t,n,r)=>{let i=eN(e),a=$(e.client_id),o=r.map(e=>dN(e,t)).join(``)||`<p class="help">${i?`Holds no refresh token.`:`No Resource approved yet.`}</p>`,s=i?`<details><summary>Allowed Resources · ${n.length} of ${t.length}</summary><form data-client-access="${a}"><fieldset><legend>Allowed Resources</legend>${iN(t,n)||`<p class="help">Add a Resource above to configure access.</p>`}</fieldset><p class="help">This Client skips consent for the Resources ticked here. Unticking one revokes its authorization for it.</p><button>Save access</button></form></details>`:`<p class="help">May ask for any Resource; each needs your consent. Revoke a connection, or block the Client to keep it out.</p>`,c=i?`<details><summary>Edit client</summary><form data-client-edit="${a}"><label>Client name<input name="name" required maxlength="100" value="${$(e.client_name??``)}"></label><label>Redirect URIs (one per line)<textarea name="redirect_uris" required rows="3">${$(e.redirect_uris.join(`
40
+ <div class="actions"><button class="secondary" data-key-toggle="${$(e.keyId)}" data-enabled="${e.enabled}">${e.enabled?`Disable key`:`Enable key`}</button><button class="danger" data-key-delete="${$(e.keyId)}">Delete key</button></div></article>`,lN=e=>`<h2>Create API key</h2><form id="key-create"><label>Name<input name="name" required maxlength="100" placeholder="Backup script"></label>${aN(e,{})}<label>Expiry (optional)<input name="expiry" type="datetime-local"></label><p class="help">Blank means valid until revoked. The key is shown once and works within about a minute.</p><button ${e.length?``:`disabled`}>Create API key</button></form>`,uN=e=>$(ch(e,{dateStyle:`medium`,timeStyle:`short`})),dN=(e,t)=>{let n=t.find(t=>t.identifier===e.resource),r=[e.approvedAt?`Approved ${uN(e.approvedAt)}`:`First party, no consent`,e.refreshedAt?`last token ${uN(e.refreshedAt)}`:`no refresh token`].join(` · `);return`<div class="allowed-resource connection"><strong>${$(n?.name??e.resource)}</strong><code>${$(e.resource)}</code><span class="muted">${$(e.scopes.join(` · `)||`No Resource scopes`)}</span><p class="help">${r}</p><button class="secondary" data-disconnect="${$(e.client_id)}" data-resource="${$(e.resource)}">Revoke</button></div>`},fN=(e,t,n,r)=>{let i=eN(e),a=$(e.client_id),o=r.map(e=>dN(e,t)).join(``)||`<p class="help">${i?`Holds no refresh token.`:`No Resource approved yet.`}</p>`,s=i?`<details><summary>Allowed Resources · ${n.length} of ${t.length}</summary><form data-client-access="${a}"><fieldset><legend>Allowed Resources</legend>${iN(t,n)||`<p class="help">Add a Resource above to configure access.</p>`}</fieldset><p class="help">This Client skips consent for the Resources ticked here. Unticking one revokes its authorization for it.</p><button>Save access</button></form></details>`:`<p class="help">May ask for any Resource; each needs your consent. Revoke a connection, or block the Client to keep it out.</p>`,c=i?`<details><summary>Edit client</summary><form data-client-edit="${a}"><label>Client name<input name="name" required maxlength="100" value="${$(e.client_name??``)}"></label><label>Redirect URIs (one per line)<textarea name="redirect_uris" required rows="3">${$(e.redirect_uris.join(`
41
41
  `))}</textarea></label><label class="checkbox"><input type="checkbox" name="native" ${e.application_type===`native`?`checked`:``}>Native / desktop client (loopback redirect)</label><p class="help">The authentication method is fixed at registration.</p><button>Save client</button></form></details>`:``,l=[`<button class="secondary" data-revoke="${a}">Revoke all</button>`,`<button class="secondary" data-block="${a}" data-blocked="${e.blocked?`true`:`false`}">${e.blocked?`Unblock client`:`Block client`}</button>`,i&&e.token_endpoint_auth_method!==`none`?`<button class="secondary" data-rotate="${a}">Rotate secret</button>`:``,`<button class="danger" data-delete="${a}" data-managed="${i}">Delete client</button>`].join(``);return`<article class="card client"><div class="client-heading"><h3>${$(e.client_name??`Unnamed client`)}</h3><span class="tag">${e.token_endpoint_auth_method===`none`?`PUBLIC · PKCE`:`CONFIDENTIAL · PKCE`}</span></div><p class="help">${$M[e.onboarding??`managed`]}${e.blocked?` · BLOCKED`:``}</p><label>Client ID<code>${a}</code></label><label>Redirect URIs${e.redirect_uris.map(e=>`<code>${$(e)}</code>`).join(``)}</label>
42
42
  <h3>Connections</h3>${o}${s}${c}<div class="actions">${l}</div></article>`},pN=e=>`<p class="eyebrow">MANAGED REGISTRATION</p><h2>Register client</h2><form id="register"><fieldset><label>Client name<input name="name" required maxlength="100" placeholder="My MCP client"></label><label>Redirect URIs (one per line)<textarea name="redirect_uris" required rows="3" placeholder="https://app.internal/callback"></textarea></label><fieldset><legend>Allowed Resources (optional)</legend>${iN(e,[])||`<p class="help">You can configure Resource access after registration.</p>`}</fieldset><label class="checkbox"><input type="checkbox" name="native">Native / desktop client (loopback redirect)</label><label>Client authentication<select name="token_endpoint_auth_method">${Object.entries(UM).map(([e,t])=>`<option value="${e}">${t}</option>`).join(``)}</select></label><button>Register client +</button></fieldset><p class="help">S256 PKCE is always required. Clients you register here are first party and skip the consent screen.</p></form>`;async function mN(){let[e,t]=await OM(Promise.all([Q.administration.listClients(),Q.administration.listApiKeys()])),{resources:n}=e,r=n.filter(e=>!e.builtIn),i=t=>e.clientAccess.filter(e=>e.client_id===t).map(e=>e.resource),a=t=>e.connections.filter(e=>e.client_id===t);jM.className=`dashboard`,jM.innerHTML=`
43
43
  <div class="page-title"><div><p class="eyebrow">CONTROL PLANE</p><h1>Clients and Resources</h1><p class="muted">Signed in as ${$(e.email)}</p></div><button id="logout" class="secondary">Sign out</button></div>
@@ -46,6 +46,6 @@ data: `)}\n`,t+`
46
46
  <section id="credentials" class="card hidden" aria-live="polite"></section>
47
47
  <fieldset id="dashboard-mutations" aria-label="Clients and Resources">
48
48
  ${tN(`Resources`,n.length,`Protected APIs and MCP servers, and the scopes they define.`,n.map(t=>oN(t,e.clients.filter(e=>a(e.client_id).some(e=>e.resource===t.identifier)))).join(``)||nN(`Add your first Resource`,`Define a protected API or MCP server. Compatible MCP clients onboard automatically when you connect.`),sN())}
49
- ${tN(`API keys`,t.keys.length,`Direct access for CLIs and automation. Keys carry only the scopes you select, filtered by current Resource policy. Policy changes do not revoke stored grants; restoring policy restores access. Resource servers verify keys offline: disabling, deleting, re-scoping or expiring a key takes effect within about a minute, and a Resource server that cannot reach this issuer keeps accepting the keys it last saw for up to 24 hours.`,t.keys.map(e=>cN(e,r)).join(``)||nN(`No API keys`,`Create a key for a CLI or automation that needs direct access.`),lN(r))}
49
+ ${tN(`API keys`,t.keys.length,`Direct access for CLIs and automation. Keys carry only the scopes you select, filtered by current Resource policy. Policy changes do not revoke stored grants; restoring policy restores access. Resource servers verify keys offline: a new key works within seconds and a key's expiry applies at once; any other change takes effect within about a minute, and a Resource server that cannot reach this issuer keeps accepting the keys it last saw for up to 24 hours.`,t.keys.map(e=>cN(e,r)).join(``)||nN(`No API keys`,`Create a key for a CLI or automation that needs direct access.`),lN(r))}
50
50
  ${tN(`Clients`,e.clients.length,`Compatible MCP clients register themselves when they connect; registration alone grants no access. Revoking or blocking stops new authorization and refresh at once; already-issued access tokens stay valid until they expire, at most fifteen minutes.`,e.clients.map(e=>fN(e,n,i(e.client_id),a(e.client_id))).join(``)||nN(`No Clients registered`,`Connect your MCP client to a configured server to onboard automatically, or register a managed Client here.`),pN(n))}
51
51
  </fieldset>`,YM();let o=e=>document.querySelector(e),s=(e,t)=>e.addEventListener(`submit`,n=>{n.preventDefault(),PM(()=>t(e))}),c=(e,t)=>{for(let n of jM.querySelectorAll(e))n.addEventListener(`click`,()=>t(n))};document.querySelector(`#logout`).addEventListener(`click`,()=>{PM(async()=>{await kM.signOut(),location.assign(`/login`)})}),s(o(`#register`),async e=>{let t=new FormData(e),n=await OM(Q.administration.createClient({client_name:zM(t,`name`),redirect_uris:HM(t),resources:BM(e),application_type:GM(t),token_endpoint_auth_method:WM(t)}));e.reset(),JM.set(n.client_id,n),XM(),await ZM()}),s(o(`#key-create`),async e=>{let t=new FormData(e),n=zM(t,`expiry`),r=await OM(Q.administration.createApiKey({name:zM(t,`name`),permissions:KM(e),expiresAt:n?ih(new Date(n)):null}));qM.set(r.keyId,`${r.name}\n${r.key}`),e.reset(),XM(),await ZM()}),s(o(`#resource-create`),async e=>{let t=new FormData(e);await OM(Q.administration.createResource({identifier:zM(t,`identifier`),...VM(t)})),e.reset(),await ZM()});for(let e of jM.querySelectorAll(`[data-key-rename]`))s(e,async e=>{await OM(Q.administration.updateApiKey({keyId:e.dataset.keyRename,name:zM(new FormData(e),`name`)})),await ZM()});for(let e of jM.querySelectorAll(`[data-key-grants]`))s(e,async e=>{await OM(Q.administration.updateApiKey({keyId:e.dataset.keyGrants,permissions:KM(e)})),await ZM()});for(let e of jM.querySelectorAll(`[data-resource-edit]`))s(e,async e=>{await OM(Q.administration.updateResource({identifier:e.dataset.resourceEdit,...VM(new FormData(e))})),await ZM()});for(let e of jM.querySelectorAll(`[data-client-edit]`))s(e,async e=>{let t=new FormData(e);await OM(Q.administration.updateClient({client_id:e.dataset.clientEdit,client_name:zM(t,`name`),redirect_uris:HM(t),application_type:GM(t)})),await ZM()});for(let e of jM.querySelectorAll(`[data-client-access]`))e.addEventListener(`submit`,t=>{t.preventDefault();let n=e.dataset.clientAccess,r=BM(e),a=i(n).filter(e=>!r.includes(e)),o=async()=>{await OM(Q.administration.setClientAccess({client_id:n,resources:r})),await ZM()};a.length?FM(`Remove access to ${a.join(`, `)}? This revokes the Client\u2019s authorization for ${a.length===1?`it`:`them`}.`,o):PM(o)});c(`[data-key-toggle]`,e=>{PM(async()=>{await OM(Q.administration.updateApiKey({keyId:e.dataset.keyToggle,enabled:e.dataset.enabled!==`true`})),await ZM()})}),c(`[data-key-delete]`,e=>{let t=e.dataset.keyDelete;FM(`Delete this API key?`,async()=>{await OM(Q.administration.deleteApiKey({keyId:t})),qM.delete(t),YM(),await ZM()})}),c(`[data-revoke]`,e=>{let t=e.dataset.revoke;FM(`Revoke all of this Client’s authorization? It must authorize again, and an automatic Client asks for consent.`,async()=>{await OM(Q.administration.revokeClient({client_id:t})),await ZM()})}),c(`[data-disconnect]`,e=>{let t=e.dataset.disconnect,n=e.dataset.resource;FM(`Revoke this Client’s authorization for ${n}? It must authorize again, and an automatic Client asks for consent.`,async()=>{await OM(Q.administration.revokeClient({client_id:t,resource:n})),await ZM()})}),c(`[data-block]`,e=>{let t=e.dataset.block,n=e.dataset.blocked!==`true`;FM(n?`Block this Client and revoke its authorization?`:`Unblock this Client?`,async()=>{await OM(Q.administration.blockClient({client_id:t,blocked:n})),await ZM()})}),c(`[data-rotate]`,e=>{let t=e.dataset.rotate;FM(`Rotate the secret? The old one stops working immediately.`,async()=>{let e=await OM(Q.administration.rotateClientSecret({client_id:t}));JM.set(e.client_id,e),XM()})}),c(`[data-delete]`,e=>{let t=e.dataset.delete;FM(e.dataset.managed===`true`?`Delete this Client and its authorization?`:`Forget this Client and its authorization? It can register again; block it to keep it out.`,async()=>{await OM(Q.administration.deleteClient({client_id:t})),JM.delete(t),YM(),await ZM()})}),c(`[data-resource-delete]`,e=>{let t=e.dataset.resourceDelete;FM(`Delete Resource ${t}? Every Client\u2019s authorization for it is revoked; API-key grants are kept.`,async()=>{await OM(Q.administration.deleteResource({identifier:t})),await ZM()})})}try{if((await OM(Q.issuer.setupStatus())).required)location.pathname===`/setup`?IM():location.replace(`/setup`);else if(location.pathname===`/setup`){let e=await kM.getSession();location.replace(e.data?`/`:`/login`)}else location.pathname===`/consent`?await RM():!(await kM.getSession()).data||location.pathname===`/login`?LM():await mN()}catch(e){jM.innerHTML=`<section class="card"><h1>Unable to continue</h1><p>${$(e instanceof Error?e.message:`Please try again`)}</p><a href="/login">Return to sign in</a></section>`}
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="color-scheme" content="light" />
7
7
  <title>clankerauth</title>
8
- <script type="module" crossorigin src="/assets/index-By6qXbiL.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BMyw4f-O.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-9VtsK-ro.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/clankerauth-dev",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Disposable local clankerauth issuer for application development",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "devDependencies": {
30
30
  "@clankerauth/admin-api": "0.1.0",
31
- "@clankerauth/server": "0.11.0",
31
+ "@clankerauth/server": "0.11.1",
32
32
  "@clankerauth/web": "0.1.0",
33
33
  "@effect/platform-node": "4.0.0-rc.118",
34
34
  "@opentelemetry/api": "1.9.1",