@adonis-agora/authkit-server 0.75.0 → 0.76.0
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.
- package/README.md +3 -0
- package/build/host/views/account/apps.edge +30 -0
- package/build/host/views/agents/consent.edge +60 -0
- package/build/host/views/agents/done.edge +24 -0
- package/build/host/views/consent.edge +9 -1
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +9 -0
- package/build/index.js +4 -0
- package/build/providers/authkit_server_provider.js +75 -50
- package/build/src/agents/agent_identity.d.ts +33 -0
- package/build/src/agents/agent_identity.js +156 -0
- package/build/src/agents/config.d.ts +99 -0
- package/build/src/agents/config.js +101 -0
- package/build/src/agents/delegation_service.d.ts +154 -0
- package/build/src/agents/delegation_service.js +394 -0
- package/build/src/agents/delegation_store.d.ts +93 -0
- package/build/src/agents/delegation_store.js +222 -0
- package/build/src/agents/middleware.d.ts +73 -0
- package/build/src/agents/middleware.js +113 -0
- package/build/src/agents/protocol.d.ts +62 -0
- package/build/src/agents/protocol.js +51 -0
- package/build/src/agents/runtime.d.ts +36 -0
- package/build/src/agents/runtime.js +65 -0
- package/build/src/agents/signer.d.ts +34 -0
- package/build/src/agents/signer.js +70 -0
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +5 -0
- package/build/src/controllers/authorization_server_metadata_controller.d.ts +10 -0
- package/build/src/controllers/authorization_server_metadata_controller.js +20 -0
- package/build/src/define_config.d.ts +26 -3
- package/build/src/define_config.js +34 -1
- package/build/src/host/account_api/account_api_controller.d.ts +12 -0
- package/build/src/host/account_api/account_api_controller.js +40 -0
- package/build/src/host/admin_api/dto.d.ts +1 -1
- package/build/src/host/admin_sessions_service.d.ts +2 -0
- package/build/src/host/admin_sessions_service.js +18 -0
- package/build/src/host/auth_host_config.d.ts +4 -0
- package/build/src/host/client_names.d.ts +11 -0
- package/build/src/host/client_names.js +12 -0
- package/build/src/host/controllers/account_apps_controller.d.ts +2 -0
- package/build/src/host/controllers/account_apps_controller.js +39 -3
- package/build/src/host/controllers/account_orgs_controller.js +2 -1
- package/build/src/host/controllers/account_session_controller.js +2 -1
- package/build/src/host/controllers/agent_consent_controller.d.ts +17 -0
- package/build/src/host/controllers/agent_consent_controller.js +138 -0
- package/build/src/host/controllers/agent_oauth_controller.d.ts +23 -0
- package/build/src/host/controllers/agent_oauth_controller.js +110 -0
- package/build/src/host/controllers/interaction_controller.js +28 -0
- package/build/src/host/csrf.d.ts +8 -18
- package/build/src/host/csrf.js +28 -2
- package/build/src/host/i18n.d.ts +54 -0
- package/build/src/host/i18n.js +54 -0
- package/build/src/host/redirect_exact.d.ts +12 -0
- package/build/src/host/redirect_exact.js +17 -0
- package/build/src/host/register_auth_host.js +48 -7
- package/build/src/host/request_url.d.ts +10 -0
- package/build/src/host/request_url.js +17 -0
- package/build/src/host/sudo/methods/magic_link.js +2 -1
- package/build/src/host/sudo/runtime.js +3 -2
- package/build/src/host/sudo_mode.js +4 -4
- package/build/src/mcp/mcp_oauth.d.ts +85 -0
- package/build/src/mcp/mcp_oauth.js +154 -0
- package/build/src/provider/build_provider.js +15 -1
- package/build/src/provider/oidc_service.d.ts +8 -0
- package/build/src/provider/oidc_service.js +10 -0
- package/build/src/schema/ensure.js +92 -0
- package/build/stubs/ui/react/pages/consent.tsx +17 -1
- package/package.json +1 -1
- package/stubs/ui/react/pages/consent.tsx +17 -1
package/README.md
CHANGED
|
@@ -11,6 +11,9 @@ wrapper around [`oidc-provider`](https://github.com/panva/node-oidc-provider).
|
|
|
11
11
|
- **Passwordless** — magic-link email login and passkey-first login.
|
|
12
12
|
- **Protocol extensions** — Device Flow (RFC 8628), DPoP (RFC 9449), PAR (RFC 9126), step-up
|
|
13
13
|
auth via `acr_values`, Dynamic Client Registration (RFC 7591/7592).
|
|
14
|
+
- **Personal agents** — AI assistants acting for your users: agent identity by signed JWT
|
|
15
|
+
(JWKS, no shared secrets) and user-approved, revocable delegation with scopes you define,
|
|
16
|
+
over a pluggable protocol adapter ([PACT](https://openpactprotocol.org) built in).
|
|
14
17
|
- **Native apps** — RFC 8252 native clients (`authkit:clients:create --native`: private-use
|
|
15
18
|
scheme / claimed https / loopback redirects, always public + PKCE) and the `@adonisjs/auth`
|
|
16
19
|
guards `oidcRpGuard` (session) and `oidcBearerGuard` (`Authorization: Bearer` access tokens,
|
|
@@ -56,5 +56,35 @@
|
|
|
56
56
|
@end
|
|
57
57
|
</div>
|
|
58
58
|
@end
|
|
59
|
+
|
|
60
|
+
@if(agents)
|
|
61
|
+
<h2 class="mt-8 mb-1 text-base font-semibold text-gray-900">{{ t('account.apps.agents_title') }}</h2>
|
|
62
|
+
<p class="mb-3 text-sm text-gray-500">{{ t('account.apps.agents_intro') }}</p>
|
|
63
|
+
<div class="overflow-hidden rounded-xl bg-white shadow-xs ring-1 ring-black/5">
|
|
64
|
+
@if(agents.length === 0)
|
|
65
|
+
<p class="p-6 text-sm text-gray-500">{{ t('account.apps.agents_empty') }}</p>
|
|
66
|
+
@else
|
|
67
|
+
@each(agent in agents)
|
|
68
|
+
<div class="flex items-center justify-between border-b border-gray-100 p-4 last:border-0">
|
|
69
|
+
<div>
|
|
70
|
+
<p class="text-sm font-medium text-gray-900">{{ agent.name }}</p>
|
|
71
|
+
<ul class="mt-1 text-xs text-gray-500">
|
|
72
|
+
@each(scope in agent.scopes)
|
|
73
|
+
<li>{{ scope.description }}</li>
|
|
74
|
+
@end
|
|
75
|
+
</ul>
|
|
76
|
+
</div>
|
|
77
|
+
<form method="POST" action="{{ accountPaths?.apps ?? '/account/apps' }}/agents/{{ agent.id }}/revoke"
|
|
78
|
+
onsubmit="return confirm('{{ t('account.apps.revoke_confirm') }}')">
|
|
79
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
80
|
+
<button type="submit" class="rounded-lg border border-red-300 px-3 py-1.5 text-sm font-semibold text-red-700 hover:bg-red-50">
|
|
81
|
+
{{ t('account.apps.revoke') }}
|
|
82
|
+
</button>
|
|
83
|
+
</form>
|
|
84
|
+
</div>
|
|
85
|
+
@end
|
|
86
|
+
@end
|
|
87
|
+
</div>
|
|
88
|
+
@end
|
|
59
89
|
</div>
|
|
60
90
|
</body></html>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('agents.consent.page_title') }}</title>
|
|
3
|
+
@include('authkit::partials/styles')
|
|
4
|
+
@include('authkit::partials/submit_lock')
|
|
5
|
+
</head>
|
|
6
|
+
<body class="min-h-screen flex items-center justify-center bg-gray-50">
|
|
7
|
+
<div class="w-full max-w-md bg-white p-8 rounded-2xl shadow-xl ring-1 ring-black/5">
|
|
8
|
+
@if(request)
|
|
9
|
+
<form method="POST" action="{{ action }}">
|
|
10
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
11
|
+
<input type="hidden" name="user_code" value="{{ request.userCode }}">
|
|
12
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('agents.consent.title', { agent: request.agentName }) }}</h1>
|
|
13
|
+
<p class="mt-2 text-sm text-gray-600">{{ t('agents.consent.body', { origin: request.agentOrigin }) }}</p>
|
|
14
|
+
@if(account && account.email)
|
|
15
|
+
<p class="mt-2 text-xs text-gray-500">{{ t('agents.consent.signed_in_as', { email: account.email }) }}</p>
|
|
16
|
+
@end
|
|
17
|
+
|
|
18
|
+
<fieldset class="mt-6 space-y-3">
|
|
19
|
+
<legend class="text-sm font-medium text-gray-900">{{ t('agents.consent.scopes_label') }}</legend>
|
|
20
|
+
@each(scope in request.scopes)
|
|
21
|
+
<label class="flex items-start gap-3 rounded-lg border border-gray-200 p-3">
|
|
22
|
+
<input type="checkbox" name="scope" value="{{ scope.id }}" checked class="mt-0.5">
|
|
23
|
+
<span class="text-sm text-gray-800">{{ scope.description }}</span>
|
|
24
|
+
</label>
|
|
25
|
+
@end
|
|
26
|
+
</fieldset>
|
|
27
|
+
|
|
28
|
+
<p class="mt-4 text-xs text-gray-500">{{ t('agents.consent.code', { code: request.userCode }) }}</p>
|
|
29
|
+
|
|
30
|
+
<div class="mt-6 flex gap-3">
|
|
31
|
+
<button type="submit" name="decision" value="deny"
|
|
32
|
+
class="flex-1 rounded-lg border border-gray-300 py-2.5 text-sm font-semibold text-gray-700 transition hover:bg-gray-50">
|
|
33
|
+
{{ t('agents.consent.deny') }}
|
|
34
|
+
</button>
|
|
35
|
+
<button type="submit" name="decision" value="allow"
|
|
36
|
+
class="flex-1 rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
37
|
+
{{ t('agents.consent.allow') }}
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</form>
|
|
41
|
+
@else
|
|
42
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('agents.consent.enter_code_title') }}</h1>
|
|
43
|
+
@if(error)
|
|
44
|
+
<p class="mt-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800">{{ t(error) }}</p>
|
|
45
|
+
@end
|
|
46
|
+
@if(error !== 'agents.consent.impersonating')
|
|
47
|
+
<form method="GET" action="{{ action }}" class="mt-4">
|
|
48
|
+
<label class="block text-sm text-gray-600" for="user_code">{{ t('agents.consent.enter_code_body') }}</label>
|
|
49
|
+
<input id="user_code" name="user_code" autocomplete="one-time-code" autocapitalize="characters"
|
|
50
|
+
class="mt-2 w-full rounded-lg border border-gray-300 px-3 py-2 text-center font-mono text-lg tracking-widest"
|
|
51
|
+
placeholder="XXXX-XXXX" required>
|
|
52
|
+
<button type="submit"
|
|
53
|
+
class="mt-4 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
54
|
+
{{ t('agents.consent.continue') }}
|
|
55
|
+
</button>
|
|
56
|
+
</form>
|
|
57
|
+
@end
|
|
58
|
+
@end
|
|
59
|
+
</div>
|
|
60
|
+
</body></html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('agents.consent.page_title') }}</title>
|
|
3
|
+
@include('authkit::partials/styles')
|
|
4
|
+
</head>
|
|
5
|
+
<body class="min-h-screen flex items-center justify-center bg-gray-50">
|
|
6
|
+
<div class="w-full max-w-md bg-white p-8 rounded-2xl shadow-xl ring-1 ring-black/5">
|
|
7
|
+
@if(status === 'approved')
|
|
8
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('agents.done.approved_title', { agent: agentName }) }}</h1>
|
|
9
|
+
<ul class="mt-4 space-y-1 text-sm text-gray-700">
|
|
10
|
+
@each(scope in scopes)
|
|
11
|
+
<li>• {{ scope.description }}</li>
|
|
12
|
+
@end
|
|
13
|
+
</ul>
|
|
14
|
+
<p class="mt-4 text-sm text-gray-600">{{ t('agents.done.return') }}</p>
|
|
15
|
+
<p class="mt-2 text-xs text-gray-500">{{ t('agents.done.revoke_hint') }}</p>
|
|
16
|
+
@elseif(status === 'denied')
|
|
17
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('agents.done.denied_title') }}</h1>
|
|
18
|
+
<p class="mt-2 text-sm text-gray-600">{{ t('agents.done.return') }}</p>
|
|
19
|
+
@else
|
|
20
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('agents.done.expired_title') }}</h1>
|
|
21
|
+
<p class="mt-2 text-sm text-gray-600">{{ t('agents.done.expired_body') }}</p>
|
|
22
|
+
@end
|
|
23
|
+
</div>
|
|
24
|
+
</body></html>
|
|
@@ -9,8 +9,16 @@
|
|
|
9
9
|
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
10
10
|
<h1 class="text-xl font-semibold text-gray-900">{{ t('consent.title') }}</h1>
|
|
11
11
|
<p class="mt-2 text-sm text-gray-600">
|
|
12
|
-
{{{ t('consent.body', { app:
|
|
12
|
+
{{{ t('consent.body', { app: clientNameHtml }) }}}
|
|
13
13
|
</p>
|
|
14
|
+
@if(scopes && scopes.length)
|
|
15
|
+
<p class="mt-4 text-sm font-medium text-gray-900">{{ t('consent.scopes_title') }}</p>
|
|
16
|
+
<ul class="mt-2 space-y-1 text-sm text-gray-600 list-disc list-inside">
|
|
17
|
+
@each(scope in scopes)
|
|
18
|
+
<li>{{ scope.label }}</li>
|
|
19
|
+
@end
|
|
20
|
+
</ul>
|
|
21
|
+
@end
|
|
14
22
|
|
|
15
23
|
<button type="submit"
|
|
16
24
|
class="mt-6 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
{{-- Gerado por scripts/build_host_css.mjs — não editar à mão. --}}
|
|
2
2
|
<style>/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
3
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-800:oklch(47.3% .137 46.201);--color-green-50:oklch(98.2% .018 155.826);--color-green-700:oklch(52.7% .154 150.069);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--tracking-widest:.1em;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}button:not(:disabled),[role=button]:not(:disabled){cursor:pointer}}@layer components;@layer utilities{.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-4{margin-block:calc(var(--spacing) * 4)}.my-6{margin-block:calc(var(--spacing) * 6)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-12{height:calc(var(--spacing) * 12)}.h-16{height:calc(var(--spacing) * 16)}.h-48{height:calc(var(--spacing) * 48)}.h-px{height:1px}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-12{width:calc(var(--spacing) * 12)}.w-16{width:calc(var(--spacing) * 16)}.w-48{width:calc(var(--spacing) * 48)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-sm{max-width:var(--container-sm)}.flex-1{flex:1}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-blue-300{border-color:var(--color-blue-300)}.border-emerald-300{border-color:var(--color-emerald-300)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-red-300{border-color:var(--color-red-300)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-600{background-color:var(--color-red-600)}.bg-white{background-color:var(--color-white)}.object-cover{object-fit:cover}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.4em\]{--tw-tracking:.4em;letter-spacing:.4em}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.text-amber-600{color:var(--color-amber-600)}.text-amber-800{color:var(--color-amber-800)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-700{color:var(--color-green-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-amber-200{--tw-ring-color:var(--color-amber-200)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab, var(--color-black) 5%, transparent)}}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.ring-emerald-200{--tw-ring-color:var(--color-emerald-200)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.ring-red-200{--tw-ring-color:var(--color-red-200)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-none{-webkit-user-select:none;user-select:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}}.focus\:border-gray-900:focus{border-color:var(--color-gray-900)}.focus\:border-red-600:focus{border-color:var(--color-red-600)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}</style>
|
|
3
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-800:oklch(47.3% .137 46.201);--color-green-50:oklch(98.2% .018 155.826);--color-green-700:oklch(52.7% .154 150.069);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--tracking-widest:.1em;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}button:not(:disabled),[role=button]:not(:disabled){cursor:pointer}}@layer components;@layer utilities{.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.my-4{margin-block:calc(var(--spacing) * 4)}.my-6{margin-block:calc(var(--spacing) * 6)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-12{height:calc(var(--spacing) * 12)}.h-16{height:calc(var(--spacing) * 16)}.h-48{height:calc(var(--spacing) * 48)}.h-px{height:1px}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-12{width:calc(var(--spacing) * 12)}.w-16{width:calc(var(--spacing) * 16)}.w-48{width:calc(var(--spacing) * 48)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.flex-1{flex:1}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-blue-300{border-color:var(--color-blue-300)}.border-emerald-300{border-color:var(--color-emerald-300)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-600{background-color:var(--color-red-600)}.bg-white{background-color:var(--color-white)}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.4em\]{--tw-tracking:.4em;letter-spacing:.4em}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.text-amber-600{color:var(--color-amber-600)}.text-amber-800{color:var(--color-amber-800)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-700{color:var(--color-green-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-amber-200{--tw-ring-color:var(--color-amber-200)}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab, var(--color-black) 5%, transparent)}}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.ring-emerald-200{--tw-ring-color:var(--color-emerald-200)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.ring-red-200{--tw-ring-color:var(--color-red-200)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-none{-webkit-user-select:none;user-select:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}}.focus\:border-gray-900:focus{border-color:var(--color-gray-900)}.focus\:border-red-600:focus{border-color:var(--color-red-600)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}</style>
|
package/build/index.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ export { appKeyEncrypter, lucidAccountStore } from './src/accounts/lucid_account
|
|
|
10
10
|
export { AuthOrganization, AuthOrganizationInvitation, AuthOrganizationMember, defaultOrganizationModels, } from './src/accounts/lucid_store/organization_models.js';
|
|
11
11
|
export type { LucidStoresModels, LucidStoresOptions, LucidStoresResult, } from './src/accounts/lucid_stores.js';
|
|
12
12
|
export { lucidStores } from './src/accounts/lucid_stores.js';
|
|
13
|
+
export type { PersonalAgentIdentity } from './src/agents/agent_identity.js';
|
|
14
|
+
export { PersonalAgentVerifier } from './src/agents/agent_identity.js';
|
|
15
|
+
export type { PersonalAgentDelegationConfigInput, PersonalAgentRegistration, PersonalAgentResolver, PersonalAgentsConfigInput, ResolvedPersonalAgentsConfig, } from './src/agents/config.js';
|
|
16
|
+
export type { DelegationContext, DelegationGrantSummary, DelegationReceipt, } from './src/agents/delegation_service.js';
|
|
17
|
+
export type { PersonalAgentAuthOptions, PersonalAgentRequestContext, } from './src/agents/middleware.js';
|
|
18
|
+
export { personalAgentAuth, personalAgentOf, personalAgentReceipt, personalAgentSecurity, personalAgentStepUp, } from './src/agents/middleware.js';
|
|
19
|
+
export type { BuiltinProtocolId, PactOptions, PersonalAgentProtocol, } from './src/agents/protocol.js';
|
|
20
|
+
export { pact, pactProtocol } from './src/agents/protocol.js';
|
|
13
21
|
export type { AuditEvent, AuditEventType, AuditPage, AuditSink, ListAuditParams, StoredAuditEvent, } from './src/audit/audit_sink.js';
|
|
14
22
|
export { lucidAuditSink } from './src/audit/lucid_audit_sink.js';
|
|
15
23
|
export type { AdminApiConfigInput, AdminConfigInput, AuthHostRenderer, AuthMethodsConfigInput, AuthServerConfigInput, AuthSocialConfig, DynamicRegistrationConfigInput, LoginConfigInput, OrganizationsConfigInput, PasswordlessConfigInput, RateLimitBucket, RateLimitConfigInput, RegistrationConfigInput, ResolvedAdminApiConfig, ResolvedAdminConfig, ResolvedAuthMethodsConfig, ResolvedDynamicRegistrationConfig, ResolvedLoginConfig, ResolvedNotificationsConfig, ResolvedOrganizationsConfig, ResolvedPasswordlessConfig, ResolvedRateLimitConfig, ResolvedRegistrationConfig, ResolvedServerConfig, ResolvedWebauthnConfig, WebauthnConfigInput, } from './src/define_config.js';
|
|
@@ -109,6 +117,7 @@ export type { ParsedUserAgent } from './src/host/user_agent.js';
|
|
|
109
117
|
export { parseUserAgent } from './src/host/user_agent.js';
|
|
110
118
|
export type { ResolvedUserLoginMethods, UserLoginMethodKey, UserLoginMethods, } from './src/host/user_login_methods.js';
|
|
111
119
|
export { normalizeUserLoginMethods, parseUserLoginMethodsPayload, resolveEffectiveUserLoginMethods, USER_LOGIN_METHOD_KEYS, } from './src/host/user_login_methods.js';
|
|
120
|
+
export { MCP_CLIENT_REDIRECTS, MCP_RESOURCE_SCOPES, type McpOAuthConfigInput, type OAuthResourceRegistration, registeredOAuthResources, registerOAuthResource, } from './src/mcp/mcp_oauth.js';
|
|
112
121
|
export { withAuditLog } from './src/mixins/with_audit_log.js';
|
|
113
122
|
export { withAuthUser } from './src/mixins/with_auth_user.js';
|
|
114
123
|
export { withCredentials } from './src/mixins/with_credentials.js';
|
package/build/index.js
CHANGED
|
@@ -7,6 +7,9 @@ export { supportsAccountDeletion, supportsAccountEmailRewrite, supportsAccountIm
|
|
|
7
7
|
export { appKeyEncrypter, lucidAccountStore } from './src/accounts/lucid_account_store.js';
|
|
8
8
|
export { AuthOrganization, AuthOrganizationInvitation, AuthOrganizationMember, defaultOrganizationModels, } from './src/accounts/lucid_store/organization_models.js';
|
|
9
9
|
export { lucidStores } from './src/accounts/lucid_stores.js';
|
|
10
|
+
export { PersonalAgentVerifier } from './src/agents/agent_identity.js';
|
|
11
|
+
export { personalAgentAuth, personalAgentOf, personalAgentReceipt, personalAgentSecurity, personalAgentStepUp, } from './src/agents/middleware.js';
|
|
12
|
+
export { pact, pactProtocol } from './src/agents/protocol.js';
|
|
10
13
|
export { lucidAuditSink } from './src/audit/lucid_audit_sink.js';
|
|
11
14
|
export { adapters, defineConfig, resolveAdmin, resolveAdminApi, resolveAuthMethodsConfig, resolveDynamicRegistration, resolveLogin, resolveNotifications, resolveOrganizations, resolvePasswordless, resolveRateLimit, resolveRegistration, resolveWebauthn, toSeconds, } from './src/define_config.js';
|
|
12
15
|
export { buildWebhookBody, composeAuditSink, resolveEvents, signWebhookBody, } from './src/events/dispatcher.js';
|
|
@@ -107,6 +110,7 @@ export { buildTrustedDevicePayload, isTrustedDeviceValid, resolveTrustedDevices,
|
|
|
107
110
|
export { parseUserAgent } from './src/host/user_agent.js';
|
|
108
111
|
// Tipos de login POR USUÁRIO (self-service no console de conta).
|
|
109
112
|
export { normalizeUserLoginMethods, parseUserLoginMethodsPayload, resolveEffectiveUserLoginMethods, USER_LOGIN_METHOD_KEYS, } from './src/host/user_login_methods.js';
|
|
113
|
+
export { MCP_CLIENT_REDIRECTS, MCP_RESOURCE_SCOPES, registeredOAuthResources, registerOAuthResource, } from './src/mcp/mcp_oauth.js';
|
|
110
114
|
export { withAuditLog } from './src/mixins/with_audit_log.js';
|
|
111
115
|
export { withAuthUser } from './src/mixins/with_auth_user.js';
|
|
112
116
|
export { withCredentials } from './src/mixins/with_credentials.js';
|
|
@@ -47,58 +47,36 @@ export default class AuthkitServerProvider {
|
|
|
47
47
|
if (this.app.config.get('authkit')) {
|
|
48
48
|
resolveAppKey(this.app);
|
|
49
49
|
}
|
|
50
|
-
// Config locks
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
// Config locks + stash dos bits de routing + auto-montagem: tudo sai do config RESOLVIDO.
|
|
51
|
+
//
|
|
52
|
+
// A resolução pode falhar AQUI e dar certo um instante depois: o `jwks` de um keystore
|
|
53
|
+
// criptografado precisa do serviço de encryption, que nem sempre está pronto durante o boot
|
|
54
|
+
// dos providers. Antes, o catch engolia a falha e nada derivado do config valia — locks,
|
|
55
|
+
// `sudo.methods`, headless, personal agents — sem um aviso. Agora a falha aqui é tentada de
|
|
56
|
+
// novo no `booted` (todos os providers prontos, ANTES do preload `start/routes.ts`), e só a
|
|
57
|
+
// segunda falha desiste, com warning.
|
|
58
|
+
if (this.app.config.get('authkit')) {
|
|
59
|
+
let config;
|
|
60
|
+
try {
|
|
61
|
+
config = await this.#resolveConfig();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
this.app.booted(async () => {
|
|
65
|
+
let late;
|
|
66
|
+
try {
|
|
67
|
+
late = await this.#resolveConfig();
|
|
63
68
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
adminApiEnabled: config.adminApi.enabled,
|
|
73
|
-
// `config.sudo.methods` passa a decidir também o que é MONTADO — sem
|
|
74
|
-
// isto o host teria de repetir a lista no `registerAuthHost`, e as
|
|
75
|
-
// duas divergiriam (tela oferecendo endpoint que dá 404).
|
|
76
|
-
sudoMethods: config.sudo?.methods,
|
|
77
|
-
// Defaults estruturais de `config.routes` (o argumento ainda vence).
|
|
78
|
-
routes: typeof config.routes === 'object' ? config.routes : undefined,
|
|
79
|
-
lockedRouteOptions: config.lockedRouteOptions,
|
|
80
|
-
// API headless — repassa pro registerAuthHost montar as rotas.
|
|
81
|
-
headless: config.headless
|
|
82
|
-
? {
|
|
83
|
-
baseUrl: config.headless.baseUrl,
|
|
84
|
-
resolveAccountId: config.headless.resolveAccountId,
|
|
85
|
-
}
|
|
86
|
-
: undefined,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const logger = await this.app.container.make('logger');
|
|
71
|
+
logger.warn({ err: error }, 'authkit: config não resolveu no boot — locks, stash de rotas (registerAuthHost) e config.routes não foram aplicados');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (late)
|
|
75
|
+
await this.#applyResolvedConfig(late);
|
|
76
|
+
});
|
|
89
77
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
/* sem locks / sem stash → registerAuthHost cai em opts/defaults */
|
|
93
|
-
}
|
|
94
|
-
// Auto-montagem das rotas (`config.routes`). FORA do try/catch fail-safe
|
|
95
|
-
// acima de propósito: "as rotas não subiram" não pode degradar em silêncio —
|
|
96
|
-
// seria um app inteiro em 404 sem nenhuma pista. Chama a MESMA função
|
|
97
|
-
// exportada que o `start/routes.ts` chamaria; não há segunda implementação.
|
|
98
|
-
if (resolvedConfig?.routes) {
|
|
99
|
-
const router = await this.app.container.make('router');
|
|
100
|
-
const { autoMountAuthHost } = await import('../src/host/register_auth_host.js');
|
|
101
|
-
autoMountAuthHost(router);
|
|
78
|
+
if (config)
|
|
79
|
+
await this.#applyResolvedConfig(config);
|
|
102
80
|
}
|
|
103
81
|
// Registra o disco "authkit" no edge.js para que os templates sejam referenciados
|
|
104
82
|
// como `authkit::login`, `authkit::account/tokens`, etc.
|
|
@@ -123,6 +101,53 @@ export default class AuthkitServerProvider {
|
|
|
123
101
|
// edge.js ausente (host headless/Inertia-only que não usa edgeRenderer) — ignora.
|
|
124
102
|
}
|
|
125
103
|
}
|
|
104
|
+
async #resolveConfig() {
|
|
105
|
+
const value = this.app.config.get('authkit');
|
|
106
|
+
return (await configProvider.resolve(this.app, value));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Aplica o que o boot deriva do config resolvido: os locks de settings, o stash que o
|
|
110
|
+
* `registerAuthHost` lê e a auto-montagem de `config.routes` (cuja falha propaga).
|
|
111
|
+
*/
|
|
112
|
+
async #applyResolvedConfig(config) {
|
|
113
|
+
if (config.lockedSettingKeys?.length) {
|
|
114
|
+
const { setLockedSettingKeys } = await import('../src/host/config_locks.js');
|
|
115
|
+
setLockedSettingKeys(config.lockedSettingKeys);
|
|
116
|
+
}
|
|
117
|
+
// Stash dos bits de routing p/ o registerAuthHost ler do config (dedup). Roda antes do
|
|
118
|
+
// preload start/routes.ts (no boot, ou no `booted` quando o boot não conseguiu resolver).
|
|
119
|
+
const { setAuthHostConfig } = await import('../src/host/auth_host_config.js');
|
|
120
|
+
setAuthHostConfig({
|
|
121
|
+
mountPath: config.mountPath,
|
|
122
|
+
social: config.social,
|
|
123
|
+
rateLimit: config.rateLimit,
|
|
124
|
+
adminEnabled: config.admin.enabled,
|
|
125
|
+
adminApiEnabled: config.adminApi.enabled,
|
|
126
|
+
// `config.sudo.methods` passa a decidir também o que é MONTADO — sem isto o host teria de
|
|
127
|
+
// repetir a lista no `registerAuthHost`, e as duas divergiriam (tela oferecendo endpoint
|
|
128
|
+
// que dá 404).
|
|
129
|
+
sudoMethods: config.sudo?.methods,
|
|
130
|
+
// Defaults estruturais de `config.routes` (o argumento ainda vence).
|
|
131
|
+
routes: typeof config.routes === 'object' ? config.routes : undefined,
|
|
132
|
+
lockedRouteOptions: config.lockedRouteOptions,
|
|
133
|
+
personalAgents: config.personalAgents ? { prefix: config.personalAgents.prefix } : undefined,
|
|
134
|
+
// API headless — repassa pro registerAuthHost montar as rotas.
|
|
135
|
+
headless: config.headless
|
|
136
|
+
? {
|
|
137
|
+
baseUrl: config.headless.baseUrl,
|
|
138
|
+
resolveAccountId: config.headless.resolveAccountId,
|
|
139
|
+
}
|
|
140
|
+
: undefined,
|
|
141
|
+
});
|
|
142
|
+
// Auto-montagem das rotas (`config.routes`). Chama a MESMA função exportada que o
|
|
143
|
+
// `start/routes.ts` chamaria; não há segunda implementação. "As rotas não subiram" não pode
|
|
144
|
+
// degradar em silêncio: uma falha aqui propaga.
|
|
145
|
+
if (config.routes) {
|
|
146
|
+
const router = await this.app.container.make('router');
|
|
147
|
+
const { autoMountAuthHost } = await import('../src/host/register_auth_host.js');
|
|
148
|
+
autoMountAuthHost(router);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
126
151
|
register() {
|
|
127
152
|
// Entrega o app BOOTADO ao singleton de `services/main` (e ao
|
|
128
153
|
// `recordSubRevocation` do AdminSessionsService) para que nunca precisem
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type JWTPayload, type JWTVerifyGetKey } from 'jose';
|
|
2
|
+
import type { PersonalAgentRegistration, ResolvedPersonalAgentsConfig } from './config.js';
|
|
3
|
+
/** Quem está chamando: o agente e o usuário DELE (`sub`, opaco). */
|
|
4
|
+
export interface PersonalAgentIdentity {
|
|
5
|
+
/** `iss` do JWT — identifica o agente; também é o `client_id` OAuth dele. */
|
|
6
|
+
issuer: string;
|
|
7
|
+
/** Usuário do agente. Opaco e estável; NÃO é a conta neste app. */
|
|
8
|
+
sub: string;
|
|
9
|
+
/** Nome de exibição do agente (registro ou host do issuer). */
|
|
10
|
+
name: string;
|
|
11
|
+
claims: JWTPayload;
|
|
12
|
+
}
|
|
13
|
+
export interface PersonalAgentVerifierOptions {
|
|
14
|
+
/** Fonte das chaves por `jwksUri`. Default: `createRemoteJWKSet` com cache. */
|
|
15
|
+
getKey?: (jwksUri: string) => JWTVerifyGetKey;
|
|
16
|
+
/** `fetch` da descoberta OIDC no modo `open`. Default: o global. */
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
now?: () => Date;
|
|
19
|
+
}
|
|
20
|
+
/** Extrai o token de um header `Authorization: Bearer <token>`. */
|
|
21
|
+
export declare function bearerToken(header: string | null | undefined): string | null;
|
|
22
|
+
export declare function displayNameFor(registration: PersonalAgentRegistration): string;
|
|
23
|
+
/**
|
|
24
|
+
* Verifica o JWT que um personal agent manda em `Authorization: Bearer`, com
|
|
25
|
+
* as regras do protocolo configurado (PACT §3.2: ES256/RS256, vida ≤ 300 s,
|
|
26
|
+
* 30 s de relógio). Devolve `null` para QUALQUER falha — o chamador responde
|
|
27
|
+
* 401 sem detalhe.
|
|
28
|
+
*/
|
|
29
|
+
export declare class PersonalAgentVerifier {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(cfg: ResolvedPersonalAgentsConfig, options?: PersonalAgentVerifierOptions);
|
|
32
|
+
verify(authorization: string | null | undefined): Promise<PersonalAgentIdentity | null>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createRemoteJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify, } from 'jose';
|
|
2
|
+
/** Descoberta do modo `open`: quanto tempo um resultado vale, e quantos guardar. */
|
|
3
|
+
const DISCOVERY_TTL_MS = 60 * 60 * 1000;
|
|
4
|
+
const DISCOVERY_FAILURE_TTL_MS = 60 * 1000;
|
|
5
|
+
const MAX_CACHED_ISSUERS = 1000;
|
|
6
|
+
/** Map com teto: passando do limite, sai a entrada mais antiga (ordem de inserção). */
|
|
7
|
+
function setBounded(map, key, value) {
|
|
8
|
+
map.delete(key);
|
|
9
|
+
map.set(key, value);
|
|
10
|
+
if (map.size > MAX_CACHED_ISSUERS)
|
|
11
|
+
map.delete(map.keys().next().value);
|
|
12
|
+
}
|
|
13
|
+
/** Extrai o token de um header `Authorization: Bearer <token>`. */
|
|
14
|
+
export function bearerToken(header) {
|
|
15
|
+
if (!header)
|
|
16
|
+
return null;
|
|
17
|
+
const match = /^Bearer\s+(\S+)\s*$/i.exec(header);
|
|
18
|
+
return match ? match[1] : null;
|
|
19
|
+
}
|
|
20
|
+
export function displayNameFor(registration) {
|
|
21
|
+
if (registration.name)
|
|
22
|
+
return registration.name;
|
|
23
|
+
try {
|
|
24
|
+
return new URL(registration.issuer).host;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return registration.issuer;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Verifica o JWT que um personal agent manda em `Authorization: Bearer`, com
|
|
32
|
+
* as regras do protocolo configurado (PACT §3.2: ES256/RS256, vida ≤ 300 s,
|
|
33
|
+
* 30 s de relógio). Devolve `null` para QUALQUER falha — o chamador responde
|
|
34
|
+
* 401 sem detalhe.
|
|
35
|
+
*/
|
|
36
|
+
export class PersonalAgentVerifier {
|
|
37
|
+
#cfg;
|
|
38
|
+
#getKey;
|
|
39
|
+
#fetch;
|
|
40
|
+
#now;
|
|
41
|
+
#remoteSets = new Map();
|
|
42
|
+
#discovered = new Map();
|
|
43
|
+
constructor(cfg, options = {}) {
|
|
44
|
+
this.#cfg = cfg;
|
|
45
|
+
this.#getKey = options.getKey ?? ((uri) => this.#remoteSet(uri));
|
|
46
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
47
|
+
this.#now = options.now ?? (() => new Date());
|
|
48
|
+
}
|
|
49
|
+
async verify(authorization) {
|
|
50
|
+
const token = bearerToken(authorization);
|
|
51
|
+
if (!token)
|
|
52
|
+
return null;
|
|
53
|
+
const rules = this.#cfg.protocol.identity;
|
|
54
|
+
let issuer;
|
|
55
|
+
try {
|
|
56
|
+
if (!rules.algorithms.includes(decodeProtectedHeader(token).alg ?? ''))
|
|
57
|
+
return null;
|
|
58
|
+
issuer = decodeJwt(token).iss;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (typeof issuer !== 'string' || !issuer)
|
|
64
|
+
return null;
|
|
65
|
+
const registration = await this.#registrationFor(issuer);
|
|
66
|
+
if (!registration || registration.enabled === false)
|
|
67
|
+
return null;
|
|
68
|
+
const now = this.#now();
|
|
69
|
+
let payload;
|
|
70
|
+
try {
|
|
71
|
+
({ payload } = await jwtVerify(token, this.#getKey(registration.jwksUri), {
|
|
72
|
+
algorithms: rules.algorithms,
|
|
73
|
+
issuer: registration.issuer,
|
|
74
|
+
audience: this.#cfg.audience,
|
|
75
|
+
clockTolerance: rules.clockSkewSeconds,
|
|
76
|
+
currentDate: now,
|
|
77
|
+
requiredClaims: ['sub', 'iat', 'exp'],
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// O que o `jwtVerify` não cobre: `aud` é UMA string, `iat` não está no
|
|
84
|
+
// futuro além da tolerância, e a vida do token é curta.
|
|
85
|
+
const nowSeconds = Math.floor(now.getTime() / 1000);
|
|
86
|
+
if (typeof payload.aud !== 'string')
|
|
87
|
+
return null;
|
|
88
|
+
if (typeof payload.sub !== 'string' || !payload.sub)
|
|
89
|
+
return null;
|
|
90
|
+
if (payload.iat > nowSeconds + rules.clockSkewSeconds)
|
|
91
|
+
return null;
|
|
92
|
+
if (payload.exp - payload.iat > rules.maxLifetimeSeconds)
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
issuer: registration.issuer,
|
|
96
|
+
sub: payload.sub,
|
|
97
|
+
name: displayNameFor(registration),
|
|
98
|
+
claims: payload,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async #registrationFor(issuer) {
|
|
102
|
+
const registered = await this.#cfg.resolveAgent(issuer);
|
|
103
|
+
if (registered)
|
|
104
|
+
return registered;
|
|
105
|
+
if (!this.#cfg.open)
|
|
106
|
+
return null;
|
|
107
|
+
const jwksUri = await this.#discover(issuer);
|
|
108
|
+
return jwksUri ? { issuer, jwksUri } : null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Modo `open`: `jwks_uri` via `{iss}/.well-known/openid-configuration`, só
|
|
112
|
+
* HTTPS. O `iss` vem de um JWT ainda NÃO verificado, então tudo é limitado:
|
|
113
|
+
* falhas também ficam em cache (um minuto — o mesmo `iss` não vira uma
|
|
114
|
+
* requisição de saída por request), acertos expiram (o `jwks_uri` pode
|
|
115
|
+
* mudar) e o cache tem teto de entradas.
|
|
116
|
+
*/
|
|
117
|
+
#discover(issuer) {
|
|
118
|
+
const now = this.#now().getTime();
|
|
119
|
+
const cached = this.#discovered.get(issuer);
|
|
120
|
+
if (cached && cached.expiresAt > now)
|
|
121
|
+
return cached.result;
|
|
122
|
+
const result = this.#fetchJwksUri(issuer);
|
|
123
|
+
const entry = { result, expiresAt: now + DISCOVERY_TTL_MS };
|
|
124
|
+
setBounded(this.#discovered, issuer, entry);
|
|
125
|
+
result.then((uri) => {
|
|
126
|
+
if (!uri)
|
|
127
|
+
entry.expiresAt = now + DISCOVERY_FAILURE_TTL_MS;
|
|
128
|
+
});
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
async #fetchJwksUri(issuer) {
|
|
132
|
+
try {
|
|
133
|
+
const url = new URL(issuer);
|
|
134
|
+
if (url.protocol !== 'https:')
|
|
135
|
+
return null;
|
|
136
|
+
const res = await this.#fetch(`${issuer.replace(/\/+$/, '')}/.well-known/openid-configuration`, { signal: AbortSignal.timeout(5000), redirect: 'error' });
|
|
137
|
+
if (!res.ok)
|
|
138
|
+
return null;
|
|
139
|
+
const meta = (await res.json());
|
|
140
|
+
if (meta.issuer !== issuer || typeof meta.jwks_uri !== 'string')
|
|
141
|
+
return null;
|
|
142
|
+
return new URL(meta.jwks_uri).protocol === 'https:' ? meta.jwks_uri : null;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
#remoteSet(jwksUri) {
|
|
149
|
+
let set = this.#remoteSets.get(jwksUri);
|
|
150
|
+
if (!set) {
|
|
151
|
+
set = createRemoteJWKSet(new URL(jwksUri));
|
|
152
|
+
setBounded(this.#remoteSets, jwksUri, set);
|
|
153
|
+
}
|
|
154
|
+
return set;
|
|
155
|
+
}
|
|
156
|
+
}
|