@dudousxd/adonis-authkit-server 0.1.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/LICENSE +21 -0
- package/README.md +137 -0
- package/build/assets/grafana/authkit-dashboard.json +118 -0
- package/build/commands/commands.json +30 -0
- package/build/commands/configure.d.ts +2 -0
- package/build/commands/configure.js +42 -0
- package/build/commands/eject.d.ts +11 -0
- package/build/commands/eject.js +96 -0
- package/build/commands/main.d.ts +12 -0
- package/build/commands/main.js +38 -0
- package/build/commands/ui_preset.d.ts +4 -0
- package/build/commands/ui_preset.js +32 -0
- package/build/database/migrations/make_authkit_oidc_table.d.ts +6 -0
- package/build/database/migrations/make_authkit_oidc_table.js +19 -0
- package/build/host/views/account/login.edge +29 -0
- package/build/host/views/account/mfa.edge +151 -0
- package/build/host/views/account/tokens.edge +70 -0
- package/build/host/views/admin/audit.edge +72 -0
- package/build/host/views/admin/clients.edge +51 -0
- package/build/host/views/admin/dashboard.edge +58 -0
- package/build/host/views/admin/users.edge +76 -0
- package/build/host/views/consent.edge +19 -0
- package/build/host/views/forgot.edge +30 -0
- package/build/host/views/login.edge +91 -0
- package/build/host/views/mfa-challenge.edge +88 -0
- package/build/host/views/reset.edge +29 -0
- package/build/host/views/signup.edge +44 -0
- package/build/host/views/verify-email.edge +16 -0
- package/build/index.d.ts +42 -0
- package/build/index.js +28 -0
- package/build/providers/authkit_server_provider.d.ts +19 -0
- package/build/providers/authkit_server_provider.js +81 -0
- package/build/src/accounts/account_store.d.ts +136 -0
- package/build/src/accounts/account_store.js +1 -0
- package/build/src/accounts/lucid_account_store.d.ts +75 -0
- package/build/src/accounts/lucid_account_store.js +396 -0
- package/build/src/adapters/adapter_contract.d.ts +18 -0
- package/build/src/adapters/adapter_contract.js +1 -0
- package/build/src/adapters/database_adapter.d.ts +15 -0
- package/build/src/adapters/database_adapter.js +63 -0
- package/build/src/adapters/factory.d.ts +30 -0
- package/build/src/adapters/factory.js +43 -0
- package/build/src/adapters/redis_adapter.d.ts +16 -0
- package/build/src/adapters/redis_adapter.js +95 -0
- package/build/src/audit/audit_sink.d.ts +54 -0
- package/build/src/audit/audit_sink.js +1 -0
- package/build/src/audit/lucid_audit_sink.d.ts +10 -0
- package/build/src/audit/lucid_audit_sink.js +60 -0
- package/build/src/controllers/oidc_callback_controller.d.ts +22 -0
- package/build/src/controllers/oidc_callback_controller.js +33 -0
- package/build/src/define_config.d.ts +261 -0
- package/build/src/define_config.js +115 -0
- package/build/src/host/account_lockout.d.ts +86 -0
- package/build/src/host/account_lockout.js +185 -0
- package/build/src/host/augmentations.d.ts +1 -0
- package/build/src/host/augmentations.js +1 -0
- package/build/src/host/branding.d.ts +17 -0
- package/build/src/host/branding.js +8 -0
- package/build/src/host/controllers/account_mfa_controller.d.ts +30 -0
- package/build/src/host/controllers/account_mfa_controller.js +157 -0
- package/build/src/host/controllers/account_session_controller.d.ts +7 -0
- package/build/src/host/controllers/account_session_controller.js +50 -0
- package/build/src/host/controllers/account_tokens_controller.d.ts +7 -0
- package/build/src/host/controllers/account_tokens_controller.js +55 -0
- package/build/src/host/controllers/admin/admin_audit_controller.d.ts +10 -0
- package/build/src/host/controllers/admin/admin_audit_controller.js +56 -0
- package/build/src/host/controllers/admin/admin_clients_controller.d.ts +10 -0
- package/build/src/host/controllers/admin/admin_clients_controller.js +24 -0
- package/build/src/host/controllers/admin/admin_dashboard_controller.d.ts +10 -0
- package/build/src/host/controllers/admin/admin_dashboard_controller.js +27 -0
- package/build/src/host/controllers/admin/admin_users_controller.d.ts +11 -0
- package/build/src/host/controllers/admin/admin_users_controller.js +53 -0
- package/build/src/host/controllers/interaction_controller.d.ts +44 -0
- package/build/src/host/controllers/interaction_controller.js +304 -0
- package/build/src/host/controllers/pat_introspection_controller.d.ts +22 -0
- package/build/src/host/controllers/pat_introspection_controller.js +46 -0
- package/build/src/host/controllers/registration_controller.d.ts +18 -0
- package/build/src/host/controllers/registration_controller.js +169 -0
- package/build/src/host/controllers/social_controller.d.ts +8 -0
- package/build/src/host/controllers/social_controller.js +82 -0
- package/build/src/host/default_mailer.d.ts +39 -0
- package/build/src/host/default_mailer.js +141 -0
- package/build/src/host/email_templates.d.ts +35 -0
- package/build/src/host/email_templates.js +66 -0
- package/build/src/host/i18n.d.ts +178 -0
- package/build/src/host/i18n.js +208 -0
- package/build/src/host/middleware/account_auth.d.ts +7 -0
- package/build/src/host/middleware/account_auth.js +11 -0
- package/build/src/host/rate_limit.d.ts +32 -0
- package/build/src/host/rate_limit.js +87 -0
- package/build/src/host/register_auth_host.d.ts +41 -0
- package/build/src/host/register_auth_host.js +133 -0
- package/build/src/host/renderers/edge_renderer.d.ts +3 -0
- package/build/src/host/renderers/edge_renderer.js +29 -0
- package/build/src/host/renderers/inertia_renderer.d.ts +5 -0
- package/build/src/host/renderers/inertia_renderer.js +26 -0
- package/build/src/host/validators.d.ts +39 -0
- package/build/src/host/validators.js +13 -0
- package/build/src/keys/jwks_manager.d.ts +6 -0
- package/build/src/keys/jwks_manager.js +11 -0
- package/build/src/mixins/with_audit_log.d.ts +19 -0
- package/build/src/mixins/with_audit_log.js +41 -0
- package/build/src/mixins/with_auth_user.d.ts +18 -0
- package/build/src/mixins/with_auth_user.js +39 -0
- package/build/src/mixins/with_credentials.d.ts +20 -0
- package/build/src/mixins/with_credentials.js +29 -0
- package/build/src/mixins/with_mfa.d.ts +31 -0
- package/build/src/mixins/with_mfa.js +39 -0
- package/build/src/mixins/with_personal_access_token.d.ts +19 -0
- package/build/src/mixins/with_personal_access_token.js +44 -0
- package/build/src/mixins/with_provider_identity.d.ts +20 -0
- package/build/src/mixins/with_provider_identity.js +32 -0
- package/build/src/mixins/with_webauthn_credential.d.ts +37 -0
- package/build/src/mixins/with_webauthn_credential.js +49 -0
- package/build/src/observability/metrics_controller.d.ts +5 -0
- package/build/src/observability/metrics_controller.js +24 -0
- package/build/src/observability/metrics_service.d.ts +2 -0
- package/build/src/observability/metrics_service.js +7 -0
- package/build/src/observability/otel_recorder.d.ts +10 -0
- package/build/src/observability/otel_recorder.js +59 -0
- package/build/src/observability/wire_provider_events.d.ts +12 -0
- package/build/src/observability/wire_provider_events.js +19 -0
- package/build/src/pat/lucid_pat_store.d.ts +6 -0
- package/build/src/pat/lucid_pat_store.js +62 -0
- package/build/src/pat/pat_store.d.ts +31 -0
- package/build/src/pat/pat_store.js +1 -0
- package/build/src/pat/pat_tokens.d.ts +4 -0
- package/build/src/pat/pat_tokens.js +9 -0
- package/build/src/provider/build_provider.d.ts +8 -0
- package/build/src/provider/build_provider.js +101 -0
- package/build/src/provider/interaction_actions.d.ts +21 -0
- package/build/src/provider/interaction_actions.js +32 -0
- package/build/src/provider/oidc_service.d.ts +17 -0
- package/build/src/provider/oidc_service.js +84 -0
- package/build/src/provider/token_exchange.d.ts +15 -0
- package/build/src/provider/token_exchange.js +72 -0
- package/build/src/register_routes.d.ts +16 -0
- package/build/src/register_routes.js +21 -0
- package/build/stubs/config/authkit.stub +29 -0
- package/build/stubs/main.d.ts +1 -0
- package/build/stubs/main.js +2 -0
- package/build/stubs/models/auth_user.stub +13 -0
- package/build/stubs/ui/edge/views/consent.edge +13 -0
- package/build/stubs/ui/edge/views/login.edge +19 -0
- package/build/stubs/ui/react/components/auth_shell.tsx +67 -0
- package/build/stubs/ui/react/pages/account/login.tsx +56 -0
- package/build/stubs/ui/react/pages/account/mfa.tsx +132 -0
- package/build/stubs/ui/react/pages/account/tokens.tsx +88 -0
- package/build/stubs/ui/react/pages/consent.tsx +39 -0
- package/build/stubs/ui/react/pages/forgot.tsx +44 -0
- package/build/stubs/ui/react/pages/login.tsx +171 -0
- package/build/stubs/ui/react/pages/mfa-challenge.tsx +72 -0
- package/build/stubs/ui/react/pages/reset.tsx +58 -0
- package/build/stubs/ui/react/pages/signup.tsx +78 -0
- package/build/stubs/ui/react/pages/verify-email.tsx +24 -0
- package/build/types.d.ts +7 -0
- package/build/types.js +1 -0
- package/package.json +108 -0
- package/stubs/config/authkit.stub +29 -0
- package/stubs/main.ts +2 -0
- package/stubs/models/auth_user.stub +13 -0
- package/stubs/ui/edge/views/consent.edge +13 -0
- package/stubs/ui/edge/views/login.edge +19 -0
- package/stubs/ui/react/components/auth_shell.tsx +67 -0
- package/stubs/ui/react/pages/account/login.tsx +56 -0
- package/stubs/ui/react/pages/account/mfa.tsx +132 -0
- package/stubs/ui/react/pages/account/tokens.tsx +88 -0
- package/stubs/ui/react/pages/consent.tsx +39 -0
- package/stubs/ui/react/pages/forgot.tsx +44 -0
- package/stubs/ui/react/pages/login.tsx +171 -0
- package/stubs/ui/react/pages/mfa-challenge.tsx +72 -0
- package/stubs/ui/react/pages/reset.tsx +58 -0
- package/stubs/ui/react/pages/signup.tsx +78 -0
- package/stubs/ui/react/pages/verify-email.tsx +24 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('account.mfa.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen bg-gray-100 p-4">
|
|
5
|
+
<div class="mx-auto max-w-2xl">
|
|
6
|
+
<div class="flex items-center justify-between py-6">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('account.mfa.title') }}</h1>
|
|
10
|
+
</div>
|
|
11
|
+
<form method="POST" action="/account/logout">
|
|
12
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
13
|
+
<button type="submit" class="text-sm text-gray-500 hover:underline">{{ t('account.mfa.logout') }}</button>
|
|
14
|
+
</form>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
@if(error)
|
|
18
|
+
<p class="mb-4 text-sm text-red-600">{{ error }}</p>
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
{{-- Recovery codes mostrados uma única vez após confirmar o enrollment --}}
|
|
22
|
+
@if(recoveryCodes && recoveryCodes.length > 0)
|
|
23
|
+
<div class="mb-6 rounded-lg border border-emerald-300 bg-emerald-50 p-4">
|
|
24
|
+
<p class="text-sm font-medium text-emerald-900">
|
|
25
|
+
{{ t('account.mfa.recovery_codes_notice') }}
|
|
26
|
+
</p>
|
|
27
|
+
<ul class="mt-3 grid grid-cols-2 gap-2">
|
|
28
|
+
@each(rc in recoveryCodes)
|
|
29
|
+
<li><code class="block rounded bg-white px-3 py-2 text-sm text-emerald-800 ring-1 ring-emerald-200">{{ rc }}</code></li>
|
|
30
|
+
@end
|
|
31
|
+
</ul>
|
|
32
|
+
</div>
|
|
33
|
+
@end
|
|
34
|
+
|
|
35
|
+
@if(enrolling)
|
|
36
|
+
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
|
|
37
|
+
<p class="text-sm text-gray-600">
|
|
38
|
+
{{ t('account.mfa.enroll_intro') }}
|
|
39
|
+
</p>
|
|
40
|
+
@if(qrDataUrl)
|
|
41
|
+
<img src="{{ qrDataUrl }}" alt="{{ t('account.mfa.qr_alt') }}" class="mx-auto my-4 h-48 w-48" />
|
|
42
|
+
@end
|
|
43
|
+
@if(secret)
|
|
44
|
+
<p class="text-center text-xs text-gray-500">{{ t('account.mfa.manual_intro') }}</p>
|
|
45
|
+
<code class="mx-auto mt-1 block w-fit break-all rounded bg-gray-100 px-3 py-2 text-sm text-gray-800">{{ secret }}</code>
|
|
46
|
+
@end
|
|
47
|
+
|
|
48
|
+
<form method="POST" action="/account/mfa/confirm" class="mt-6">
|
|
49
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
50
|
+
<label for="code" class="mb-1 block text-sm font-medium text-gray-700">{{ t('account.mfa.confirm_code_label') }}</label>
|
|
51
|
+
<input id="code" name="code" inputmode="numeric" pattern="[0-9]*" maxlength="6" autofocus
|
|
52
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-center text-lg tracking-[0.4em] outline-none focus:border-gray-900" />
|
|
53
|
+
<button type="submit" class="mt-4 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white">
|
|
54
|
+
{{ t('account.mfa.activate') }}
|
|
55
|
+
</button>
|
|
56
|
+
</form>
|
|
57
|
+
</div>
|
|
58
|
+
@elseif(enabled)
|
|
59
|
+
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
|
|
60
|
+
<p class="text-sm text-gray-700">
|
|
61
|
+
{{{ t('account.mfa.enabled_html') }}}
|
|
62
|
+
</p>
|
|
63
|
+
<form method="POST" action="/account/mfa/disable" class="mt-4">
|
|
64
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
65
|
+
<button type="submit" class="rounded-lg border border-red-300 px-4 py-2 text-sm font-semibold text-red-600 hover:bg-red-50">
|
|
66
|
+
{{ t('account.mfa.disable') }}
|
|
67
|
+
</button>
|
|
68
|
+
</form>
|
|
69
|
+
</div>
|
|
70
|
+
@else
|
|
71
|
+
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
|
|
72
|
+
<p class="text-sm text-gray-700">
|
|
73
|
+
{{ t('account.mfa.disabled_intro') }}
|
|
74
|
+
</p>
|
|
75
|
+
<form method="POST" action="/account/mfa/enroll" class="mt-4">
|
|
76
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
77
|
+
<button type="submit" class="rounded-lg bg-gray-900 px-4 py-2 text-sm font-semibold text-white">
|
|
78
|
+
{{ t('account.mfa.enable') }}
|
|
79
|
+
</button>
|
|
80
|
+
</form>
|
|
81
|
+
</div>
|
|
82
|
+
@end
|
|
83
|
+
|
|
84
|
+
@if(passkeysSupported)
|
|
85
|
+
{{-- Passkeys / chaves de acesso (WebAuthn) — 2º fator alternativo ao TOTP. --}}
|
|
86
|
+
<div class="mt-6 rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
|
|
87
|
+
<h2 class="text-base font-semibold text-gray-900">{{ t('mfa.passkey.section_title') }}</h2>
|
|
88
|
+
<p class="mt-1 text-sm text-gray-600">{{ t('mfa.passkey.section_intro') }}</p>
|
|
89
|
+
|
|
90
|
+
@if(passkeys && passkeys.length > 0)
|
|
91
|
+
<ul class="mt-4 space-y-2">
|
|
92
|
+
@each(pk in passkeys)
|
|
93
|
+
<li class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
|
|
94
|
+
<span class="text-sm text-gray-800">{{ pk.label ? pk.label : t('mfa.passkey.unnamed') }}</span>
|
|
95
|
+
<form method="POST" action="/account/mfa/passkeys/{{ pk.id }}/remove">
|
|
96
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
97
|
+
<button type="submit" class="text-sm text-red-600 hover:underline">{{ t('mfa.passkey.remove') }}</button>
|
|
98
|
+
</form>
|
|
99
|
+
</li>
|
|
100
|
+
@end
|
|
101
|
+
</ul>
|
|
102
|
+
@else
|
|
103
|
+
<p class="mt-4 text-sm text-gray-400">{{ t('mfa.passkey.empty') }}</p>
|
|
104
|
+
@end
|
|
105
|
+
|
|
106
|
+
<button type="button" id="passkey-add"
|
|
107
|
+
class="mt-4 rounded-lg border border-gray-300 px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50">
|
|
108
|
+
{{ t('mfa.passkey.add') }}
|
|
109
|
+
</button>
|
|
110
|
+
<p id="passkey-error" class="mt-3 hidden text-sm text-red-600">{{ t('mfa.passkey.register_error') }}</p>
|
|
111
|
+
</div>
|
|
112
|
+
@end
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
@if(passkeysSupported)
|
|
116
|
+
<script type="module">
|
|
117
|
+
import { startRegistration } from 'https://cdn.jsdelivr.net/npm/@simplewebauthn/browser@13/dist/bundle/index.js'
|
|
118
|
+
const btn = document.getElementById('passkey-add')
|
|
119
|
+
const errEl = document.getElementById('passkey-error')
|
|
120
|
+
const csrf = '{{ csrfToken }}'
|
|
121
|
+
btn?.addEventListener('click', async () => {
|
|
122
|
+
errEl.classList.add('hidden')
|
|
123
|
+
btn.disabled = true
|
|
124
|
+
try {
|
|
125
|
+
// 1) Opções de registro (challenge guardado na sessão server-side).
|
|
126
|
+
const optsRes = await fetch('/account/mfa/passkeys/options', {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: { 'content-type': 'application/json', 'x-csrf-token': csrf },
|
|
129
|
+
body: JSON.stringify({}),
|
|
130
|
+
})
|
|
131
|
+
if (!optsRes.ok) throw new Error('options')
|
|
132
|
+
const optionsJSON = await optsRes.json()
|
|
133
|
+
// 2) Cerimônia de registro no authenticator.
|
|
134
|
+
const attResp = await startRegistration({ optionsJSON })
|
|
135
|
+
// 3) Verifica/persiste no servidor.
|
|
136
|
+
const verifyRes = await fetch('/account/mfa/passkeys/verify', {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: { 'content-type': 'application/json', 'x-csrf-token': csrf },
|
|
139
|
+
body: JSON.stringify({ response: attResp }),
|
|
140
|
+
})
|
|
141
|
+
if (!verifyRes.ok) throw new Error('verify')
|
|
142
|
+
// 4) Recarrega para mostrar a nova passkey.
|
|
143
|
+
window.location.reload()
|
|
144
|
+
} catch (e) {
|
|
145
|
+
errEl.classList.remove('hidden')
|
|
146
|
+
btn.disabled = false
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
</script>
|
|
150
|
+
@end
|
|
151
|
+
</body></html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('account.tokens.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen bg-gray-100 p-4">
|
|
5
|
+
<div class="mx-auto max-w-2xl">
|
|
6
|
+
<div class="flex items-center justify-between py-6">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('account.tokens.title') }}</h1>
|
|
10
|
+
</div>
|
|
11
|
+
<form method="POST" action="/account/logout">
|
|
12
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
13
|
+
<button type="submit" class="text-sm text-gray-500 hover:underline">{{ t('account.tokens.logout') }}</button>
|
|
14
|
+
</form>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
@if(createdToken)
|
|
18
|
+
<div class="mb-6 rounded-lg border border-emerald-300 bg-emerald-50 p-4">
|
|
19
|
+
<p class="text-sm font-medium text-emerald-900">
|
|
20
|
+
{{ t('account.tokens.created_notice') }}
|
|
21
|
+
</p>
|
|
22
|
+
<code class="mt-2 block break-all rounded bg-white px-3 py-2 text-sm text-emerald-800 ring-1 ring-emerald-200">
|
|
23
|
+
{{ createdToken }}
|
|
24
|
+
</code>
|
|
25
|
+
</div>
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
<form method="POST" action="/account/tokens"
|
|
29
|
+
class="mb-6 flex gap-2 rounded-xl bg-white p-4 shadow-sm ring-1 ring-black/5">
|
|
30
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
31
|
+
<input name="name" placeholder="{{ t('account.tokens.name_placeholder') }}"
|
|
32
|
+
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
33
|
+
<button type="submit" class="rounded-lg bg-gray-900 px-4 text-sm font-semibold text-white">
|
|
34
|
+
{{ t('account.tokens.create') }}
|
|
35
|
+
</button>
|
|
36
|
+
</form>
|
|
37
|
+
|
|
38
|
+
<div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-black/5">
|
|
39
|
+
@if(tokens.length === 0)
|
|
40
|
+
<p class="p-6 text-sm text-gray-500">{{ t('account.tokens.empty') }}</p>
|
|
41
|
+
@else
|
|
42
|
+
@each(token in tokens)
|
|
43
|
+
<div class="flex items-center justify-between border-b border-gray-100 p-4 last:border-0">
|
|
44
|
+
<div>
|
|
45
|
+
<p class="text-sm font-medium text-gray-900">{{ token.name }}</p>
|
|
46
|
+
<p class="text-xs text-gray-500">
|
|
47
|
+
{{ t('account.tokens.created_at', { date: token.createdAt }) }}
|
|
48
|
+
@if(token.lastUsedAt)
|
|
49
|
+
{{ t('account.tokens.last_used', { date: token.lastUsedAt }) }}
|
|
50
|
+
@else
|
|
51
|
+
{{ t('account.tokens.never_used') }}
|
|
52
|
+
@end
|
|
53
|
+
</p>
|
|
54
|
+
@if(token.scopes && token.scopes.length > 0)
|
|
55
|
+
<p class="text-xs text-gray-400">{{ t('account.tokens.scopes', { scopes: token.scopes.join(', ') }) }}</p>
|
|
56
|
+
@end
|
|
57
|
+
@if(token.audience)
|
|
58
|
+
<p class="text-xs text-gray-400">{{ t('account.tokens.audience', { audience: token.audience }) }}</p>
|
|
59
|
+
@end
|
|
60
|
+
</div>
|
|
61
|
+
<form method="POST" action="/account/tokens/{{ token.id }}/revoke">
|
|
62
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
63
|
+
<button type="submit" class="text-sm text-red-600 hover:underline">{{ t('account.tokens.revoke') }}</button>
|
|
64
|
+
</form>
|
|
65
|
+
</div>
|
|
66
|
+
@end
|
|
67
|
+
@end
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</body></html>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('admin.audit.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen bg-gray-100 p-4">
|
|
5
|
+
<div class="mx-auto max-w-4xl">
|
|
6
|
+
<div class="flex items-center justify-between py-6">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('admin.audit.title') }}</h1>
|
|
10
|
+
</div>
|
|
11
|
+
<form method="POST" action="/account/logout">
|
|
12
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
13
|
+
<button type="submit" class="text-sm text-gray-500 hover:underline">{{ t('admin.nav.logout') }}</button>
|
|
14
|
+
</form>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<nav class="mb-6 flex gap-4 text-sm font-medium">
|
|
18
|
+
<a href="/admin" class="text-gray-500 hover:underline">{{ t('admin.nav.dashboard') }}</a>
|
|
19
|
+
<a href="/admin/users" class="text-gray-500 hover:underline">{{ t('admin.nav.users') }}</a>
|
|
20
|
+
<a href="/admin/clients" class="text-gray-500 hover:underline">{{ t('admin.nav.clients') }}</a>
|
|
21
|
+
<a href="/admin/audit" class="text-gray-900 underline">{{ t('admin.nav.audit') }}</a>
|
|
22
|
+
</nav>
|
|
23
|
+
|
|
24
|
+
@if(!supported)
|
|
25
|
+
<div class="rounded-xl bg-white p-6 text-sm text-gray-500 shadow-sm ring-1 ring-black/5">
|
|
26
|
+
{{ t('admin.audit.not_supported') }}
|
|
27
|
+
</div>
|
|
28
|
+
@else
|
|
29
|
+
<form method="GET" action="/admin/audit" class="mb-6 flex gap-2">
|
|
30
|
+
<input name="type" value="{{ type }}" placeholder="{{ t('admin.audit.type_placeholder') }}"
|
|
31
|
+
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
32
|
+
<input name="subject" value="{{ subject }}" placeholder="{{ t('admin.audit.subject_placeholder') }}"
|
|
33
|
+
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
34
|
+
<button type="submit" class="rounded-lg bg-gray-900 px-4 text-sm font-semibold text-white">
|
|
35
|
+
{{ t('admin.audit.filter') }}
|
|
36
|
+
</button>
|
|
37
|
+
</form>
|
|
38
|
+
|
|
39
|
+
<div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-black/5">
|
|
40
|
+
@if(events.length === 0)
|
|
41
|
+
<p class="p-6 text-sm text-gray-500">{{ t('admin.audit.empty') }}</p>
|
|
42
|
+
@else
|
|
43
|
+
@each(event in events)
|
|
44
|
+
<div class="border-b border-gray-100 p-4 last:border-0">
|
|
45
|
+
<div class="flex items-center justify-between">
|
|
46
|
+
<p class="text-sm font-medium text-gray-900">{{ event.type }}</p>
|
|
47
|
+
<p class="text-xs text-gray-400">{{ event.createdAt }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
<p class="mt-1 text-xs text-gray-500">
|
|
50
|
+
{{ [event.email, event.accountId, event.clientId, event.ip].filter((v) => v).join(' · ') }}
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
@end
|
|
54
|
+
@end
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
@if(totalPages > 1)
|
|
58
|
+
<div class="mt-4 flex items-center justify-between text-sm text-gray-500">
|
|
59
|
+
<span>{{ t('admin.pagination.page', { page: page, total: totalPages }) }}</span>
|
|
60
|
+
<div class="flex gap-2">
|
|
61
|
+
@if(page > 1)
|
|
62
|
+
<a href="/admin/audit?type={{ type }}&subject={{ subject }}&page={{ page - 1 }}" class="rounded border border-gray-300 px-3 py-1 hover:bg-white">{{ t('admin.pagination.prev') }}</a>
|
|
63
|
+
@end
|
|
64
|
+
@if(page < totalPages)
|
|
65
|
+
<a href="/admin/audit?type={{ type }}&subject={{ subject }}&page={{ page + 1 }}" class="rounded border border-gray-300 px-3 py-1 hover:bg-white">{{ t('admin.pagination.next') }}</a>
|
|
66
|
+
@end
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
@end
|
|
70
|
+
@end
|
|
71
|
+
</div>
|
|
72
|
+
</body></html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('admin.clients.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen bg-gray-100 p-4">
|
|
5
|
+
<div class="mx-auto max-w-4xl">
|
|
6
|
+
<div class="flex items-center justify-between py-6">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('admin.clients.title') }}</h1>
|
|
10
|
+
</div>
|
|
11
|
+
<form method="POST" action="/account/logout">
|
|
12
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
13
|
+
<button type="submit" class="text-sm text-gray-500 hover:underline">{{ t('admin.nav.logout') }}</button>
|
|
14
|
+
</form>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<nav class="mb-6 flex gap-4 text-sm font-medium">
|
|
18
|
+
<a href="/admin" class="text-gray-500 hover:underline">{{ t('admin.nav.dashboard') }}</a>
|
|
19
|
+
<a href="/admin/users" class="text-gray-500 hover:underline">{{ t('admin.nav.users') }}</a>
|
|
20
|
+
<a href="/admin/clients" class="text-gray-900 underline">{{ t('admin.nav.clients') }}</a>
|
|
21
|
+
<a href="/admin/audit" class="text-gray-500 hover:underline">{{ t('admin.nav.audit') }}</a>
|
|
22
|
+
</nav>
|
|
23
|
+
|
|
24
|
+
@if(dynamicEnabled)
|
|
25
|
+
<div class="mb-6 rounded-lg border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900">
|
|
26
|
+
{{ t('admin.clients.dynamic_notice') }}
|
|
27
|
+
</div>
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
<div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-black/5">
|
|
31
|
+
@if(clients.length === 0)
|
|
32
|
+
<p class="p-6 text-sm text-gray-500">{{ t('admin.clients.empty') }}</p>
|
|
33
|
+
@else
|
|
34
|
+
@each(client in clients)
|
|
35
|
+
<div class="border-b border-gray-100 p-4 last:border-0">
|
|
36
|
+
<div class="flex items-center justify-between">
|
|
37
|
+
<p class="text-sm font-medium text-gray-900">{{ client.clientId }}</p>
|
|
38
|
+
<span class="rounded-full px-2 py-0.5 text-xs {{ client.confidential ? 'bg-gray-900 text-white' : 'bg-gray-100 text-gray-600' }}">
|
|
39
|
+
{{ client.confidential ? t('admin.clients.confidential') : t('admin.clients.public') }}
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
<p class="mt-1 text-xs text-gray-500">{{ t('admin.clients.grants', { grants: client.grants.join(', ') }) }}</p>
|
|
43
|
+
@if(client.redirectUris.length > 0)
|
|
44
|
+
<p class="text-xs text-gray-400">{{ t('admin.clients.redirect_uris', { uris: client.redirectUris.join(', ') }) }}</p>
|
|
45
|
+
@end
|
|
46
|
+
</div>
|
|
47
|
+
@end
|
|
48
|
+
@end
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</body></html>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('admin.dashboard.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen bg-gray-100 p-4">
|
|
5
|
+
<div class="mx-auto max-w-4xl">
|
|
6
|
+
<div class="flex items-center justify-between py-6">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('admin.dashboard.title') }}</h1>
|
|
10
|
+
</div>
|
|
11
|
+
<form method="POST" action="/account/logout">
|
|
12
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
13
|
+
<button type="submit" class="text-sm text-gray-500 hover:underline">{{ t('admin.nav.logout') }}</button>
|
|
14
|
+
</form>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<nav class="mb-6 flex gap-4 text-sm font-medium">
|
|
18
|
+
<a href="/admin" class="text-gray-900 underline">{{ t('admin.nav.dashboard') }}</a>
|
|
19
|
+
<a href="/admin/users" class="text-gray-500 hover:underline">{{ t('admin.nav.users') }}</a>
|
|
20
|
+
<a href="/admin/clients" class="text-gray-500 hover:underline">{{ t('admin.nav.clients') }}</a>
|
|
21
|
+
<a href="/admin/audit" class="text-gray-500 hover:underline">{{ t('admin.nav.audit') }}</a>
|
|
22
|
+
</nav>
|
|
23
|
+
|
|
24
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
|
25
|
+
<a href="/admin/users" class="rounded-xl bg-white p-5 shadow-sm ring-1 ring-black/5 hover:ring-gray-900/20">
|
|
26
|
+
<p class="text-xs uppercase tracking-wide text-gray-400">{{ t('admin.dashboard.users_count') }}</p>
|
|
27
|
+
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ usersTotal }}</p>
|
|
28
|
+
</a>
|
|
29
|
+
<a href="/admin/clients" class="rounded-xl bg-white p-5 shadow-sm ring-1 ring-black/5 hover:ring-gray-900/20">
|
|
30
|
+
<p class="text-xs uppercase tracking-wide text-gray-400">{{ t('admin.dashboard.clients_count') }}</p>
|
|
31
|
+
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ clientsCount }}</p>
|
|
32
|
+
</a>
|
|
33
|
+
<a href="/admin/audit" class="rounded-xl bg-white p-5 shadow-sm ring-1 ring-black/5 hover:ring-gray-900/20">
|
|
34
|
+
<p class="text-xs uppercase tracking-wide text-gray-400">{{ t('admin.dashboard.audit_count') }}</p>
|
|
35
|
+
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ auditSupported ? auditTotal : '—' }}</p>
|
|
36
|
+
</a>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<h2 class="mt-8 mb-3 text-sm font-semibold uppercase tracking-wide text-gray-500">{{ t('admin.dashboard.recent_title') }}</h2>
|
|
40
|
+
<div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-black/5">
|
|
41
|
+
@if(!auditSupported)
|
|
42
|
+
<p class="p-6 text-sm text-gray-500">{{ t('admin.audit.not_supported') }}</p>
|
|
43
|
+
@elseif(recentEvents.length === 0)
|
|
44
|
+
<p class="p-6 text-sm text-gray-500">{{ t('admin.audit.empty') }}</p>
|
|
45
|
+
@else
|
|
46
|
+
@each(event in recentEvents)
|
|
47
|
+
<div class="flex items-center justify-between border-b border-gray-100 p-4 last:border-0">
|
|
48
|
+
<div>
|
|
49
|
+
<p class="text-sm font-medium text-gray-900">{{ event.type }}</p>
|
|
50
|
+
<p class="text-xs text-gray-500">{{ event.email ?? event.accountId ?? '—' }}</p>
|
|
51
|
+
</div>
|
|
52
|
+
<p class="text-xs text-gray-400">{{ event.createdAt }}</p>
|
|
53
|
+
</div>
|
|
54
|
+
@end
|
|
55
|
+
@end
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</body></html>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('admin.users.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen bg-gray-100 p-4">
|
|
5
|
+
<div class="mx-auto max-w-4xl">
|
|
6
|
+
<div class="flex items-center justify-between py-6">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('admin.users.title') }}</h1>
|
|
10
|
+
</div>
|
|
11
|
+
<form method="POST" action="/account/logout">
|
|
12
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
13
|
+
<button type="submit" class="text-sm text-gray-500 hover:underline">{{ t('admin.nav.logout') }}</button>
|
|
14
|
+
</form>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<nav class="mb-6 flex gap-4 text-sm font-medium">
|
|
18
|
+
<a href="/admin" class="text-gray-500 hover:underline">{{ t('admin.nav.dashboard') }}</a>
|
|
19
|
+
<a href="/admin/users" class="text-gray-900 underline">{{ t('admin.nav.users') }}</a>
|
|
20
|
+
<a href="/admin/clients" class="text-gray-500 hover:underline">{{ t('admin.nav.clients') }}</a>
|
|
21
|
+
<a href="/admin/audit" class="text-gray-500 hover:underline">{{ t('admin.nav.audit') }}</a>
|
|
22
|
+
</nav>
|
|
23
|
+
|
|
24
|
+
<form method="GET" action="/admin/users" class="mb-6 flex gap-2">
|
|
25
|
+
<input name="search" value="{{ search }}" placeholder="{{ t('admin.users.search_placeholder') }}"
|
|
26
|
+
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
27
|
+
<button type="submit" class="rounded-lg bg-gray-900 px-4 text-sm font-semibold text-white">
|
|
28
|
+
{{ t('admin.users.search') }}
|
|
29
|
+
</button>
|
|
30
|
+
</form>
|
|
31
|
+
|
|
32
|
+
<div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-black/5">
|
|
33
|
+
@if(users.length === 0)
|
|
34
|
+
<p class="p-6 text-sm text-gray-500">{{ t('admin.users.empty') }}</p>
|
|
35
|
+
@else
|
|
36
|
+
@each(user in users)
|
|
37
|
+
<div class="border-b border-gray-100 p-4 last:border-0">
|
|
38
|
+
<div class="flex items-center justify-between">
|
|
39
|
+
<div>
|
|
40
|
+
<p class="text-sm font-medium text-gray-900">{{ user.email }}</p>
|
|
41
|
+
@if(user.name)
|
|
42
|
+
<p class="text-xs text-gray-500">{{ user.name }}</p>
|
|
43
|
+
@end
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<form method="POST" action="/admin/users/{{ user.id }}/roles" class="mt-3 flex gap-2">
|
|
47
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
48
|
+
<input type="hidden" name="search" value="{{ search }}">
|
|
49
|
+
<input type="hidden" name="page" value="{{ page }}">
|
|
50
|
+
<input name="roles" value="{{ user.rolesText }}"
|
|
51
|
+
placeholder="{{ t('admin.users.roles_placeholder') }}"
|
|
52
|
+
class="flex-1 rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
53
|
+
<button type="submit" class="rounded-lg bg-gray-900 px-4 text-sm font-semibold text-white">
|
|
54
|
+
{{ t('admin.users.save_roles') }}
|
|
55
|
+
</button>
|
|
56
|
+
</form>
|
|
57
|
+
</div>
|
|
58
|
+
@end
|
|
59
|
+
@end
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
@if(totalPages > 1)
|
|
63
|
+
<div class="mt-4 flex items-center justify-between text-sm text-gray-500">
|
|
64
|
+
<span>{{ t('admin.pagination.page', { page: page, total: totalPages }) }}</span>
|
|
65
|
+
<div class="flex gap-2">
|
|
66
|
+
@if(page > 1)
|
|
67
|
+
<a href="/admin/users?search={{ search }}&page={{ page - 1 }}" class="rounded border border-gray-300 px-3 py-1 hover:bg-white">{{ t('admin.pagination.prev') }}</a>
|
|
68
|
+
@end
|
|
69
|
+
@if(page < totalPages)
|
|
70
|
+
<a href="/admin/users?search={{ search }}&page={{ page + 1 }}" class="rounded border border-gray-300 px-3 py-1 hover:bg-white">{{ t('admin.pagination.next') }}</a>
|
|
71
|
+
@end
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
@end
|
|
75
|
+
</div>
|
|
76
|
+
</body></html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('consent.page_title') }} — {{ brand && brand.appName ? brand.appName : t('common.app_fallback') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen flex items-center justify-center bg-gray-50">
|
|
5
|
+
<div class="w-full max-w-sm bg-white p-8 rounded-2xl shadow-xl ring-1 ring-black/5">
|
|
6
|
+
<form method="POST" action="/auth/interaction/{{ uid }}/consent">
|
|
7
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
8
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('consent.title') }}</h1>
|
|
9
|
+
<p class="mt-2 text-sm text-gray-600">
|
|
10
|
+
{{{ t('consent.body', { app: brand && brand.appName ? brand.appName : params.client_id }) }}}
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<button type="submit"
|
|
14
|
+
class="mt-6 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
15
|
+
{{ t('consent.submit') }}
|
|
16
|
+
</button>
|
|
17
|
+
</form>
|
|
18
|
+
</div>
|
|
19
|
+
</body></html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('forgot.page_title') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen flex items-center justify-center bg-gray-50">
|
|
5
|
+
<div class="w-full max-w-sm bg-white p-8 rounded-2xl shadow-xl ring-1 ring-black/5">
|
|
6
|
+
@if(sent)
|
|
7
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('forgot.sent_title') }}</h1>
|
|
8
|
+
<p class="mt-2 text-sm text-gray-600">
|
|
9
|
+
{{ t('forgot.sent_body') }}
|
|
10
|
+
</p>
|
|
11
|
+
@else
|
|
12
|
+
<form method="POST" action="/auth/forgot-password">
|
|
13
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
14
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('forgot.title') }}</h1>
|
|
15
|
+
<p class="mt-1 text-sm text-gray-500">{{ t('forgot.intro') }}</p>
|
|
16
|
+
|
|
17
|
+
<div class="mt-6">
|
|
18
|
+
<label for="email" class="mb-1 block text-sm font-medium text-gray-700">{{ t('forgot.email_label') }}</label>
|
|
19
|
+
<input id="email" name="email" type="email" required autofocus
|
|
20
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none transition focus:border-gray-900 focus:ring-2 focus:ring-gray-900" />
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<button type="submit"
|
|
24
|
+
class="mt-6 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
25
|
+
{{ t('forgot.submit') }}
|
|
26
|
+
</button>
|
|
27
|
+
</form>
|
|
28
|
+
@end
|
|
29
|
+
</div>
|
|
30
|
+
</body></html>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('login.page_title') }} — {{ brand && brand.appName ? brand.appName : t('common.app_fallback') }}</title>
|
|
3
|
+
<script src="https://cdn.tailwindcss.com"></script></head>
|
|
4
|
+
<body class="min-h-screen flex items-center justify-center bg-gray-50">
|
|
5
|
+
@if(step === 'identifier')
|
|
6
|
+
<div class="w-full max-w-sm bg-white p-8 rounded-2xl shadow-xl ring-1 ring-black/5">
|
|
7
|
+
<form method="POST" action="/auth/interaction/{{ uid }}/identifier">
|
|
8
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
9
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('login.title') }}</h1>
|
|
10
|
+
<p class="mt-1 text-sm text-gray-500">{{ t('login.identifier_intro') }}</p>
|
|
11
|
+
|
|
12
|
+
@if(error)
|
|
13
|
+
<p class="mt-4 text-sm text-red-600">{{ error }}</p>
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
<div class="mt-6">
|
|
17
|
+
<label for="email" class="mb-1 block text-sm font-medium text-gray-700">{{ t('login.email_label') }}</label>
|
|
18
|
+
<input id="email" name="email" type="email" required autofocus
|
|
19
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none transition focus:border-gray-900 focus:ring-2 focus:ring-gray-900" />
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<button type="submit"
|
|
23
|
+
class="mt-6 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
24
|
+
{{ t('login.identifier_submit') }}
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<div class="mt-4 flex justify-between text-sm text-gray-600">
|
|
28
|
+
<a href="/auth/interaction/{{ uid }}/signup" class="hover:underline">{{ t('login.create_account') }}</a>
|
|
29
|
+
<a href="/auth/forgot-password" class="hover:underline">{{ t('login.forgot_password') }}</a>
|
|
30
|
+
</div>
|
|
31
|
+
</form>
|
|
32
|
+
|
|
33
|
+
<div class="my-6 flex items-center gap-3 text-xs text-gray-400">
|
|
34
|
+
<span class="h-px flex-1 bg-gray-200"></span>
|
|
35
|
+
{{ t('login.divider_or') }}
|
|
36
|
+
<span class="h-px flex-1 bg-gray-200"></span>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<a href="/auth/google/redirect/{{ uid }}"
|
|
40
|
+
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">
|
|
41
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" aria-hidden="true">
|
|
42
|
+
<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"/>
|
|
43
|
+
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23Z"/>
|
|
44
|
+
<path fill="#FBBC05" d="M5.84 14.1a6.6 6.6 0 0 1 0-4.2V7.06H2.18a11 11 0 0 0 0 9.88l3.66-2.84Z"/>
|
|
45
|
+
<path fill="#EA4335" d="M12 4.75c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 1.46 14.97.5 12 .5A11 11 0 0 0 2.18 7.06l3.66 2.84C6.71 7.3 9.14 4.75 12 4.75Z"/>
|
|
46
|
+
</svg>
|
|
47
|
+
{{ t('login.google') }}
|
|
48
|
+
</a>
|
|
49
|
+
</div>
|
|
50
|
+
@else
|
|
51
|
+
<div class="w-full max-w-sm bg-white p-8 rounded-2xl shadow-xl ring-1 ring-black/5">
|
|
52
|
+
<form method="POST" action="/auth/interaction/{{ uid }}/login">
|
|
53
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
54
|
+
|
|
55
|
+
@if(account && account.fullName)
|
|
56
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('login.greeting', { name: account.fullName }) }}</h1>
|
|
57
|
+
@else
|
|
58
|
+
<h1 class="text-xl font-semibold text-gray-900">{{ t('login.title') }}</h1>
|
|
59
|
+
@end
|
|
60
|
+
|
|
61
|
+
<div class="mt-1 flex items-center gap-2">
|
|
62
|
+
@if(email)
|
|
63
|
+
<span class="text-sm text-gray-500">{{ email }}</span>
|
|
64
|
+
@end
|
|
65
|
+
<a href="/auth/interaction/{{ uid }}/switch" class="text-xs font-medium text-gray-900 hover:underline">
|
|
66
|
+
{{ t('login.switch_account') }}
|
|
67
|
+
</a>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
@if(error)
|
|
71
|
+
<p class="mt-4 text-sm text-red-600">{{ error }}</p>
|
|
72
|
+
@end
|
|
73
|
+
|
|
74
|
+
<div class="mt-6">
|
|
75
|
+
<label for="password" class="mb-1 block text-sm font-medium text-gray-700">{{ t('login.password_label') }}</label>
|
|
76
|
+
<input id="password" name="password" type="password" required autofocus
|
|
77
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none transition focus:border-gray-900 focus:ring-2 focus:ring-gray-900" />
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<button type="submit"
|
|
81
|
+
class="mt-6 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
82
|
+
{{ t('login.submit') }}
|
|
83
|
+
</button>
|
|
84
|
+
|
|
85
|
+
<div class="mt-4 flex justify-end text-sm text-gray-600">
|
|
86
|
+
<a href="/auth/forgot-password" class="hover:underline">{{ t('login.forgot_password') }}</a>
|
|
87
|
+
</div>
|
|
88
|
+
</form>
|
|
89
|
+
</div>
|
|
90
|
+
@end
|
|
91
|
+
</body></html>
|