@adonis-agora/authkit-server 0.83.0 → 0.84.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.
@@ -19,7 +19,13 @@
19
19
  @end
20
20
 
21
21
  <div class="mt-6">
22
- <label for="email" class="mb-1 block text-sm font-medium text-gray-700">{{ t('login.email_label') }}</label>
22
+ {{-- Selo "último usado": este dispositivo entrou por e-mail (senha, link, código ou passkey). --}}
23
+ <div class="mb-1 flex items-center justify-between">
24
+ <label for="email" class="block text-sm font-medium text-gray-700">{{ t('login.email_label') }}</label>
25
+ @if(['password', 'magic_link', 'otp', 'passkey'].includes(lastUsedLoginMethod))
26
+ <span class="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">{{ t('login.last_used') }}</span>
27
+ @end
28
+ </div>
23
29
  <input id="email" name="email" type="email" required autofocus
24
30
  autocomplete="{{ authMethods && authMethods.passkeyAutofill ? 'username webauthn' : 'username' }}"
25
31
  class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-hidden transition focus:border-gray-900 focus:ring-2 focus:ring-gray-900" />
@@ -49,7 +55,7 @@
49
55
 
50
56
  @each(provider in (authMethods && authMethods.social ? authMethods.social : []))
51
57
  <a href="/auth/{{ provider }}/redirect/{{ uid }}"
52
- class="flex w-full items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white py-2.5 text-sm font-medium text-gray-700 transition hover:bg-gray-50 mb-2">
58
+ class="relative flex w-full items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white py-2.5 text-sm font-medium text-gray-700 transition hover:bg-gray-50 mb-2">
53
59
  @if(provider === 'google')
54
60
  <svg class="h-4 w-4" viewBox="0 0 24 24" aria-hidden="true">
55
61
  <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.27-4.74 3.27-8.1Z"/>
@@ -61,6 +67,9 @@
61
67
  @else
62
68
  {{ t('login.social_provider', { provider: provider }) }}
63
69
  @end
70
+ @if(lastUsedLoginMethod === 'social:' + provider)
71
+ <span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">{{ t('login.last_used') }}</span>
72
+ @end
64
73
  </a>
65
74
  @end
66
75
  @end
@@ -106,7 +115,12 @@
106
115
  @end
107
116
 
108
117
  <div class="mt-6">
109
- <label for="password" class="mb-1 block text-sm font-medium text-gray-700">{{ t('login.password_label') }}</label>
118
+ <div class="mb-1 flex items-center justify-between">
119
+ <label for="password" class="block text-sm font-medium text-gray-700">{{ t('login.password_label') }}</label>
120
+ @if(lastUsedLoginMethod === 'password')
121
+ <span class="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">{{ t('login.last_used') }}</span>
122
+ @end
123
+ </div>
110
124
  <input id="password" name="password" type="password" required autofocus
111
125
  class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-hidden transition focus:border-gray-900 focus:ring-2 focus:ring-gray-900" />
112
126
  </div>
@@ -189,8 +203,11 @@
189
203
  <form method="POST" action="/auth/interaction/{{ uid }}/magic" class="mt-4">
190
204
  <input type="hidden" name="_csrf" value="{{ csrfToken }}">
191
205
  <button type="submit"
192
- class="w-full rounded-lg border border-gray-300 py-2.5 text-sm font-semibold text-gray-700 transition hover:bg-gray-50">
206
+ class="relative w-full rounded-lg border border-gray-300 py-2.5 text-sm font-semibold text-gray-700 transition hover:bg-gray-50">
193
207
  {{ t('login.magic_link_button') }}
208
+ @if(lastUsedLoginMethod === 'magic_link' || lastUsedLoginMethod === 'otp')
209
+ <span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">{{ t('login.last_used') }}</span>
210
+ @end
194
211
  </button>
195
212
  </form>
196
213
  @end
@@ -204,8 +221,11 @@
204
221
  <input type="hidden" name="response" id="passkey-response">
205
222
  </form>
206
223
  <button type="button" id="passkey-button"
207
- class="mt-4 w-full rounded-lg border border-gray-300 py-2.5 text-sm font-semibold text-gray-700 transition hover:bg-gray-50">
224
+ class="relative mt-4 w-full rounded-lg border border-gray-300 py-2.5 text-sm font-semibold text-gray-700 transition hover:bg-gray-50">
208
225
  {{ t('login.passkey_button') }}
226
+ @if(lastUsedLoginMethod === 'passkey')
227
+ <span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">{{ t('login.last_used') }}</span>
228
+ @end
209
229
  </button>
210
230
  <p id="passkey-error" class="mt-3 hidden text-sm text-red-600">{{ t('mfa_challenge.passkey_error') }}</p>
211
231
  {{-- M12: script inline sem nonce é bloqueado por CSP `script-src 'self'` — extraído
@@ -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-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>
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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--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{.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.top-1\/2{top:50%}.right-2{right:calc(var(--spacing) * 2)}.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}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.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-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.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))}.text-\[11px\]{font-size:11px}.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-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@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>
@@ -586,10 +586,18 @@ export interface LoginConfigInput {
586
586
  * idêntico ao de antes, e-mail idêntico). Ver `host/otp_login.ts`.
587
587
  */
588
588
  otp?: OtpLoginConfigInput;
589
+ /**
590
+ * Selo "último usado" na tela de login: o método com que este dispositivo
591
+ * entrou da última vez (senha, link, código, passkey, social) fica num cookie
592
+ * sem identidade e a tela o destaca. Por dispositivo, nunca por conta — não
593
+ * vaza como uma conta entra. Default: **ligado**. Ver `host/last_login_method.ts`.
594
+ */
595
+ lastUsedMethod?: boolean;
589
596
  }
590
597
  export interface ResolvedLoginConfig {
591
598
  requireVerifiedEmail: boolean;
592
599
  otp: ResolvedOtpLoginConfig;
600
+ lastUsedMethod: boolean;
593
601
  }
594
602
  export declare function resolveLogin(input?: LoginConfigInput): ResolvedLoginConfig;
595
603
  /**
@@ -154,6 +154,7 @@ export function resolveLogin(input) {
154
154
  return {
155
155
  requireVerifiedEmail: input?.requireVerifiedEmail ?? false,
156
156
  otp: resolveOtpLoginConfig(input?.otp),
157
+ lastUsedMethod: input?.lastUsedMethod ?? true,
157
158
  };
158
159
  }
159
160
  export function resolveRegistration(input) {
@@ -10,6 +10,7 @@ import { beginCustomMfa, clearMfaFlow, customMfaViewProps, getMfaFlow, initializ
10
10
  import { sendMagicLinkEmail, sendOtpUnlockEmail } from '../default_mailer.js';
11
11
  import { normalizeEmailIdentifier } from '../email_identifier.js';
12
12
  import { translate } from '../i18n.js';
13
+ import { readLastLoginMethod, rememberLastLoginMethod } from '../last_login_method.js';
13
14
  import { assertLoginAllowed, attemptPasswordLogin, isEmailUnverifiedBlock, } from '../login_attempt.js';
14
15
  import { magicChannelProp, normalizeLoginChannel } from '../login_channel.js';
15
16
  import { notifyLoginSuccess } from '../login_notify.js';
@@ -125,6 +126,7 @@ export default class AuthInteractionController {
125
126
  message: maintenance.message ?? translate(cfg.messages, 'maintenance.default_message'),
126
127
  });
127
128
  }
129
+ rememberLastLoginMethod(ctx, cfg, `custom:${input.method}`);
128
130
  const gate = await this.secondFactorGate(ctx, cfg, account.id, details, input.method, input.factorId, input.remember, input.passwordless === true);
129
131
  if (gate.kind === 'challenge') {
130
132
  ctx.session.put(MFA_CUSTOM_PRIMARY_KEY, {
@@ -175,6 +177,8 @@ export default class AuthInteractionController {
175
177
  authMethods,
176
178
  magicLinkAvailable: authMethods.magicLink && magicLinkCapableConfig,
177
179
  otpEnabled,
180
+ // Selo "último usado": método com que ESTE dispositivo entrou da última vez.
181
+ lastUsedLoginMethod: readLastLoginMethod(ctx, cfg),
178
182
  };
179
183
  }
180
184
  /**
@@ -495,6 +499,7 @@ export default class AuthInteractionController {
495
499
  // Gate do 2º fator — a MESMA regra dos três caminhos de login (ver
496
500
  // `secondFactorGate`). Quando desafia, não finaliza a interaction: guarda o
497
501
  // accountId pendente e devolve a tela.
502
+ rememberLastLoginMethod(ctx, cfg, 'password');
498
503
  const gate = await this.secondFactorGate(ctx, cfg, acc.id, details, 'pwd');
499
504
  if (gate.kind === 'challenge')
500
505
  return gate.response;
@@ -1349,6 +1354,7 @@ export default class AuthInteractionController {
1349
1354
  });
1350
1355
  }
1351
1356
  // Conta com segundo fator não termina o login só com o e-mail.
1357
+ rememberLastLoginMethod(ctx, cfg, 'magic_link');
1352
1358
  const magicGate = await this.secondFactorGate(ctx, cfg, acc.id, await service.interactions.details(ctx), 'email');
1353
1359
  if (magicGate.kind === 'challenge')
1354
1360
  return magicGate.response;
@@ -1471,6 +1477,7 @@ export default class AuthInteractionController {
1471
1477
  clientId,
1472
1478
  });
1473
1479
  // Conta com segundo fator não termina o login só com o código do e-mail.
1480
+ rememberLastLoginMethod(ctx, cfg, 'otp');
1474
1481
  const otpGate = await this.secondFactorGate(ctx, cfg, result.account.id, await service.interactions.details(ctx), 'email');
1475
1482
  if (otpGate.kind === 'challenge')
1476
1483
  return otpGate.response;
@@ -1738,6 +1745,11 @@ export default class AuthInteractionController {
1738
1745
  return ctx.response.redirect(`/auth/interaction/${ctx.request.param('uid')}`);
1739
1746
  return this.finishVerifiedMfa(ctx, cfg, details, 'webauthn', 'webauthn');
1740
1747
  }
1748
+ // Passkey como fator PRIMÁRIO (passkey-first/autofill). Como 2º fator ela não
1749
+ // é o método com que a pessoa se identificou — o selo fica com o primário.
1750
+ if (ctx.session.get(MFA_PENDING_KEY) !== accountId) {
1751
+ rememberLastLoginMethod(ctx, cfg, 'passkey');
1752
+ }
1741
1753
  if (cfg.mfa) {
1742
1754
  const gate = await this.secondFactorGate(ctx, cfg, accountId, details, 'webauthn');
1743
1755
  if (gate.kind === 'challenge')
@@ -1845,6 +1857,7 @@ export default class AuthInteractionController {
1845
1857
  // Senha trocada: limpa o step e finaliza o login.
1846
1858
  ctx.session.forget(PASSWORD_EXPIRED_KEY);
1847
1859
  // Verifica se precisa de MFA mesmo após a troca — mesma regra do resto.
1860
+ rememberLastLoginMethod(ctx, cfg, 'password');
1848
1861
  const expiredGate = await this.secondFactorGate(ctx, cfg, accountId, details, 'pwd');
1849
1862
  if (expiredGate.kind === 'challenge')
1850
1863
  return expiredGate.response;
@@ -7,6 +7,7 @@ import { guardBotProtection, resolveEffectiveBotProtection } from '../bot_protec
7
7
  import { brandFor } from '../branding.js';
8
8
  import { sendEmailVerificationEmail, sendMagicLinkEmail, sendPasswordResetEmail, } from '../default_mailer.js';
9
9
  import { translate } from '../i18n.js';
10
+ import { rememberLastLoginMethod } from '../last_login_method.js';
10
11
  import { authkitOrigin } from '../origin.js';
11
12
  import { RuntimeSettings, resolveRuntimeSettings } from '../runtime_settings.js';
12
13
  import { resolveEffectiveAuthMethods, resolveEffectiveMaintenanceMode, resolveEffectiveRegistration, } from '../runtime_toggles.js';
@@ -165,6 +166,8 @@ export default class AuthRegistrationController {
165
166
  ip: ctx.request.ip?.() ?? null,
166
167
  clientId: details.params.client_id ?? null,
167
168
  });
169
+ // Conta nova entra por senha: o selo "último usado" já nasce no método certo.
170
+ rememberLastLoginMethod(ctx, cfg, 'password');
168
171
  // Finaliza a interaction como login (interactionFinished escreve o redirect 303 que
169
172
  // retoma o authorize; o form nativo da tela segue esse redirect no sucesso).
170
173
  const result = await service.interactions.login(ctx, {
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
3
3
  import { supportsLoginMethodsPreference, supportsMagicLink, supportsPasskeys, supportsProviderIdentity, } from '../../accounts/account_store.js';
4
4
  import { resolveAccountRoles } from '../account_roles.js';
5
5
  import { normalizeEmailIdentifier } from '../email_identifier.js';
6
+ import { rememberLastLoginMethod } from '../last_login_method.js';
6
7
  import { assertLoginAllowed } from '../login_attempt.js';
7
8
  import { resolveRuntimeSettingsOrNoop } from '../runtime_settings.js';
8
9
  import { resolveEffectiveAuthMethods } from '../runtime_toggles.js';
@@ -196,6 +197,7 @@ export default class AuthSocialController {
196
197
  }
197
198
  }
198
199
  ctx.session.forget(UID_SESSION_KEY);
200
+ rememberLastLoginMethod(ctx, cfg, `social:${provider}`);
199
201
  // 2º fator: o provider provou a identidade, mas quem cadastrou TOTP/passkey
200
202
  // aqui espera que o fator valha também neste caminho. O desafio mora no
201
203
  // interaction controller (mesmo gate de senha/magic link/OTP), então este
@@ -50,6 +50,7 @@ export declare const DEFAULT_MESSAGES: {
50
50
  'login.magic_link_sent': string;
51
51
  'signup.magic_link_sent': string;
52
52
  'login.passkey_button': string;
53
+ 'login.last_used': string;
53
54
  'login.otp_label': string;
54
55
  'login.otp_placeholder': string;
55
56
  'login.otp_submit': string;
@@ -809,6 +810,7 @@ export declare const PT_BR_MESSAGES: {
809
810
  'login.magic_link_sent': string;
810
811
  'signup.magic_link_sent': string;
811
812
  'login.passkey_button': string;
813
+ 'login.last_used': string;
812
814
  'login.otp_label': string;
813
815
  'login.otp_placeholder': string;
814
816
  'login.otp_submit': string;
@@ -41,6 +41,8 @@ export const DEFAULT_MESSAGES = {
41
41
  'login.magic_link_sent': 'If the account exists, we sent you a login link.',
42
42
  'signup.magic_link_sent': 'Check your email — we sent you a link to finish creating your account.',
43
43
  'login.passkey_button': 'Sign in with a passkey',
44
+ // Selo do método com que este dispositivo entrou da última vez.
45
+ 'login.last_used': 'Last used',
44
46
  // Login por OTP (código digitável).
45
47
  'login.otp_label': 'Enter the login code from the email',
46
48
  'login.otp_placeholder': '000000',
@@ -891,6 +893,7 @@ export const PT_BR_MESSAGES = {
891
893
  'login.magic_link_sent': 'Se a conta existir, enviamos um link de login.',
892
894
  'signup.magic_link_sent': 'Enviamos um link para o seu e-mail. Abra-o para concluir o cadastro.',
893
895
  'login.passkey_button': 'Entrar com passkey',
896
+ 'login.last_used': 'Último usado',
894
897
  // Login por OTP (código digitável).
895
898
  'login.otp_label': 'Digite o código de login do e-mail',
896
899
  'login.otp_placeholder': '000000',
@@ -0,0 +1,67 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ /**
3
+ * "Último método usado" no login (o selo "Last used" do Cloudflare, Vercel, Clerk…).
4
+ *
5
+ * O método fica num cookie DESTE DISPOSITIVO, não na conta. É de propósito:
6
+ * - o passo 1 (identifier) ainda não sabe quem é o usuário, e é exatamente ali
7
+ * que o selo mais ajuda ("da última vez você entrou com Google");
8
+ * - guardar por conta e mostrar no passo 2 diria a quem digita um e-mail qualquer
9
+ * como aquela conta entra — vazamento de enumeração. O cookie só conta ao
10
+ * próprio browser o que ele mesmo fez.
11
+ *
12
+ * O cookie não carrega identidade nenhuma (nem e-mail, nem accountId), só o método.
13
+ * É puramente de SUPERFÍCIE: a tela destaca um botão, mas nada no fluxo muda — os
14
+ * métodos oferecidos continuam vindo de `authMethods`, e um valor adulterado no
15
+ * cookie só destaca outro botão (ou nenhum, se não passar na validação).
16
+ *
17
+ * Gravado quando o fator PRIMÁRIO é aceito. Numa conta com 2º fator, isso acontece
18
+ * antes do desafio de MFA: o selo responde "como você se identificou", que é o
19
+ * botão que a pessoa procura na próxima vez.
20
+ */
21
+ /** Nome do cookie do último método de login. */
22
+ export declare const LAST_LOGIN_METHOD_COOKIE = "authkit_last_login";
23
+ /**
24
+ * Método de login primário. `social:<provider>` e `custom:<id>` carregam o id do
25
+ * provedor/método para a tela destacar o botão certo entre vários.
26
+ */
27
+ export type LastLoginMethod = 'password' | 'magic_link' | 'otp' | 'passkey' | `social:${string}` | `custom:${string}`;
28
+ /**
29
+ * Valida um valor lido do cookie. Só os métodos conhecidos passam; qualquer outra
30
+ * coisa (ausente, lixo, valor adulterado) vira `undefined` — sem selo.
31
+ */
32
+ export declare function normalizeLastLoginMethod(raw: unknown): LastLoginMethod | undefined;
33
+ /**
34
+ * Lê o último método deste dispositivo para a tela de login. Fail-safe: feature
35
+ * desligada, ctx sem cookies ou qualquer erro → `undefined` (sem selo).
36
+ */
37
+ export declare function readLastLoginMethod(ctx: HttpContext, cfg: {
38
+ login?: {
39
+ lastUsedMethod?: boolean;
40
+ };
41
+ }): LastLoginMethod | undefined;
42
+ /**
43
+ * Registra o método primário que acabou de ser aceito. Grava via resposta do
44
+ * Adonis; quando o login termina no `interactionFinished` (que encerra a resposta
45
+ * crua), o `completeLogin` repassa o cookie pendente — ver
46
+ * {@link relayPendingCookies}. Best-effort: nunca derruba o login.
47
+ */
48
+ export declare function rememberLastLoginMethod(ctx: HttpContext, cfg: {
49
+ login?: {
50
+ lastUsedMethod?: boolean;
51
+ };
52
+ }, method: LastLoginMethod): void;
53
+ /**
54
+ * Repassa os `Set-Cookie` que o handler gravou na resposta do Adonis para a
55
+ * resposta crua do Node.
56
+ *
57
+ * O Adonis só escreve os headers no `finish()`, depois do handler. Mas o
58
+ * `interactionFinished` do oidc-provider escreve o 303 e dá `res.end()` direto na
59
+ * resposta crua — quando o Adonis chega no `finish()`, a resposta já saiu e todo
60
+ * cookie gravado via `ctx.response.cookie()` no mesmo request é descartado em
61
+ * silêncio. Chamado pelo `completeLogin` logo antes do `interactionFinished`.
62
+ *
63
+ * Os cookies saem do Adonis ao serem repassados, então não há como irem em dobro.
64
+ * O `Set-Cookie` que o próprio oidc-provider grava depois é ACRESCENTADO aos
65
+ * existentes (a lib `cookies` do Koa preserva o header), então nada se perde.
66
+ */
67
+ export declare function relayPendingCookies(ctx: HttpContext): void;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * "Último método usado" no login (o selo "Last used" do Cloudflare, Vercel, Clerk…).
3
+ *
4
+ * O método fica num cookie DESTE DISPOSITIVO, não na conta. É de propósito:
5
+ * - o passo 1 (identifier) ainda não sabe quem é o usuário, e é exatamente ali
6
+ * que o selo mais ajuda ("da última vez você entrou com Google");
7
+ * - guardar por conta e mostrar no passo 2 diria a quem digita um e-mail qualquer
8
+ * como aquela conta entra — vazamento de enumeração. O cookie só conta ao
9
+ * próprio browser o que ele mesmo fez.
10
+ *
11
+ * O cookie não carrega identidade nenhuma (nem e-mail, nem accountId), só o método.
12
+ * É puramente de SUPERFÍCIE: a tela destaca um botão, mas nada no fluxo muda — os
13
+ * métodos oferecidos continuam vindo de `authMethods`, e um valor adulterado no
14
+ * cookie só destaca outro botão (ou nenhum, se não passar na validação).
15
+ *
16
+ * Gravado quando o fator PRIMÁRIO é aceito. Numa conta com 2º fator, isso acontece
17
+ * antes do desafio de MFA: o selo responde "como você se identificou", que é o
18
+ * botão que a pessoa procura na próxima vez.
19
+ */
20
+ /** Nome do cookie do último método de login. */
21
+ export const LAST_LOGIN_METHOD_COOKIE = 'authkit_last_login';
22
+ /** Validade do cookie: 1 ano (renovado a cada login). */
23
+ const LAST_LOGIN_METHOD_MAX_AGE = 365 * 24 * 60 * 60;
24
+ const FIXED_METHODS = new Set(['password', 'magic_link', 'otp', 'passkey']);
25
+ /** Ids de provedor/método: mesmo formato curto que vai em rota/config. */
26
+ const QUALIFIED_METHOD = /^(social|custom):[a-z0-9][a-z0-9_-]{0,63}$/i;
27
+ /**
28
+ * Valida um valor lido do cookie. Só os métodos conhecidos passam; qualquer outra
29
+ * coisa (ausente, lixo, valor adulterado) vira `undefined` — sem selo.
30
+ */
31
+ export function normalizeLastLoginMethod(raw) {
32
+ if (typeof raw !== 'string')
33
+ return undefined;
34
+ if (FIXED_METHODS.has(raw) || QUALIFIED_METHOD.test(raw))
35
+ return raw;
36
+ return undefined;
37
+ }
38
+ /** `false` desliga a feature (`login.lastUsedMethod: false`); ausente = ligada. */
39
+ function isEnabled(cfg) {
40
+ return cfg.login?.lastUsedMethod !== false;
41
+ }
42
+ /**
43
+ * Lê o último método deste dispositivo para a tela de login. Fail-safe: feature
44
+ * desligada, ctx sem cookies ou qualquer erro → `undefined` (sem selo).
45
+ */
46
+ export function readLastLoginMethod(ctx, cfg) {
47
+ if (!isEnabled(cfg))
48
+ return undefined;
49
+ try {
50
+ return normalizeLastLoginMethod(ctx.request.plainCookie?.(LAST_LOGIN_METHOD_COOKIE));
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ /**
57
+ * Registra o método primário que acabou de ser aceito. Grava via resposta do
58
+ * Adonis; quando o login termina no `interactionFinished` (que encerra a resposta
59
+ * crua), o `completeLogin` repassa o cookie pendente — ver
60
+ * {@link relayPendingCookies}. Best-effort: nunca derruba o login.
61
+ */
62
+ export function rememberLastLoginMethod(ctx, cfg, method) {
63
+ if (!isEnabled(cfg))
64
+ return;
65
+ if (!normalizeLastLoginMethod(method))
66
+ return;
67
+ try {
68
+ ctx.response.plainCookie?.(LAST_LOGIN_METHOD_COOKIE, method, {
69
+ httpOnly: true,
70
+ sameSite: 'lax',
71
+ path: '/',
72
+ maxAge: LAST_LOGIN_METHOD_MAX_AGE,
73
+ secure: ctx.request.secure?.() ?? false,
74
+ });
75
+ }
76
+ catch {
77
+ // Selo é cosmético: falha ao gravar não interfere no login.
78
+ }
79
+ }
80
+ /**
81
+ * Repassa os `Set-Cookie` que o handler gravou na resposta do Adonis para a
82
+ * resposta crua do Node.
83
+ *
84
+ * O Adonis só escreve os headers no `finish()`, depois do handler. Mas o
85
+ * `interactionFinished` do oidc-provider escreve o 303 e dá `res.end()` direto na
86
+ * resposta crua — quando o Adonis chega no `finish()`, a resposta já saiu e todo
87
+ * cookie gravado via `ctx.response.cookie()` no mesmo request é descartado em
88
+ * silêncio. Chamado pelo `completeLogin` logo antes do `interactionFinished`.
89
+ *
90
+ * Os cookies saem do Adonis ao serem repassados, então não há como irem em dobro.
91
+ * O `Set-Cookie` que o próprio oidc-provider grava depois é ACRESCENTADO aos
92
+ * existentes (a lib `cookies` do Koa preserva o header), então nada se perde.
93
+ */
94
+ export function relayPendingCookies(ctx) {
95
+ try {
96
+ const response = ctx.response;
97
+ const raw = response?.response;
98
+ if (!raw || typeof raw.setHeader !== 'function' || raw.headersSent)
99
+ return;
100
+ const pending = response.getHeader?.('set-cookie');
101
+ if (pending === undefined || pending === null)
102
+ return;
103
+ const toList = (value) => value === undefined || value === null
104
+ ? []
105
+ : Array.isArray(value)
106
+ ? value.map(String)
107
+ : [String(value)];
108
+ // O `getHeader` do Adonis também enxerga o que já está na resposta crua, então
109
+ // a lista pendente pode repetir o que lá está — dedup preservando a ordem.
110
+ const cookies = [...new Set([...toList(raw.getHeader?.('set-cookie')), ...toList(pending)])];
111
+ // O `removeHeader` do Adonis apaga também da resposta crua — por isso vem
112
+ // ANTES do `setHeader`, que regrava a lista completa.
113
+ response.removeHeader?.('set-cookie');
114
+ raw.setHeader('set-cookie', cookies);
115
+ }
116
+ catch {
117
+ // Repasse é best-effort: nunca derruba o login.
118
+ }
119
+ }
@@ -1,5 +1,6 @@
1
1
  import { readActiveOrgFromHostCtx } from '../host/active_org_cookie.js';
2
2
  import { InteractionSessionLostException, isInteractionSessionLost, } from '../host/interaction_recovery.js';
3
+ import { relayPendingCookies } from '../host/last_login_method.js';
3
4
  /**
4
5
  * Choke point ÚNICO de `interactionDetails`: converte o `SessionNotFound` do
5
6
  * `oidc-provider` (sessão de interaction perdida — caso NORMAL) na exceção
@@ -31,6 +32,7 @@ export function createInteractionActions(provider, deps) {
31
32
  const account = await deps.verifyCredentials(email, password);
32
33
  if (!account)
33
34
  return { ok: false };
35
+ relayPendingCookies(ctx);
34
36
  await provider.interactionFinished(ctx.request.request, ctx.response.response, { login: { accountId: account.id } }, { mergeWithLastSubmission: false });
35
37
  return { ok: true };
36
38
  },
@@ -47,6 +49,10 @@ export function createInteractionActions(provider, deps) {
47
49
  // O campo `remember` é nativo do oidc-provider v9 — veja resume.js.
48
50
  if (extra?.remember === false)
49
51
  login.remember = false;
52
+ // O `interactionFinished` encerra a resposta crua: os cookies que o handler
53
+ // gravou no Adonis (último método de login, dispositivo confiável) só saem
54
+ // se forem repassados antes.
55
+ relayPendingCookies(ctx);
50
56
  await provider.interactionFinished(ctx.request.request, ctx.response.response, { login }, { mergeWithLastSubmission: false });
51
57
  return { ok: true };
52
58
  },
@@ -75,6 +81,7 @@ export function createInteractionActions(provider, deps) {
75
81
  }
76
82
  }
77
83
  const grantId = await grant.save();
84
+ relayPendingCookies(ctx);
78
85
  return provider.interactionFinished(ctx.request.request, ctx.response.response, { consent: { grantId } }, { mergeWithLastSubmission: true });
79
86
  },
80
87
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.83.0",
3
+ "version": "0.84.0",
4
4
  "description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",
@@ -116,7 +116,7 @@
116
116
  "qrcode": "1.5.4"
117
117
  },
118
118
  "devDependencies": {
119
- "@adonis-agora/authkit-react": "0.25.2",
119
+ "@adonis-agora/authkit-react": "0.26.0",
120
120
  "@adonis-agora/durable": "0.53.0",
121
121
  "@adonis-agora/telescope": "0.23.0",
122
122
  "@adonisjs/ally": "6.3.2",