@adonis-agora/authkit-server 0.61.1 → 0.61.3
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/build/src/host/assets/logout.js +4 -0
- package/build/src/host/controllers/logout_asset_controller.d.ts +27 -0
- package/build/src/host/controllers/logout_asset_controller.js +71 -0
- package/build/src/host/register_auth_host.js +6 -0
- package/build/src/provider/logout_sources.js +6 -1
- package/build/src/schema/ensure.js +25 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* GET /authkit/assets/logout.js
|
|
4
|
+
*
|
|
5
|
+
* Serve o script de auto-submit do splash de logout a partir do próprio host.
|
|
6
|
+
*
|
|
7
|
+
* O `logoutSource` (provider/logout_sources.ts) injeta este script VIA
|
|
8
|
+
* `<script src>` em vez de inline: hosts com CSP `script-src 'self'`
|
|
9
|
+
* bloqueiam script inline sem nonce/hash, e o auto-confirm do logout
|
|
10
|
+
* silenciosamente parava de rodar — a tela "Saindo / Encerrando sua
|
|
11
|
+
* sessão…" ficava presa para sempre. Um asset same-origin é permitido por
|
|
12
|
+
* `'self'` em qualquer CSP razoável, sem depender de nonce nem hash.
|
|
13
|
+
*
|
|
14
|
+
* SEM AUTENTICAÇÃO, e é intencional: é asset estático necessário na tela de
|
|
15
|
+
* logout, antes de a sessão SSO ser destruída. Também não pode viver sob o
|
|
16
|
+
* prefixo do console admin (que é opt-in).
|
|
17
|
+
*/
|
|
18
|
+
export default class LogoutAssetController {
|
|
19
|
+
handle(ctx: HttpContext): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Limpa o cache do asset. Existe para os testes conseguirem exercitar tanto o
|
|
23
|
+
* caminho feliz quanto o 404 no mesmo processo.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export declare function resetLogoutAssetCache(): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
/**
|
|
3
|
+
* URL do script de auto-confirmação do RP-initiated logout, servido pelo
|
|
4
|
+
* próprio host.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ RESOLVIDO VIA `import.meta.url`, NUNCA relativo ao cwd. Os apps que
|
|
7
|
+
* consomem este pacote rodam `pnpm deploy --legacy`, que remonta a árvore de
|
|
8
|
+
* `node_modules` num diretório novo — um caminho relativo ao cwd do host
|
|
9
|
+
* apontaria para o lugar errado e a rota daria 404 em produção. Ancorado no
|
|
10
|
+
* módulo, o caminho segue o pacote para onde quer que ele seja copiado.
|
|
11
|
+
*
|
|
12
|
+
* Funciona nos dois layouts porque o asset é commitado em `src/host/assets/`
|
|
13
|
+
* e copiado para `build/src/host/assets/` pelo script `build`:
|
|
14
|
+
* dev → src/host/controllers/… → src/host/assets/logout.js
|
|
15
|
+
* build → build/src/host/controllers/… → build/src/host/assets/logout.js
|
|
16
|
+
*/
|
|
17
|
+
const ASSET_URL = new URL('../assets/logout.js', import.meta.url);
|
|
18
|
+
/**
|
|
19
|
+
* Cache do conteúdo do asset. É imutável por versão do pacote — ler do disco
|
|
20
|
+
* a cada request de logout não compra nada.
|
|
21
|
+
*
|
|
22
|
+
* `null` = ainda não lido. `false` = lido e ausente (404 memoizado); sem isso
|
|
23
|
+
* um asset faltando viraria um `readFile` que falha por request.
|
|
24
|
+
*/
|
|
25
|
+
let cached = null;
|
|
26
|
+
/**
|
|
27
|
+
* GET /authkit/assets/logout.js
|
|
28
|
+
*
|
|
29
|
+
* Serve o script de auto-submit do splash de logout a partir do próprio host.
|
|
30
|
+
*
|
|
31
|
+
* O `logoutSource` (provider/logout_sources.ts) injeta este script VIA
|
|
32
|
+
* `<script src>` em vez de inline: hosts com CSP `script-src 'self'`
|
|
33
|
+
* bloqueiam script inline sem nonce/hash, e o auto-confirm do logout
|
|
34
|
+
* silenciosamente parava de rodar — a tela "Saindo / Encerrando sua
|
|
35
|
+
* sessão…" ficava presa para sempre. Um asset same-origin é permitido por
|
|
36
|
+
* `'self'` em qualquer CSP razoável, sem depender de nonce nem hash.
|
|
37
|
+
*
|
|
38
|
+
* SEM AUTENTICAÇÃO, e é intencional: é asset estático necessário na tela de
|
|
39
|
+
* logout, antes de a sessão SSO ser destruída. Também não pode viver sob o
|
|
40
|
+
* prefixo do console admin (que é opt-in).
|
|
41
|
+
*/
|
|
42
|
+
export default class LogoutAssetController {
|
|
43
|
+
async handle(ctx) {
|
|
44
|
+
if (cached === null) {
|
|
45
|
+
try {
|
|
46
|
+
cached = await readFile(ASSET_URL);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
cached = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (cached === false) {
|
|
53
|
+
// 404 limpo: o asset não foi incluído no pacote. O splash degrada para o
|
|
54
|
+
// botão `<noscript>` (que continua funcionando sem JS) em vez de 500.
|
|
55
|
+
return ctx.response.notFound();
|
|
56
|
+
}
|
|
57
|
+
return ctx.response
|
|
58
|
+
.type('text/javascript')
|
|
59
|
+
.header('Cache-Control', 'public, max-age=31536000, immutable')
|
|
60
|
+
.send(cached);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Limpa o cache do asset. Existe para os testes conseguirem exercitar tanto o
|
|
65
|
+
* caminho feliz quanto o 404 no mesmo processo.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export function resetLogoutAssetCache() {
|
|
70
|
+
cached = null;
|
|
71
|
+
}
|
|
@@ -181,6 +181,7 @@ const C = {
|
|
|
181
181
|
accountOrgs: () => import('./controllers/account_orgs_controller.js'),
|
|
182
182
|
accountConfirm: () => import('./controllers/account_confirm_controller.js'),
|
|
183
183
|
webauthnAsset: () => import('./controllers/webauthn_asset_controller.js'),
|
|
184
|
+
logoutAsset: () => import('./controllers/logout_asset_controller.js'),
|
|
184
185
|
// Console React JSON API (session-authed, under {prefix}/api/*).
|
|
185
186
|
consoleShell: () => import('./admin_console/admin_shell_controller.js'),
|
|
186
187
|
consoleOverview: () => import('./admin_console/console_overview_controller.js'),
|
|
@@ -386,6 +387,11 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
386
387
|
// • registrado ANTES do wildcard `${mount}/*` para que nenhum mountPath
|
|
387
388
|
// agressivo (ex.: '/') consiga engolir o asset e quebrar o login.
|
|
388
389
|
router.get('/authkit/assets/webauthn.js', [C.webauthnAsset]).as('authkit.assets.webauthn');
|
|
390
|
+
// Script de auto-confirmação do splash de logout (RP-initiated logout).
|
|
391
|
+
// Same-origin (permitido por CSP `script-src 'self'`) — o logoutSource NÃO
|
|
392
|
+
// pode usar script inline: o CSP bloqueia sem nonce/hash e o logout fica
|
|
393
|
+
// preso em "Saindo / Encerrando sua sessão…".
|
|
394
|
+
router.get('/authkit/assets/logout.js', [C.logoutAsset]).as('authkit.assets.logout');
|
|
389
395
|
// Provider OIDC (wildcard + root) — o que registerOidcRoutes fazia.
|
|
390
396
|
router.any(`${mount}/*`, [C.oidc]).as('authkit.oidc.wildcard');
|
|
391
397
|
router.any(mount, [C.oidc]).as('authkit.oidc.root');
|
|
@@ -45,7 +45,12 @@ export function createLogoutSources(messages) {
|
|
|
45
45
|
async logoutSource(ctx, form) {
|
|
46
46
|
// Injeta o campo de confirmação dentro do form fornecido pela lib.
|
|
47
47
|
const confirmedForm = form.replace(/<\/form>/, '<input type="hidden" name="logout" value="yes"/></form>');
|
|
48
|
-
|
|
48
|
+
// Auto-submit via asset SAME-ORIGIN (`/authkit/assets/logout.js`), NÃO
|
|
49
|
+
// script inline: hosts com CSP `script-src 'self'` (+ nonce/hash, sem
|
|
50
|
+
// 'unsafe-inline') bloqueiam inline e o logout ficava preso em "Saindo /
|
|
51
|
+
// Encerrando sua sessão…" para sempre (o POST de confirm nunca rodava).
|
|
52
|
+
// `<script src>` same-origin é permitido por `'self'` sem nonce/hash.
|
|
53
|
+
ctx.body = page(t('logout.title'), `<div class="spinner" aria-hidden="true"></div><h1>${esc(t('logout.title'))}</h1><p>${esc(t('logout.body'))}</p>${confirmedForm}<noscript><button type="submit" form="op.logoutForm">${esc(t('logout.fallback'))}</button></noscript><script src="/authkit/assets/logout.js"></script>`);
|
|
49
54
|
},
|
|
50
55
|
/**
|
|
51
56
|
* Mostrada apenas quando NÃO há `post_logout_redirect_uri` (senão o provider
|
|
@@ -260,5 +260,30 @@ export async function ensureAuthkitSchema(db, options = {}) {
|
|
|
260
260
|
report.altered[def.name].push(column);
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
+
// Coluna `login_methods` em `auth.users` — a tabela é HOST-owned (não está em
|
|
264
|
+
// TABLES porque o nome/shape são decisão do app), mas a LI B consome a coluna
|
|
265
|
+
// (LoginMethodsPreferenceCapability). Alguns deploys de host só migram o schema
|
|
266
|
+
// do domínio, não o schema `auth` — então a migration manual da coluna pode
|
|
267
|
+
// nunca rodar, e isso derrubava TODO login/callback OIDC com "column users.login_methods
|
|
268
|
+
// does not exist" no instante em que o model passou a declará-la.
|
|
269
|
+
// Aqui garantimos: SE `auth.users` existe E a coluna falta, adicionamos. Nunca
|
|
270
|
+
// criamos a tabela (host-owned). Aditivo + idempotente — roda em todo boot.
|
|
271
|
+
try {
|
|
272
|
+
if (await tableExists(conn, 'users')) {
|
|
273
|
+
if (!(await columnExists(conn, 'users', 'login_methods'))) {
|
|
274
|
+
await conn.schema.alterTable('users', (t) => {
|
|
275
|
+
t.jsonb('login_methods').nullable();
|
|
276
|
+
});
|
|
277
|
+
report.altered.users = ['login_methods'];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
// Tabela host-owned pode não existir num banco que nunca criou users — não
|
|
283
|
+
// é erro: fail-soft (a migração do host cuida). Nunca criar a tabela aqui.
|
|
284
|
+
if (!(await tableExists(conn, 'users'))) {
|
|
285
|
+
throw error; // alguém criou entre o probe e o ALTER — repropaga se sumiu
|
|
286
|
+
}
|
|
287
|
+
}
|
|
263
288
|
return report;
|
|
264
289
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "0.61.
|
|
3
|
+
"version": "0.61.3",
|
|
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",
|