@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi de Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @dudousxd/adonis-authkit-server
|
|
2
|
+
|
|
3
|
+
Authorization Server OpenID Connect para AdonisJS — um wrapper idiomático em volta do
|
|
4
|
+
[`oidc-provider`](https://github.com/panva/node-oidc-provider).
|
|
5
|
+
|
|
6
|
+
## Instalação
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
node ace add @dudousxd/adonis-authkit-server
|
|
10
|
+
# ou: pnpm add @dudousxd/adonis-authkit-server && node ace configure @dudousxd/adonis-authkit-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
O `configure` publica `config/authkit_server.ts`, o model `app/models/auth_user.ts`,
|
|
14
|
+
o controller de interactions (`app/controllers/auth_interaction_controller.ts`) e
|
|
15
|
+
registra o provider.
|
|
16
|
+
|
|
17
|
+
## Montar as rotas OIDC
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// start/routes.ts
|
|
21
|
+
import router from '@adonisjs/core/services/router'
|
|
22
|
+
import { registerOidcRoutes } from '@dudousxd/adonis-authkit-server'
|
|
23
|
+
|
|
24
|
+
registerOidcRoutes(router) // monta em /oidc por padrão
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Defina `AUTHKIT_ISSUER` apontando para `<host>/oidc` (o issuer deve terminar no mount path).
|
|
28
|
+
|
|
29
|
+
## Rotas de interaction (login/consentimento)
|
|
30
|
+
|
|
31
|
+
O `oidc-provider` redireciona o usuário não autenticado para `interactions.url`
|
|
32
|
+
(`/auth/interaction/:uid`). Essas telas são **suas** (o `configure` ejeta
|
|
33
|
+
`app/controllers/auth_interaction_controller.ts`). Registre as rotas que apontam para ele:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// start/routes.ts
|
|
37
|
+
import AuthInteractionController from '#controllers/auth_interaction_controller'
|
|
38
|
+
|
|
39
|
+
router.get('/auth/interaction/:uid', [AuthInteractionController, 'show'])
|
|
40
|
+
router.post('/auth/interaction/:uid/login', [AuthInteractionController, 'login'])
|
|
41
|
+
router.post('/auth/interaction/:uid/consent', [AuthInteractionController, 'consent'])
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Sem essas rotas o fluxo de autorização cai num 404 ao chegar na tela de login.
|
|
45
|
+
|
|
46
|
+
## UI de login/consent (configurável)
|
|
47
|
+
|
|
48
|
+
O `configure` ejeta as telas de interaction a partir de um preset escolhido:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node ace configure @dudousxd/adonis-authkit-server --ui=edge
|
|
52
|
+
# valores: edge | react | headless — se omitir, o configure pergunta
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Cada preset publica:
|
|
56
|
+
|
|
57
|
+
- **`headless`** — apenas o controller (`app/controllers/auth_interaction_controller.ts`).
|
|
58
|
+
`show` devolve JSON (`{ uid, prompt, params }`) e `login`/`consent` respondem JSON/erro.
|
|
59
|
+
Você constrói o front como quiser.
|
|
60
|
+
- **`edge`** — controller + views Edge (`resources/views/authkit/login.edge` e
|
|
61
|
+
`consent.edge`). `show` renderiza a view de acordo com o prompt.
|
|
62
|
+
- **`react`** — controller + páginas Inertia/React (`inertia/pages/authkit/login.tsx` e
|
|
63
|
+
`consent.tsx`). `show` faz `inertia.render`. Exige `@adonisjs/inertia` + Vite + React no
|
|
64
|
+
app — o `configure` valida essa stack antes de publicar o preset.
|
|
65
|
+
|
|
66
|
+
Em todos os presets o controller ejetado é **casca**: a lógica vive em
|
|
67
|
+
`service.interactions` (resolvido via `containerResolver.make('authkit.server')`), que expõe
|
|
68
|
+
`details(ctx)`, `login(ctx, { email, password })` e `consent(ctx)`. Você edita só a parte de
|
|
69
|
+
render/redirect.
|
|
70
|
+
|
|
71
|
+
Quem decide se as credenciais valem é o `verifyCredentials` do `config/authkit_server.ts`
|
|
72
|
+
— é o que o `service.interactions.login` chama. O default consulta o `AuthUser` por e-mail e
|
|
73
|
+
usa `verifyPassword`; sobrescreva para plugar sua própria base de usuários.
|
|
74
|
+
|
|
75
|
+
As 3 rotas que o consumidor registra são as mesmas da seção anterior:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import AuthInteractionController from '#controllers/auth_interaction_controller'
|
|
79
|
+
|
|
80
|
+
router.get('/auth/interaction/:uid', [AuthInteractionController, 'show'])
|
|
81
|
+
router.post('/auth/interaction/:uid/login', [AuthInteractionController, 'login'])
|
|
82
|
+
router.post('/auth/interaction/:uid/consent', [AuthInteractionController, 'consent'])
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Persistência
|
|
86
|
+
|
|
87
|
+
Escolha o backend no `config/authkit_server.ts`:
|
|
88
|
+
- `adapters.redis({ connection })` — requer `@adonisjs/redis` configurado.
|
|
89
|
+
- `adapters.database({ connection? })` — Lucid; rode a migração `authkit_oidc_payloads`.
|
|
90
|
+
|
|
91
|
+
## Observabilidade
|
|
92
|
+
|
|
93
|
+
A lib agrega métricas de auth (logins, tokens, refresh, grants revogados, duração/erros
|
|
94
|
+
de resolve) e as expõe de forma opt-in.
|
|
95
|
+
|
|
96
|
+
### Configuração
|
|
97
|
+
|
|
98
|
+
No `config/authkit_server.ts`, use a chave `observability`:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
observability: {
|
|
102
|
+
metrics: true, // habilita a coleta/agregação de métricas
|
|
103
|
+
jsonRoutes: true, // libera a rota JSON de snapshot
|
|
104
|
+
dashboard: true, // libera o dashboard HTML embutido
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Rotas
|
|
109
|
+
|
|
110
|
+
Passe as flags em `registerOidcRoutes` no `start/routes.ts`:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { registerOidcRoutes } from '@dudousxd/adonis-authkit-server'
|
|
114
|
+
|
|
115
|
+
registerOidcRoutes(router, { metrics: true, dashboard: true })
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- `GET /authkit/metrics` — snapshot agregado em JSON (`{ counters, histograms, updatedAt }`).
|
|
119
|
+
- `GET /authkit/dashboard` — dashboard HTML embutido (auto-refresh a cada 5s, sem
|
|
120
|
+
dependência do Edge do consumidor).
|
|
121
|
+
|
|
122
|
+
### OpenTelemetry
|
|
123
|
+
|
|
124
|
+
As métricas são emitidas via OpenTelemetry **quando** `@opentelemetry/api` e `@adonisjs/otel`
|
|
125
|
+
estão instalados. Sem esses pacotes a emissão é no-op — a agregação em memória (e as rotas
|
|
126
|
+
JSON/HTML) continua funcionando normalmente.
|
|
127
|
+
|
|
128
|
+
### Grafana
|
|
129
|
+
|
|
130
|
+
O arquivo `assets/grafana/authkit-dashboard.json` pode ser importado diretamente no Grafana
|
|
131
|
+
(Dashboards → Import). Ele usa nomes de métrica no padrão OTel→Prometheus (pontos viram
|
|
132
|
+
underscores e counters ganham `_total`), portanto requer um exporter Prometheus no pipeline
|
|
133
|
+
OTel para que as séries existam.
|
|
134
|
+
|
|
135
|
+
## Notas
|
|
136
|
+
- Access tokens são opacos; ID tokens são JWT (assinados pelo JWKS gerido).
|
|
137
|
+
- PKCE (S256) é obrigatório; refresh tokens são rotacionados.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "AuthKit",
|
|
3
|
+
"uid": "authkit",
|
|
4
|
+
"schemaVersion": 39,
|
|
5
|
+
"version": 1,
|
|
6
|
+
"editable": true,
|
|
7
|
+
"graphTooltip": 0,
|
|
8
|
+
"time": { "from": "now-6h", "to": "now" },
|
|
9
|
+
"refresh": "30s",
|
|
10
|
+
"tags": ["authkit", "oidc", "auth"],
|
|
11
|
+
"templating": {
|
|
12
|
+
"list": [
|
|
13
|
+
{
|
|
14
|
+
"name": "datasource",
|
|
15
|
+
"label": "Datasource",
|
|
16
|
+
"type": "datasource",
|
|
17
|
+
"query": "prometheus",
|
|
18
|
+
"current": {},
|
|
19
|
+
"hide": 0,
|
|
20
|
+
"refresh": 1
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"panels": [
|
|
25
|
+
{
|
|
26
|
+
"id": 1,
|
|
27
|
+
"title": "Login (sucesso vs falha)",
|
|
28
|
+
"type": "timeseries",
|
|
29
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
30
|
+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
|
|
31
|
+
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
|
32
|
+
"targets": [
|
|
33
|
+
{
|
|
34
|
+
"refId": "A",
|
|
35
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
36
|
+
"expr": "rate(authkit_login_success_total[5m])",
|
|
37
|
+
"legendFormat": "success"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"refId": "B",
|
|
41
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
42
|
+
"expr": "rate(authkit_login_failure_total[5m])",
|
|
43
|
+
"legendFormat": "failure"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": 2,
|
|
49
|
+
"title": "Tokens emitidos vs refresh rotacionados",
|
|
50
|
+
"type": "timeseries",
|
|
51
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
52
|
+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
|
|
53
|
+
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
|
54
|
+
"targets": [
|
|
55
|
+
{
|
|
56
|
+
"refId": "A",
|
|
57
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
58
|
+
"expr": "rate(authkit_token_issued_total[5m])",
|
|
59
|
+
"legendFormat": "issued"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"refId": "B",
|
|
63
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
64
|
+
"expr": "rate(authkit_refresh_rotated_total[5m])",
|
|
65
|
+
"legendFormat": "refresh rotated"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": 3,
|
|
71
|
+
"title": "Grants revogados (total)",
|
|
72
|
+
"type": "stat",
|
|
73
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
74
|
+
"gridPos": { "h": 8, "w": 8, "x": 0, "y": 8 },
|
|
75
|
+
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
76
|
+
"targets": [
|
|
77
|
+
{
|
|
78
|
+
"refId": "A",
|
|
79
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
80
|
+
"expr": "sum(authkit_grant_revoked_total)",
|
|
81
|
+
"legendFormat": "revoked"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": 4,
|
|
87
|
+
"title": "Resolve duration (p95)",
|
|
88
|
+
"type": "timeseries",
|
|
89
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
90
|
+
"gridPos": { "h": 8, "w": 8, "x": 8, "y": 8 },
|
|
91
|
+
"fieldConfig": { "defaults": { "unit": "s" }, "overrides": [] },
|
|
92
|
+
"targets": [
|
|
93
|
+
{
|
|
94
|
+
"refId": "A",
|
|
95
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
96
|
+
"expr": "histogram_quantile(0.95, rate(authkit_resolve_duration_bucket[5m]))",
|
|
97
|
+
"legendFormat": "p95"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": 5,
|
|
103
|
+
"title": "Erros de resolve (total)",
|
|
104
|
+
"type": "stat",
|
|
105
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
106
|
+
"gridPos": { "h": 8, "w": 8, "x": 16, "y": 8 },
|
|
107
|
+
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
108
|
+
"targets": [
|
|
109
|
+
{
|
|
110
|
+
"refId": "A",
|
|
111
|
+
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
|
112
|
+
"expr": "sum(authkit_resolve_errors_total)",
|
|
113
|
+
"legendFormat": "errors"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"commands": [
|
|
4
|
+
{
|
|
5
|
+
"commandName": "authkit:eject",
|
|
6
|
+
"description": "Ejeta views Edge ou um controller do host-kit do @dudousxd/adonis-authkit-server para customização local",
|
|
7
|
+
"namespace": "authkit",
|
|
8
|
+
"aliases": [],
|
|
9
|
+
"flags": [
|
|
10
|
+
{
|
|
11
|
+
"name": "views",
|
|
12
|
+
"flagName": "views",
|
|
13
|
+
"required": false,
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"description": "Copia as views Edge da lib para resources/views/authkit/"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "controller",
|
|
19
|
+
"flagName": "controller",
|
|
20
|
+
"required": false,
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Copia um controller do host-kit (ex.: interaction) para app/controllers/authkit/"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"args": [],
|
|
26
|
+
"options": {},
|
|
27
|
+
"filePath": "eject.js"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { stubsRoot } from '../stubs/main.js';
|
|
4
|
+
import { resolveUiPreset, uiStubPaths } from './ui_preset.js';
|
|
5
|
+
function assertPresetPrereqs(preset, appRoot) {
|
|
6
|
+
if (preset !== 'react')
|
|
7
|
+
return;
|
|
8
|
+
const pkgPath = join(appRoot, 'package.json');
|
|
9
|
+
const pkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, 'utf8')) : {};
|
|
10
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
11
|
+
const hasInertia = !!deps['@adonisjs/inertia'];
|
|
12
|
+
const hasReact = !!deps['react'];
|
|
13
|
+
const hasVite = existsSync(join(appRoot, 'vite.config.ts')) || existsSync(join(appRoot, 'vite.config.js'));
|
|
14
|
+
if (!hasInertia || !hasReact || !hasVite) {
|
|
15
|
+
throw new Error('authkit --ui=react requer @adonisjs/inertia + react + Vite no app. ' +
|
|
16
|
+
'Rode `node ace add @adonisjs/inertia` (com React) antes, ou use --ui=edge|headless.');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function configure(command) {
|
|
20
|
+
const flag = command.parsedFlags?.ui;
|
|
21
|
+
const chosen = flag ?? (await command.prompt.choice('UI das telas de login/consent', ['edge', 'react', 'headless']));
|
|
22
|
+
const preset = resolveUiPreset(chosen);
|
|
23
|
+
assertPresetPrereqs(preset, command.app.makePath());
|
|
24
|
+
const codemods = await command.createCodemods();
|
|
25
|
+
await codemods.makeUsingStub(stubsRoot, 'config/authkit.stub', {});
|
|
26
|
+
await codemods.makeUsingStub(stubsRoot, 'models/auth_user.stub', {});
|
|
27
|
+
for (const path of uiStubPaths(preset)) {
|
|
28
|
+
await codemods.makeUsingStub(stubsRoot, path, {});
|
|
29
|
+
}
|
|
30
|
+
await codemods.updateRcFile((rcFile) => {
|
|
31
|
+
rcFile.addProvider('@dudousxd/adonis-authkit-server/authkit_server_provider');
|
|
32
|
+
});
|
|
33
|
+
await codemods.defineEnvValidations({
|
|
34
|
+
leadingComment: 'Variáveis do @dudousxd/adonis-authkit-server (Authorization Server OIDC)',
|
|
35
|
+
variables: {
|
|
36
|
+
AUTHKIT_ISSUER: `Env.schema.string({ format: 'url', tld: false })`,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
await codemods.defineEnvVariables({
|
|
40
|
+
AUTHKIT_ISSUER: 'http://localhost:3333/oidc',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
|
+
import type { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
export default class AuthkitEject extends BaseCommand {
|
|
4
|
+
static commandName: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static help: string[];
|
|
7
|
+
static options: CommandOptions;
|
|
8
|
+
views: boolean;
|
|
9
|
+
controller?: string;
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
8
|
+
const KNOWN_CONTROLLERS = [
|
|
9
|
+
'interaction',
|
|
10
|
+
'registration',
|
|
11
|
+
'social',
|
|
12
|
+
'account_session',
|
|
13
|
+
'account_tokens',
|
|
14
|
+
'pat_introspection',
|
|
15
|
+
];
|
|
16
|
+
export default class AuthkitEject extends BaseCommand {
|
|
17
|
+
static commandName = 'authkit:eject';
|
|
18
|
+
static description = 'Ejeta views Edge ou um controller do host-kit do @dudousxd/adonis-authkit-server para customização local';
|
|
19
|
+
static help = [
|
|
20
|
+
'Use --views para copiar as views Edge da lib para resources/views/authkit/.',
|
|
21
|
+
'Depois de ejetar as views, o app as servirá pelo ViewPath padrão',
|
|
22
|
+
'(sem o disco authkit::) — renomeie as chamadas view() nos controllers.',
|
|
23
|
+
'',
|
|
24
|
+
'Use --controller=<nome> para copiar um controller do host-kit para',
|
|
25
|
+
'app/controllers/authkit/<nome>_controller.ts e customizá-lo livremente.',
|
|
26
|
+
'',
|
|
27
|
+
`Controllers disponíveis: ${KNOWN_CONTROLLERS.join(', ')}`,
|
|
28
|
+
];
|
|
29
|
+
static options = {};
|
|
30
|
+
async run() {
|
|
31
|
+
const fs = await import('node:fs');
|
|
32
|
+
const { fileURLToPath } = await import('node:url');
|
|
33
|
+
/**
|
|
34
|
+
* Resolve o diretório dentro de `host/` — prefere o build compilado,
|
|
35
|
+
* cai de volta para o src quando rodando no contexto de dev da lib.
|
|
36
|
+
*/
|
|
37
|
+
const pickDir = (rel) => {
|
|
38
|
+
const built = new URL(`../build/host/${rel}`, import.meta.url);
|
|
39
|
+
const src = new URL(`../src/host/${rel}`, import.meta.url);
|
|
40
|
+
return fs.existsSync(fileURLToPath(built)) ? built : src;
|
|
41
|
+
};
|
|
42
|
+
if (this.views) {
|
|
43
|
+
const fromPath = fileURLToPath(pickDir('views'));
|
|
44
|
+
if (!fs.existsSync(fromPath)) {
|
|
45
|
+
this.logger.error('Diretório de views não encontrado. Execute `pnpm build` no pacote primeiro.');
|
|
46
|
+
this.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const toPath = this.app.makePath('resources/views/authkit');
|
|
50
|
+
fs.mkdirSync(toPath, { recursive: true });
|
|
51
|
+
fs.cpSync(fromPath, toPath, { recursive: true });
|
|
52
|
+
this.logger.success(`Views Edge ejetadas → ${toPath}`);
|
|
53
|
+
this.logger.info('Lembre-se de ajustar as chamadas view() nos controllers para usar o caminho local ao invés do disco authkit::.');
|
|
54
|
+
}
|
|
55
|
+
if (this.controller) {
|
|
56
|
+
const knownList = KNOWN_CONTROLLERS;
|
|
57
|
+
if (!knownList.includes(this.controller)) {
|
|
58
|
+
this.logger.error(`Controller desconhecido: "${this.controller}". Disponíveis: ${KNOWN_CONTROLLERS.join(', ')}`);
|
|
59
|
+
this.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const file = `${this.controller}_controller`;
|
|
63
|
+
const fromDirPath = fileURLToPath(pickDir('controllers'));
|
|
64
|
+
// Prefere .js (build) com fallback para .ts (src)
|
|
65
|
+
const jsPath = `${fromDirPath}/${file}.js`;
|
|
66
|
+
const tsPath = `${fromDirPath}/${file}.ts`;
|
|
67
|
+
const fromFilePath = fs.existsSync(jsPath) ? jsPath : tsPath;
|
|
68
|
+
const ext = fs.existsSync(jsPath) ? 'js' : 'ts';
|
|
69
|
+
if (!fs.existsSync(fromFilePath)) {
|
|
70
|
+
this.logger.error(`Arquivo do controller não encontrado: ${fromFilePath}. Execute \`pnpm build\` no pacote primeiro.`);
|
|
71
|
+
this.exitCode = 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const toDirPath = this.app.makePath('app/controllers/authkit');
|
|
75
|
+
fs.mkdirSync(toDirPath, { recursive: true });
|
|
76
|
+
const toFilePath = `${toDirPath}/${file}.${ext}`;
|
|
77
|
+
fs.copyFileSync(fromFilePath, toFilePath);
|
|
78
|
+
this.logger.success(`Controller "${this.controller}" ejetado → ${toFilePath}`);
|
|
79
|
+
this.logger.info(`Atualize as rotas do host para apontar para app/controllers/authkit/${file}.`);
|
|
80
|
+
}
|
|
81
|
+
if (!this.views && !this.controller) {
|
|
82
|
+
this.logger.info('Nenhuma opção especificada. Use --views ou --controller=<nome>.');
|
|
83
|
+
this.logger.info(`Controllers disponíveis: ${KNOWN_CONTROLLERS.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
__decorate([
|
|
88
|
+
flags.boolean({
|
|
89
|
+
description: 'Copia as views Edge da lib para resources/views/authkit/',
|
|
90
|
+
})
|
|
91
|
+
], AuthkitEject.prototype, "views", void 0);
|
|
92
|
+
__decorate([
|
|
93
|
+
flags.string({
|
|
94
|
+
description: `Copia um controller do host-kit (ex.: interaction) para app/controllers/authkit/`,
|
|
95
|
+
})
|
|
96
|
+
], AuthkitEject.prototype, "controller", void 0);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lê os metadados dos commands a partir do arquivo commands.json.
|
|
3
|
+
* O Ace usa esta função para listar os comandos disponíveis do pacote.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getMetaData(): Promise<any[] | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* Importa a classe do comando pelo `commandName`.
|
|
8
|
+
* O Ace chama esta função quando precisa executar um comando do pacote.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCommand(metaData: {
|
|
11
|
+
commandName: string;
|
|
12
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
/**
|
|
11
|
+
* Cache in-memory após primeira leitura
|
|
12
|
+
*/
|
|
13
|
+
let commandsMetaData;
|
|
14
|
+
/**
|
|
15
|
+
* Lê os metadados dos commands a partir do arquivo commands.json.
|
|
16
|
+
* O Ace usa esta função para listar os comandos disponíveis do pacote.
|
|
17
|
+
*/
|
|
18
|
+
export async function getMetaData() {
|
|
19
|
+
if (commandsMetaData) {
|
|
20
|
+
return commandsMetaData;
|
|
21
|
+
}
|
|
22
|
+
const commandsIndex = await readFile(new URL('./commands.json', import.meta.url), 'utf-8');
|
|
23
|
+
commandsMetaData = JSON.parse(commandsIndex).commands;
|
|
24
|
+
return commandsMetaData;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Importa a classe do comando pelo `commandName`.
|
|
28
|
+
* O Ace chama esta função quando precisa executar um comando do pacote.
|
|
29
|
+
*/
|
|
30
|
+
export async function getCommand(metaData) {
|
|
31
|
+
const commands = await getMetaData();
|
|
32
|
+
const command = commands.find(({ commandName }) => metaData.commandName === commandName);
|
|
33
|
+
if (!command) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const { default: commandConstructor } = await import(__rewriteRelativeImportExtension(new URL(command.filePath, import.meta.url).href));
|
|
37
|
+
return commandConstructor;
|
|
38
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const VALID = ['headless', 'edge', 'react'];
|
|
2
|
+
export function resolveUiPreset(value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return 'edge';
|
|
5
|
+
if (!VALID.includes(value)) {
|
|
6
|
+
throw new Error(`authkit: --ui inválido "${value}". Use: ${VALID.join(' | ')}`);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
/** Caminhos de stub (relativos ao stubsRoot) que o preset publica. */
|
|
11
|
+
export function uiStubPaths(preset) {
|
|
12
|
+
switch (preset) {
|
|
13
|
+
case 'headless':
|
|
14
|
+
return [];
|
|
15
|
+
case 'edge':
|
|
16
|
+
return []; // views são donas-da-lib (disco authkit::); nada a scaffoldar
|
|
17
|
+
case 'react':
|
|
18
|
+
return [
|
|
19
|
+
'ui/react/components/auth_shell.tsx',
|
|
20
|
+
'ui/react/pages/login.tsx',
|
|
21
|
+
'ui/react/pages/consent.tsx',
|
|
22
|
+
'ui/react/pages/signup.tsx',
|
|
23
|
+
'ui/react/pages/forgot.tsx',
|
|
24
|
+
'ui/react/pages/reset.tsx',
|
|
25
|
+
'ui/react/pages/verify-email.tsx',
|
|
26
|
+
'ui/react/pages/mfa-challenge.tsx',
|
|
27
|
+
'ui/react/pages/account/login.tsx',
|
|
28
|
+
'ui/react/pages/account/tokens.tsx',
|
|
29
|
+
'ui/react/pages/account/mfa.tsx',
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseSchema } from '@adonisjs/lucid/schema';
|
|
2
|
+
export default class extends BaseSchema {
|
|
3
|
+
tableName = 'authkit_oidc_payloads';
|
|
4
|
+
async up() {
|
|
5
|
+
this.schema.createTable(this.tableName, (table) => {
|
|
6
|
+
table.string('id', 255).notNullable();
|
|
7
|
+
table.string('model_name', 100).notNullable();
|
|
8
|
+
table.text('payload').notNullable();
|
|
9
|
+
table.string('grant_id', 255).nullable().index();
|
|
10
|
+
table.string('user_code', 255).nullable().index();
|
|
11
|
+
table.string('uid', 255).nullable().index();
|
|
12
|
+
table.timestamp('expires_at', { useTz: true }).nullable();
|
|
13
|
+
table.primary(['model_name', 'id']);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async down() {
|
|
17
|
+
this.schema.dropTable(this.tableName);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-br"><head><meta charset="utf-8"><title>{{ t('account.login.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-100 p-4">
|
|
5
|
+
<form method="POST" action="/account/login"
|
|
6
|
+
class="w-full max-w-sm rounded-2xl bg-white p-8 shadow-xl ring-1 ring-black/5">
|
|
7
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
8
|
+
<div class="text-xs font-semibold uppercase tracking-widest text-gray-400">{{ t('common.brand_eyebrow') }}</div>
|
|
9
|
+
<h1 class="mt-2 text-xl font-semibold text-gray-900">{{ t('account.login.title') }}</h1>
|
|
10
|
+
<p class="mt-1 text-sm text-gray-500">{{ t('account.login.intro') }}</p>
|
|
11
|
+
|
|
12
|
+
@if(error)
|
|
13
|
+
<p class="mt-4 text-sm text-red-600">{{ error }}</p>
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
<label for="email" class="mt-6 mb-1 block text-sm font-medium text-gray-700">{{ t('account.login.email_label') }}</label>
|
|
17
|
+
<input id="email" name="email" type="email" required autofocus
|
|
18
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none transition focus:border-gray-900" />
|
|
19
|
+
|
|
20
|
+
<label for="password" class="mt-4 mb-1 block text-sm font-medium text-gray-700">{{ t('account.login.password_label') }}</label>
|
|
21
|
+
<input id="password" name="password" type="password" required
|
|
22
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none transition focus:border-gray-900" />
|
|
23
|
+
|
|
24
|
+
<button type="submit"
|
|
25
|
+
class="mt-6 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
26
|
+
{{ t('account.login.submit') }}
|
|
27
|
+
</button>
|
|
28
|
+
</form>
|
|
29
|
+
</body></html>
|