@adonis-agora/authkit-server 0.54.0 → 0.55.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.
Files changed (63) hide show
  1. package/build/index.d.ts +4 -2
  2. package/build/index.js +2 -1
  3. package/build/providers/authkit_server_provider.js +18 -0
  4. package/build/src/accounts/lucid_account_store.d.ts +35 -0
  5. package/build/src/accounts/lucid_account_store.js +9 -0
  6. package/build/src/accounts/lucid_store/core.js +88 -19
  7. package/build/src/accounts/lucid_store/shared.d.ts +14 -0
  8. package/build/src/accounts/lucid_store/token_hash.d.ts +79 -0
  9. package/build/src/accounts/lucid_store/token_hash.js +145 -0
  10. package/build/src/define_config.d.ts +93 -8
  11. package/build/src/define_config.js +28 -2
  12. package/build/src/host/account_api/account_api_controller.js +5 -4
  13. package/build/src/host/admin_api/admin_users_service.js +2 -1
  14. package/build/src/host/admin_api/api_orgs_controller.js +2 -1
  15. package/build/src/host/admin_console/console_impersonation_controller.d.ts +15 -1
  16. package/build/src/host/admin_console/console_impersonation_controller.js +26 -2
  17. package/build/src/host/admin_console/console_orgs_controller.js +3 -1
  18. package/build/src/host/admin_validators.d.ts +3 -3
  19. package/build/src/host/auth_host_config.d.ts +30 -0
  20. package/build/src/host/auth_host_config.js +15 -0
  21. package/build/src/host/config_locks.d.ts +29 -0
  22. package/build/src/host/config_locks.js +46 -0
  23. package/build/src/host/console_session.d.ts +38 -2
  24. package/build/src/host/console_session.js +46 -2
  25. package/build/src/host/controllers/account_mfa_controller.js +5 -5
  26. package/build/src/host/controllers/account_orgs_controller.js +2 -1
  27. package/build/src/host/controllers/account_security_controller.js +6 -5
  28. package/build/src/host/controllers/interaction_controller.js +122 -26
  29. package/build/src/host/controllers/registration_controller.js +5 -4
  30. package/build/src/host/controllers/social_controller.js +37 -0
  31. package/build/src/host/default_mailer.d.ts +36 -5
  32. package/build/src/host/default_mailer.js +63 -10
  33. package/build/src/host/i18n.d.ts +10 -0
  34. package/build/src/host/i18n.js +16 -0
  35. package/build/src/host/login_attempt.d.ts +88 -2
  36. package/build/src/host/login_attempt.js +101 -48
  37. package/build/src/host/login_notify.js +2 -2
  38. package/build/src/host/oidc_rp_guard.d.ts +25 -2
  39. package/build/src/host/oidc_rp_guard.js +55 -9
  40. package/build/src/host/origin.d.ts +21 -0
  41. package/build/src/host/origin.js +22 -0
  42. package/build/src/host/register_auth_host.d.ts +104 -3
  43. package/build/src/host/register_auth_host.js +222 -26
  44. package/build/src/host/runtime_settings.d.ts +16 -0
  45. package/build/src/host/runtime_settings.js +24 -0
  46. package/build/src/host/runtime_toggles.d.ts +10 -0
  47. package/build/src/host/runtime_toggles.js +4 -0
  48. package/build/src/host/security_notice_service.d.ts +4 -2
  49. package/build/src/host/security_notice_service.js +4 -2
  50. package/build/src/host/sudo/index.d.ts +8 -0
  51. package/build/src/host/sudo/index.js +8 -0
  52. package/build/src/host/sudo/methods/magic_link.d.ts +17 -3
  53. package/build/src/host/sudo/methods/magic_link.js +37 -13
  54. package/build/src/host/sudo/runtime.d.ts +44 -4
  55. package/build/src/host/sudo/runtime.js +90 -6
  56. package/build/src/host/sudo/satisfiability.d.ts +62 -0
  57. package/build/src/host/sudo/satisfiability.js +89 -0
  58. package/build/src/password/common_passwords.js +27 -7
  59. package/build/src/provider/oidc_service.js +25 -13
  60. package/build/src/provider/token_exchange.d.ts +12 -1
  61. package/build/src/provider/token_exchange.js +12 -0
  62. package/package.json +6 -3
  63. /package/build/{password → src/password}/common_passwords.txt +0 -0
@@ -11,7 +11,7 @@
11
11
  * Fonte dos dados: compilação de senhas comuns de domínio público.
12
12
  * Licença do arquivo de dados: CC0 / Public Domain.
13
13
  */
14
- import { readFileSync } from 'node:fs';
14
+ import { existsSync, readFileSync } from 'node:fs';
15
15
  import { dirname, join } from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
17
  /**
@@ -23,16 +23,36 @@ let _commonPasswordsSet = null;
23
23
  /**
24
24
  * Carrega o arquivo de senhas comuns uma única vez (lazy).
25
25
  *
26
- * FAIL-SAFE: se o arquivo não existir ou não puder ser lido (ex.: bundle sem
27
- * assets), retorna um Set vazio a checagem vira no-op sem quebrar o fluxo.
26
+ * Onde o arquivo é esperado: o build script copia `common_passwords.txt`
27
+ * para o mesmo diretório do módulo compilado (`build/src/password/`, que
28
+ * `tsc` com `rootDir: "./"` preserva o subcaminho `src/`). Em dev via
29
+ * ts-exec (sem build), o módulo roda direto de `src/password/`, onde o
30
+ * `.txt` já mora ao lado do `.ts` no repo.
31
+ *
32
+ * Checamos mais de um candidato — não só o caminho-irmão — para sobreviver
33
+ * a uma mudança futura em `rootDir`/`outDir` ou a uma regressão no script de
34
+ * cópia do build: o caminho-irmão cobre o layout atual (build e dev); os
35
+ * demais são fallbacks para o layout antigo (`build/password/`) e para ler
36
+ * direto de `src/` a partir de um build.
37
+ *
38
+ * FAIL-SAFE: se o arquivo não existir em nenhum candidato, ou não puder ser
39
+ * lido (ex.: bundle sem assets), retorna um Set vazio — a checagem vira
40
+ * no-op sem quebrar o fluxo. Uma senha comum indevidamente aceita é sempre
41
+ * preferível a um login/signup quebrado por um asset ausente.
28
42
  */
29
43
  function loadCommonPasswords() {
30
44
  if (_commonPasswordsSet !== null)
31
45
  return _commonPasswordsSet;
32
46
  try {
33
- const __filename = fileURLToPath(import.meta.url);
34
- const __dir = dirname(__filename);
35
- const filePath = join(__dir, 'common_passwords.txt');
47
+ const __dir = dirname(fileURLToPath(import.meta.url));
48
+ const candidates = [
49
+ join(__dir, 'common_passwords.txt'), // caminho-irmão: build/src/password/ (prod) ou src/password/ (dev via ts-exec)
50
+ join(__dir, '..', '..', 'password', 'common_passwords.txt'), // layout antigo: build/password/
51
+ join(__dir, '..', '..', '..', 'src', 'password', 'common_passwords.txt'), // fallback: src/ a partir de um build
52
+ ];
53
+ const filePath = candidates.find((p) => existsSync(p));
54
+ if (!filePath)
55
+ throw new Error('common_passwords.txt not found in any candidate path');
36
56
  const content = readFileSync(filePath, 'utf-8');
37
57
  const entries = content
38
58
  .split('\n')
@@ -41,7 +61,7 @@ function loadCommonPasswords() {
41
61
  _commonPasswordsSet = new Set(entries);
42
62
  }
43
63
  catch {
44
- // Fail-safe: arquivo ausente → Set vazio (no-op check).
64
+ // Fail-safe: arquivo ausente em todos os candidatos → Set vazio (no-op check).
45
65
  _commonPasswordsSet = new Set();
46
66
  }
47
67
  return _commonPasswordsSet;
@@ -118,19 +118,31 @@ export class OidcService {
118
118
  },
119
119
  }, this.sessionTtlHolder, this.tokenTtlHolder);
120
120
  wireProviderEvents(provider, this.recorder);
121
- registerTokenExchange(provider, {
122
- findAccount: config.findAccount,
123
- globalRolesClaim: config.globalRolesClaim,
124
- resolveTokenRoles: config.resolveTokenRoles,
125
- // Resource indicators (RFC 8707) suportados: o `audience` default + cada
126
- // resource declarado. Usado para validar `audience`/`resource` no pedido de
127
- // token-exchange — alvos fora desta lista são rejeitados (invalid_target).
128
- supportedResources: [
129
- config.accessTokens.audience,
130
- ...Object.keys(config.accessTokens.resources),
131
- ],
132
- audit: config.audit,
133
- });
121
+ // KILL SWITCH de impersonation (`admin.impersonation`, default `true`).
122
+ // Com a flag em `false` o grant simplesmente NÃO EXISTE no provider — o
123
+ // token endpoint responde `unsupported_grant_type` — em vez de existir e
124
+ // recusar caso a caso. É a mesma flag que fecha o painel do console
125
+ // (`console_impersonation_controller.ts`): uma declaração, duas superfícies.
126
+ if (config.admin.impersonation) {
127
+ registerTokenExchange(provider, {
128
+ findAccount: config.findAccount,
129
+ globalRolesClaim: config.globalRolesClaim,
130
+ resolveTokenRoles: config.resolveTokenRoles,
131
+ // Resource indicators (RFC 8707) suportados: o `audience` default + cada
132
+ // resource declarado. Usado para validar `audience`/`resource` no pedido de
133
+ // token-exchange — alvos fora desta lista são rejeitados (invalid_target).
134
+ supportedResources: [
135
+ config.accessTokens.audience,
136
+ ...Object.keys(config.accessTokens.resources),
137
+ ],
138
+ // LOAD-BEARING: sem isto o gate de status do alvo dentro de
139
+ // `token_exchange.ts` degrada para "allowed" (o campo é opcional, para não
140
+ // quebrar hosts com stores mínimos) e um admin consegue impersonar uma
141
+ // conta que acabou de desabilitar, recebendo tokens plenamente funcionais.
142
+ accountStore: config.accountStore,
143
+ audit: config.audit,
144
+ });
145
+ }
134
146
  // Quando o issuer tem um path (ex.: http://host/oidc), o provider precisa ser
135
147
  // MONTADO sob esse path via koa-mount. Isso faz o oidc-provider gerar URLs de
136
148
  // discovery e redirects de resume/interaction CORRETAMENTE prefixados (ex.:
@@ -1,4 +1,4 @@
1
- import type { AuthAccount } from '../accounts/account_store.js';
1
+ import type { AccountStore, AuthAccount } from '../accounts/account_store.js';
2
2
  import type { AuditSink } from '../audit/audit_sink.js';
3
3
  export interface TokenExchangeAccount {
4
4
  id: string;
@@ -32,5 +32,16 @@ export interface TokenExchangeDeps {
32
32
  supportedResources?: string[];
33
33
  /** Sink de auditoria (best-effort). Quando presente, registra `impersonation`. */
34
34
  audit?: AuditSink;
35
+ /**
36
+ * AccountStore do host, usado para checar se o alvo da impersonação está
37
+ * desabilitado/expirado (mesmo gate de `attemptPasswordLogin`, via
38
+ * `assertAccountEnabled`). Impersonar uma conta desabilitada mintaria tokens
39
+ * funcionais para uma identidade que deveria estar sem acesso algum.
40
+ *
41
+ * OPCIONAL e capability-probed (via `supportsAccountStatus`, dentro do
42
+ * helper): ausente ou sem a capacidade → degrada para "permitido" (mesma
43
+ * regra de todo o resto da lib — nunca quebra hosts com um store mínimo).
44
+ */
45
+ accountStore?: AccountStore;
35
46
  }
36
47
  export declare function registerTokenExchange(provider: any, deps: TokenExchangeDeps): void;
@@ -1,4 +1,5 @@
1
1
  import { errors } from 'oidc-provider';
2
+ import { assertAccountEnabled } from '../host/login_attempt.js';
2
3
  const TOKEN_EXCHANGE = 'urn:ietf:params:oauth:grant-type:token-exchange';
3
4
  const ACCESS_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
4
5
  /**
@@ -46,6 +47,17 @@ export function registerTokenExchange(provider, deps) {
46
47
  if (!target) {
47
48
  throw new errors.InvalidGrant('requested_subject not found');
48
49
  }
50
+ // Status do alvo (disabled/expirado): mesmo gate de `attemptPasswordLogin`.
51
+ // Sem isso, impersonar um alvo desabilitado mintava tokens funcionais para
52
+ // uma identidade que o admin acreditava ter revogado. `accountStore` é
53
+ // opcional/capability-probed — sem ele (ou sem a capacidade), permanece
54
+ // "permitido" como antes.
55
+ if (deps.accountStore) {
56
+ const statusGate = await assertAccountEnabled({ accountStore: deps.accountStore, audit: deps.audit }, target.id, { email: target.email ?? '', ip: ctx.req?.socket?.remoteAddress ?? null });
57
+ if (!statusGate.allowed) {
58
+ throw new errors.InvalidGrant('requested_subject is disabled');
59
+ }
60
+ }
49
61
  // audience/resource: se o pedido vier com um alvo, ele PRECISA estar entre os
50
62
  // resource indicators suportados. Caso contrário rejeitamos (conservador) —
51
63
  // nunca embutimos audiência arbitrária no token emitido.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.54.0",
3
+ "version": "0.55.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",
@@ -124,6 +124,8 @@
124
124
  "@japa/assert": "4.2.0",
125
125
  "@japa/runner": "5.3.0",
126
126
  "@poppinss/ts-exec": "1.4.4",
127
+ "@types/better-sqlite3": "^7.6.13",
128
+ "@types/ioredis-mock": "^8.2.7",
127
129
  "@types/koa": "^2.15.2",
128
130
  "@types/koa-mount": "^4.0.5",
129
131
  "@types/luxon": "3.7.1",
@@ -151,11 +153,12 @@
151
153
  "@adonis-agora/authkit-react": "0.18.0"
152
154
  },
153
155
  "scripts": {
154
- "build": "node scripts/build_host_css.mjs && node scripts/build_webauthn.mjs && node scripts/build_ui.mjs && node -e \"const fs=require('node:fs');for(const d of ['build/stubs','build/host/views'])fs.rmSync(d,{recursive:true,force:true})\" && tsc && node -e \"require('node:fs').cpSync('src/host/assets','build/src/host/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\" && node -e \"const fs=require('node:fs');if(fs.existsSync('assets'))fs.cpSync('assets','build/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('src/host/views','build/host/views',{recursive:true})\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/host/ui',{recursive:true});fs.readdirSync('src/host/ui').filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync('src/host/ui/'+f,'build/host/ui/'+f))\" && node -e \"require('node:fs').copyFileSync('commands/commands.json','build/commands/commands.json')\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/password',{recursive:true});fs.copyFileSync('src/password/common_passwords.txt','build/password/common_passwords.txt')\"",
156
+ "build": "node scripts/build_host_css.mjs && node scripts/build_webauthn.mjs && node scripts/build_ui.mjs && node -e \"const fs=require('node:fs');for(const d of ['build/stubs','build/host/views'])fs.rmSync(d,{recursive:true,force:true})\" && tsc && node -e \"require('node:fs').cpSync('src/host/assets','build/src/host/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\" && node -e \"const fs=require('node:fs');if(fs.existsSync('assets'))fs.cpSync('assets','build/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('src/host/views','build/host/views',{recursive:true})\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/host/ui',{recursive:true});fs.readdirSync('src/host/ui').filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync('src/host/ui/'+f,'build/host/ui/'+f))\" && node -e \"require('node:fs').copyFileSync('commands/commands.json','build/commands/commands.json')\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/src/password',{recursive:true});fs.copyFileSync('src/password/common_passwords.txt','build/src/password/common_passwords.txt')\"",
155
157
  "build:ui": "node scripts/build_ui.mjs",
156
158
  "build:webauthn": "node scripts/build_webauthn.mjs",
157
159
  "check:webauthn-bundle": "node scripts/check_webauthn_bundle.mjs",
158
- "typecheck": "tsc --noEmit && node scripts/typecheck_ui.mjs",
160
+ "typecheck": "tsc --noEmit && node scripts/typecheck_ui.mjs && node scripts/typecheck_tests.mjs",
161
+ "typecheck:tests": "node scripts/typecheck_tests.mjs",
159
162
  "test": "node --import=@poppinss/ts-exec bin/test.ts",
160
163
  "build:host-css": "node scripts/build_host_css.mjs"
161
164
  }