@adonis-agora/authkit-server 0.36.0 → 0.37.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/build/index.d.ts CHANGED
@@ -13,8 +13,8 @@ export { registerOidcRoutes } from './src/register_routes.js';
13
13
  export type { AuthServerConfigInput, ResolvedServerConfig, DynamicRegistrationConfigInput, ResolvedDynamicRegistrationConfig, AdminConfigInput, ResolvedAdminConfig, AdminApiConfigInput, ResolvedAdminApiConfig, } from './src/define_config.js';
14
14
  export { resolveAdmin, resolveAdminApi, resolveWebauthn, resolveDynamicRegistration } from './src/define_config.js';
15
15
  export type { WebauthnConfigInput, ResolvedWebauthnConfig } from './src/define_config.js';
16
- export { resolvePasswordless, resolveLogin } from './src/define_config.js';
17
- export type { PasswordlessConfigInput, ResolvedPasswordlessConfig, LoginConfigInput, ResolvedLoginConfig, } from './src/define_config.js';
16
+ export { resolvePasswordless, resolveLogin, resolveAuthMethodsConfig } from './src/define_config.js';
17
+ export type { PasswordlessConfigInput, ResolvedPasswordlessConfig, AuthMethodsConfigInput, ResolvedAuthMethodsConfig, LoginConfigInput, ResolvedLoginConfig, } from './src/define_config.js';
18
18
  export { resolveTrustedDevices, isTrustedDeviceValid, buildTrustedDevicePayload, TRUSTED_DEVICE_COOKIE, } from './src/host/trusted_device.js';
19
19
  export type { TrustedDevicesConfigInput, ResolvedTrustedDevicesConfig, TrustedDevicePayload, } from './src/host/trusted_device.js';
20
20
  export { resolveBotProtection, botProtectionApplies, extractBotToken, verifyBotProtection, guardBotProtection, DEFAULT_BOT_TOKEN_FIELDS, } from './src/host/bot_protection.js';
@@ -105,8 +105,8 @@ export { buildKeysStatus, rotateNow } from './src/host/key_rotation_actions.js';
105
105
  export type { KeysStatus as ServerKeysStatus } from './src/host/key_rotation_actions.js';
106
106
  export { resolveEffectiveBotProtection, } from './src/host/bot_protection.js';
107
107
  export type { BotProtectionSetting } from './src/host/bot_protection.js';
108
- export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, } from './src/host/runtime_toggles.js';
109
- export type { SettingKey, RegistrationSetting, RequireVerifiedEmailSetting, MaintenanceModeSetting, ResolvedMaintenanceMode, } from './src/host/runtime_toggles.js';
108
+ export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, resolveEffectiveAuthMethods, configLockedAuthMethods, } from './src/host/runtime_toggles.js';
109
+ export type { SettingKey, RegistrationSetting, RequireVerifiedEmailSetting, MaintenanceModeSetting, ResolvedMaintenanceMode, AuthMethodsSetting, ResolvedAuthMethods, AuthMethodsCapabilities, AuthMethodsConfigOverride, } from './src/host/runtime_toggles.js';
110
110
  export { resolveRegistration } from './src/define_config.js';
111
111
  export type { RegistrationConfigInput, ResolvedRegistrationConfig, } from './src/define_config.js';
112
112
  export { getAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
package/build/index.js CHANGED
@@ -9,7 +9,7 @@ export { withMfa } from './src/mixins/with_mfa.js';
9
9
  export { OidcService } from './src/provider/oidc_service.js';
10
10
  export { registerOidcRoutes } from './src/register_routes.js';
11
11
  export { resolveAdmin, resolveAdminApi, resolveWebauthn, resolveDynamicRegistration } from './src/define_config.js';
12
- export { resolvePasswordless, resolveLogin } from './src/define_config.js';
12
+ export { resolvePasswordless, resolveLogin, resolveAuthMethodsConfig } from './src/define_config.js';
13
13
  export { resolveTrustedDevices, isTrustedDeviceValid, buildTrustedDevicePayload, TRUSTED_DEVICE_COOKIE, } from './src/host/trusted_device.js';
14
14
  export { resolveBotProtection, botProtectionApplies, extractBotToken, verifyBotProtection, guardBotProtection, DEFAULT_BOT_TOKEN_FIELDS, } from './src/host/bot_protection.js';
15
15
  export { lucidAccountStore, appKeyEncrypter } from './src/accounts/lucid_account_store.js';
@@ -68,7 +68,7 @@ export { RuntimeSettings, supportsSettings } from './src/host/runtime_settings.j
68
68
  export { buildKeysStatus, rotateNow } from './src/host/key_rotation_actions.js';
69
69
  export { resolveEffectiveBotProtection, } from './src/host/bot_protection.js';
70
70
  // Runtime toggles (registration, require_verified_email, maintenance_mode).
71
- export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, } from './src/host/runtime_toggles.js';
71
+ export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, resolveEffectiveAuthMethods, configLockedAuthMethods, } from './src/host/runtime_toggles.js';
72
72
  export { resolveRegistration } from './src/define_config.js';
73
73
  export { getAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
74
74
  export { ACCOUNT_SESSION_KEY } from './src/host/middleware/account_auth.js';
@@ -337,6 +337,38 @@ export interface ResolvedPasswordlessConfig {
337
337
  signup: boolean;
338
338
  }
339
339
  export declare function resolvePasswordless(input?: PasswordlessConfigInput): ResolvedPasswordlessConfig;
340
+ /**
341
+ * Fixa métodos de login PELO ARQUIVO DE CONFIG, com PRIORIDADE sobre o runtime
342
+ * setting `auth_methods` (o inverso do default — normalmente o runtime manda).
343
+ *
344
+ * Cada campo declarado aqui é AUTORITATIVO: o console admin não sobrescreve em
345
+ * runtime (a UI mostra o toggle travado, "definido no config"). Campos omitidos
346
+ * continuam controlados pelo runtime → default por capability.
347
+ *
348
+ * Guards preservados no resolver:
349
+ * - Fixar um método em `true` só o liga se a CAPACIDADE existir (ex.: `magicLink: true`
350
+ * sem store capaz continua oculto). Fixar em `false` é sempre respeitado.
351
+ * - Fail-safe all-off: se pins + runtime zerarem TODOS os métodos, volta ao default
352
+ * (nunca trancar todo mundo pra fora).
353
+ *
354
+ * @example
355
+ * // Login sem senha (magic-link + passkey), travado pelo config — sem comando por ambiente:
356
+ * authMethods: { password: false }
357
+ */
358
+ export interface AuthMethodsConfigInput {
359
+ /** Fixa o login por senha. */
360
+ password?: boolean;
361
+ /** Fixa o login por magic link (respeitando a capacidade do store). */
362
+ magicLink?: boolean;
363
+ /** Fixa o "entrar com passkey" (respeitando a capacidade do store). */
364
+ passkey?: boolean;
365
+ /** Fixa o link "esqueci a senha" (só aparece com senha ligada, de qualquer forma). */
366
+ forgotPassword?: boolean;
367
+ }
368
+ /** authMethods resolvido: só os campos que o config FIXOU (os demais ficam a cargo do runtime). */
369
+ export type ResolvedAuthMethodsConfig = AuthMethodsConfigInput;
370
+ /** Normaliza o input: mantém só os campos booleanos declarados (os pins). */
371
+ export declare function resolveAuthMethodsConfig(input?: AuthMethodsConfigInput): ResolvedAuthMethodsConfig;
340
372
  /**
341
373
  * Política de login por senha/identidade. Hoje cobre `requireVerifiedEmail`:
342
374
  * quando ligado, TODO fluxo de login que materializa uma sessão (login por senha,
@@ -604,6 +636,12 @@ export interface AuthServerConfigInput {
604
636
  * desligados. Exigem as capacidades correspondentes no accountStore.
605
637
  */
606
638
  passwordless?: PasswordlessConfigInput;
639
+ /**
640
+ * Fixa métodos de login pelo arquivo de config, com PRIORIDADE sobre o runtime
641
+ * setting `auth_methods`. Cada campo declarado trava o método (o console admin não
642
+ * sobrescreve). Default: `{}` (tudo controlado pelo runtime). Ver {@link AuthMethodsConfigInput}.
643
+ */
644
+ authMethods?: AuthMethodsConfigInput;
607
645
  /**
608
646
  * Política de login (hoje: `requireVerifiedEmail`). Default: tudo desligado.
609
647
  * `requireVerifiedEmail` é capability-probed (precisa de `isEmailVerified` no store).
@@ -758,6 +796,8 @@ export interface ResolvedServerConfig {
758
796
  botProtection?: ResolvedBotProtectionConfig;
759
797
  /** Passwordless resolvido (default tudo desligado). */
760
798
  passwordless: ResolvedPasswordlessConfig;
799
+ /** Métodos de login fixados pelo config (prioridade sobre runtime; default `{}`). */
800
+ authMethods: ResolvedAuthMethodsConfig;
761
801
  /** Política de login resolvida (requireVerifiedEmail; default desligado). */
762
802
  login: ResolvedLoginConfig;
763
803
  /** Configuração de cadastro público resolvida (enabled; default true). */
@@ -95,6 +95,21 @@ export function resolvePasswordless(input) {
95
95
  signup: input?.signup ?? false,
96
96
  };
97
97
  }
98
+ /** Normaliza o input: mantém só os campos booleanos declarados (os pins). */
99
+ export function resolveAuthMethodsConfig(input) {
100
+ if (!input)
101
+ return {};
102
+ const out = {};
103
+ if (typeof input.password === "boolean")
104
+ out.password = input.password;
105
+ if (typeof input.magicLink === "boolean")
106
+ out.magicLink = input.magicLink;
107
+ if (typeof input.passkey === "boolean")
108
+ out.passkey = input.passkey;
109
+ if (typeof input.forgotPassword === "boolean")
110
+ out.forgotPassword = input.forgotPassword;
111
+ return out;
112
+ }
98
113
  export function resolveLogin(input) {
99
114
  return {
100
115
  requireVerifiedEmail: input?.requireVerifiedEmail ?? false,
@@ -335,6 +350,7 @@ export function defineConfig(config) {
335
350
  trustedDevices: resolveTrustedDevices(config.trustedDevices),
336
351
  botProtection: resolveBotProtection(config.botProtection),
337
352
  passwordless: resolvePasswordless(config.passwordless),
353
+ authMethods: resolveAuthMethodsConfig(config.authMethods),
338
354
  login: resolveLogin(config.login),
339
355
  registration: resolveRegistration(config.registration),
340
356
  accessTokens: resolveAccessTokens(config.issuer, config.accessTokens),
@@ -29,6 +29,10 @@ export function deriveLockedSettingKeys(config) {
29
29
  locked.push(key);
30
30
  };
31
31
  add(config.registration !== undefined, SETTING_KEYS.REGISTRATION);
32
+ // authMethods no config trava a key inteira `auth_methods`: os métodos passam a ser
33
+ // controlados pelo arquivo (via cfg.authMethods → configOverrides no resolver) e a UI
34
+ // desabilita os toggles com "definido via defineConfig".
35
+ add(config.authMethods !== undefined, SETTING_KEYS.AUTH_METHODS);
32
36
  add(config.login?.requireVerifiedEmail !== undefined, SETTING_KEYS.REQUIRE_VERIFIED_EMAIL);
33
37
  add(config.lockout !== undefined, SETTING_KEYS.LOCKOUT);
34
38
  add(config.rateLimit !== undefined, SETTING_KEYS.RATE_LIMIT);
@@ -87,6 +87,7 @@ export default class AuthInteractionController {
87
87
  configuredSocialProviders,
88
88
  magicLinkCapable: magicLinkCapableConfig,
89
89
  passkeyCapable: false, // passkey-first depends on account — resolved in step 2
90
+ configOverrides: cfg.authMethods,
90
91
  });
91
92
  if (!email) {
92
93
  // Step 1: identifier (email only).
@@ -258,6 +258,7 @@ export default class AuthRegistrationController {
258
258
  const authMethods = await resolveEffectiveAuthMethods(runtimeSettings, {
259
259
  configuredSocialProviders: cfg.social?.providers ?? [],
260
260
  magicLinkCapable: cfg.passwordless?.magicLink && typeof cfg.accountStore.issueMagicLinkToken === 'function',
261
+ configOverrides: cfg.authMethods,
261
262
  });
262
263
  if (!authMethods.forgotPassword) {
263
264
  return ctx.response.notFound({ error: translate(cfg.messages, 'errors.not_found') });
@@ -286,6 +287,7 @@ export default class AuthRegistrationController {
286
287
  const authMethods = await resolveEffectiveAuthMethods(runtimeSettings, {
287
288
  configuredSocialProviders: cfg.social?.providers ?? [],
288
289
  magicLinkCapable: cfg.passwordless?.magicLink && typeof cfg.accountStore.issueMagicLinkToken === 'function',
290
+ configOverrides: cfg.authMethods,
289
291
  });
290
292
  if (!authMethods.forgotPassword) {
291
293
  return ctx.response.notFound({ error: translate(cfg.messages, 'errors.not_found') });
@@ -202,7 +202,27 @@ export interface AuthMethodsCapabilities {
202
202
  magicLinkCapable?: boolean;
203
203
  /** Passkey-first disponível (config.passwordless.passkeyFirst && store tem passkeys). */
204
204
  passkeyCapable?: boolean;
205
+ /**
206
+ * Pins vindos do ARQUIVO DE CONFIG (`cfg.authMethods`): têm PRIORIDADE sobre o
207
+ * runtime setting `auth_methods`. Cada campo presente sobrescreve o valor resolvido
208
+ * do setting. Ligar respeita as capacidades (magicLink/passkey só ligam se capable);
209
+ * desligar é sempre respeitado. Campos ausentes seguem controlados pelo runtime.
210
+ */
211
+ configOverrides?: AuthMethodsConfigOverride;
205
212
  }
213
+ /** Métodos que o config pode FIXAR (prioridade sobre runtime). Só os campos declarados travam. */
214
+ export interface AuthMethodsConfigOverride {
215
+ password?: boolean;
216
+ magicLink?: boolean;
217
+ passkey?: boolean;
218
+ forgotPassword?: boolean;
219
+ }
220
+ /**
221
+ * Quais métodos estão TRAVADOS pelo config (para a UI admin mostrar o toggle
222
+ * desabilitado com "definido no arquivo de config"). É só a lista de campos
223
+ * booleanos presentes em `configOverrides`.
224
+ */
225
+ export declare function configLockedAuthMethods(configOverrides?: AuthMethodsConfigOverride): Array<keyof AuthMethodsConfigOverride>;
206
226
  /**
207
227
  * Resolve os métodos de autenticação efetivos combinando config estático + setting
208
228
  * persistida em runtime + capabilities.
@@ -127,6 +127,22 @@ export async function resolveEffectiveMaintenanceMode(settings) {
127
127
  message: typeof setting.message === 'string' ? setting.message : undefined,
128
128
  };
129
129
  }
130
+ /**
131
+ * Quais métodos estão TRAVADOS pelo config (para a UI admin mostrar o toggle
132
+ * desabilitado com "definido no arquivo de config"). É só a lista de campos
133
+ * booleanos presentes em `configOverrides`.
134
+ */
135
+ export function configLockedAuthMethods(configOverrides) {
136
+ if (!configOverrides)
137
+ return [];
138
+ const keys = [
139
+ 'password',
140
+ 'magicLink',
141
+ 'passkey',
142
+ 'forgotPassword',
143
+ ];
144
+ return keys.filter((k) => typeof configOverrides[k] === 'boolean');
145
+ }
130
146
  /**
131
147
  * Resolve os métodos de autenticação efetivos combinando config estático + setting
132
148
  * persistida em runtime + capabilities.
@@ -143,7 +159,7 @@ export async function resolveEffectiveMaintenanceMode(settings) {
143
159
  * 6. Qualquer erro DB → defaults derivados do config (fail-safe total).
144
160
  */
145
161
  export async function resolveEffectiveAuthMethods(settings, capabilities = {}) {
146
- const { configuredSocialProviders = [], magicLinkCapable = false, passkeyCapable = false, } = capabilities;
162
+ const { configuredSocialProviders = [], magicLinkCapable = false, passkeyCapable = false, configOverrides, } = capabilities;
147
163
  // Defaults derivados do config/capabilities.
148
164
  const configDefaults = {
149
165
  password: true,
@@ -153,47 +169,55 @@ export async function resolveEffectiveAuthMethods(settings, capabilities = {}) {
153
169
  forgotPassword: true,
154
170
  passkeyAutofill: passkeyCapable, // on por default quando passkey disponível
155
171
  };
172
+ // 1) Runtime setting sobre os defaults (setting ausente/inválida → só defaults).
156
173
  const raw = await settings.getSetting(SETTING_KEYS.AUTH_METHODS);
157
- if (raw === null || raw === undefined)
158
- return configDefaults;
159
- if (typeof raw !== 'object' || Array.isArray(raw))
160
- return configDefaults; // shape inválida
161
- const s = raw;
162
- // Aplica campo a campo, com fallback ao default quando o campo está ausente.
163
- const passwordEnabled = typeof s.password === 'boolean' ? s.password : configDefaults.password;
164
- const magicLinkEnabled = typeof s.magicLink === 'boolean' ? s.magicLink : configDefaults.magicLink;
165
- const passkeyEnabled = typeof s.passkey === 'boolean' ? s.passkey : configDefaults.passkey;
174
+ const s = raw !== null && raw !== undefined && typeof raw === 'object' && !Array.isArray(raw)
175
+ ? raw
176
+ : {};
177
+ let password = typeof s.password === 'boolean' ? s.password : configDefaults.password;
178
+ let magicLink = typeof s.magicLink === 'boolean' ? s.magicLink : configDefaults.magicLink;
179
+ let passkey = typeof s.passkey === 'boolean' ? s.passkey : configDefaults.passkey;
166
180
  // Social: interseção com os providers do config (setting não pode ligar o que não existe).
167
- let socialEnabled;
168
- if (Array.isArray(s.social)) {
169
- socialEnabled = s.social.filter((p) => configuredSocialProviders.includes(p));
170
- }
171
- else {
172
- socialEnabled = configDefaults.social;
173
- }
174
- // forgotPassword: derivado automaticamente — sem senha NUNCA existe esqueci-senha.
175
- const forgotPasswordEnabled = passwordEnabled && (typeof s.forgotPassword === 'boolean' ? s.forgotPassword : true);
181
+ const social = Array.isArray(s.social)
182
+ ? s.social.filter((p) => configuredSocialProviders.includes(p))
183
+ : configDefaults.social;
184
+ // 2) Pins do config: PRIORIDADE sobre o setting. Ligar respeita a capacidade;
185
+ // desligar sempre vale. `password` não tem guard de capacidade.
186
+ if (configOverrides) {
187
+ if (typeof configOverrides.password === 'boolean')
188
+ password = configOverrides.password;
189
+ if (typeof configOverrides.magicLink === 'boolean')
190
+ magicLink = configOverrides.magicLink && magicLinkCapable;
191
+ if (typeof configOverrides.passkey === 'boolean')
192
+ passkey = configOverrides.passkey && passkeyCapable;
193
+ }
194
+ // forgotPassword: derivado — sem senha NUNCA existe esqueci-senha. O pin/setting só
195
+ // pode DESLIGAR (nunca ligar sem senha). Precedência: pin do config > setting > default(on).
196
+ const forgotPasswordRequested = typeof configOverrides?.forgotPassword === 'boolean'
197
+ ? configOverrides.forgotPassword
198
+ : typeof s.forgotPassword === 'boolean'
199
+ ? s.forgotPassword
200
+ : true;
201
+ const forgotPassword = password && forgotPasswordRequested;
176
202
  // passkeyAutofill: só faz sentido quando passkey está ligado.
177
- const passkeyAutofillEnabled = passkeyEnabled &&
203
+ const passkeyAutofill = passkey &&
178
204
  (typeof s.passkeyAutofill === 'boolean' ? s.passkeyAutofill : configDefaults.passkeyAutofill);
179
205
  const resolved = {
180
- password: passwordEnabled,
181
- magicLink: magicLinkEnabled,
182
- passkey: passkeyEnabled,
183
- social: socialEnabled,
184
- forgotPassword: forgotPasswordEnabled,
185
- passkeyAutofill: passkeyAutofillEnabled,
206
+ password,
207
+ magicLink,
208
+ passkey,
209
+ social,
210
+ forgotPassword,
211
+ passkeyAutofill,
186
212
  };
187
- // FAIL-SAFE all-off: se todos ficaram false/vazio, volta ao config derivado.
188
- const allOff = !resolved.password &&
189
- !resolved.magicLink &&
190
- !resolved.passkey &&
191
- resolved.social.length === 0;
213
+ // FAIL-SAFE all-off: se pins + setting zerarem TODOS os métodos, volta ao config derivado
214
+ // (nunca deixar a tela sem método / trancar todo mundo pra fora).
215
+ const allOff = !resolved.password && !resolved.magicLink && !resolved.passkey && resolved.social.length === 0;
192
216
  if (allOff) {
193
217
  // eslint-disable-next-line no-console
194
- console.warn('[authkit] auth_methods setting deixou todos os métodos desligados — ' +
218
+ console.warn('[authkit] auth_methods (config pins + setting) deixou todos os métodos desligados — ' +
195
219
  'revertendo para os defaults do config (fail-safe). ' +
196
- 'Verifique a setting auth_methods no console admin.');
220
+ 'Verifique cfg.authMethods e a setting auth_methods no console admin.');
197
221
  return configDefaults;
198
222
  }
199
223
  return resolved;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.36.0",
3
+ "version": "0.37.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",
@@ -140,7 +140,7 @@
140
140
  "react-error-boundary": "6.1.2",
141
141
  "nuqs": "2.8.9",
142
142
  "recharts": "3.8.1",
143
- "@adonis-agora/authkit-react": "0.14.0"
143
+ "@adonis-agora/authkit-react": "0.16.0"
144
144
  },
145
145
  "scripts": {
146
146
  "build": "node scripts/build_host_css.mjs && node scripts/build_ui.mjs && tsc && 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')\"",