@adonis-agora/authkit-server 0.51.0 → 0.51.1

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.
@@ -40,17 +40,31 @@ export default class AuthInteractionController {
40
40
  * passo login. Sem isto, os re-renders (erro de senha, magic link enviado, lockout…) mandam
41
41
  * `authMethods` undefined → a view volta ao default (senha ligada), ignorando a config.
42
42
  * `passkeyFirst` depende da conta (resolvido no fluxo principal); aqui cobrimos senha + magic link.
43
+ *
44
+ * `otpEnabled` (disponibilidade do login por OTP: config ligada E store com a
45
+ * capacidade) também sai daqui — é um FATO de disponibilidade de método de login,
46
+ * como `magicLinkAvailable`. Assim TODO render do passo login (identifier, seletor
47
+ * choose-first, magicLinkSent, erro) carrega a flag POR CONSTRUÇÃO: o seletor
48
+ * precisa dela para oferecer a opção "código" ANTES do envio do magic link.
49
+ *
50
+ * `runtimeSettings` pode ser passado já resolvido (ex.: o `show()` já resolveu para
51
+ * maintenance) para evitar uma segunda resolução; ausente, resolve sob demanda.
43
52
  */
44
- async #loginMethods(ctx, cfg) {
45
- const runtimeSettings = await getRuntimeSettings(ctx);
53
+ async #loginMethods(ctx, cfg, runtimeSettings) {
54
+ const settings = runtimeSettings ?? (await getRuntimeSettings(ctx));
46
55
  const magicLinkCapableConfig = cfg.passwordless.magicLink && supportsMagicLink(cfg.accountStore);
47
- const authMethods = await resolveEffectiveAuthMethods(runtimeSettings, {
56
+ const authMethods = await resolveEffectiveAuthMethods(settings, {
48
57
  configuredSocialProviders: cfg.social?.providers ?? [],
49
58
  magicLinkCapable: magicLinkCapableConfig,
50
59
  passkeyCapable: false,
51
60
  configOverrides: cfg.authMethods,
52
61
  });
53
- return { authMethods, magicLinkAvailable: authMethods.magicLink && magicLinkCapableConfig };
62
+ const otpEnabled = cfg.login.otp.enabled && supportsOtpLogin(cfg.accountStore);
63
+ return {
64
+ authMethods,
65
+ magicLinkAvailable: authMethods.magicLink && magicLinkCapableConfig,
66
+ otpEnabled,
67
+ };
54
68
  }
55
69
  async show(ctx) {
56
70
  const service = await ctx.containerResolver.make('authkit.server');
@@ -102,36 +116,32 @@ export default class AuthInteractionController {
102
116
  });
103
117
  }
104
118
  const email = ctx.session.get(SESSION_KEY);
105
- // Resolve auth methods (capabilities: magic link, passkey-first, social providers).
106
- // Capabilities are account-independent at this stage for the identifier step.
107
- const magicLinkCapableConfig = cfg.passwordless.magicLink && supportsMagicLink(cfg.accountStore);
108
- const configuredSocialProviders = cfg.social?.providers ?? [];
109
- const authMethodsSettings = runtimeSettingsForMaintenance;
110
- const authMethods = await resolveEffectiveAuthMethods(authMethodsSettings, {
111
- configuredSocialProviders,
112
- magicLinkCapable: magicLinkCapableConfig,
113
- passkeyCapable: false, // passkey-first depends on account — resolved in step 2
114
- configOverrides: cfg.authMethods,
115
- });
119
+ // Métodos de login efetivos (magic link, OTP, social) — account-independent
120
+ // neste ponto (passkey-first depende da conta, resolvido no step 2). Reusa o
121
+ // helper compartilhado por TODOS os renders do passo login, para que
122
+ // `authMethods`, `magicLinkAvailable` e `otpEnabled` saiam POR CONSTRUÇÃO
123
+ // (o seletor choose-first precisa de `otpEnabled` para oferecer "código").
124
+ // Passa o runtimeSettings já resolvido para maintenance (sem 2ª resolução).
125
+ const loginMethods = await this.#loginMethods(ctx, cfg, runtimeSettingsForMaintenance);
126
+ const authMethods = loginMethods.authMethods;
116
127
  if (!email) {
117
128
  // Step 1: identifier (email only).
118
129
  // Resolve registration enabled to hide "create account" link when closed.
119
130
  const registrationEnabled = await resolveEffectiveRegistration(cfg.registration?.enabled ?? true, runtimeSettingsForMaintenance);
120
131
  return render(ctx, 'login', {
132
+ ...loginMethods,
121
133
  uid: details.uid,
122
134
  csrfToken: ctx.request.csrfToken,
123
135
  step: 'identifier',
124
136
  registrationEnabled,
125
137
  brand,
126
- authMethods,
127
138
  });
128
139
  }
129
140
  // Step 2: password — look up user for personalisation (enumeration-safe: always show step 2)
130
141
  const acc = await cfg.accountStore.findByEmail(email);
131
142
  const account = acc ? { fullName: acc.name ?? null, globalRoles: acc.globalRoles ?? [] } : null;
132
- // Passwordless: magic link disponível se ligado E o store suporta E auth_methods permite.
143
+ // Passwordless: magic link disponível vem do helper (`loginMethods`).
133
144
  // Passkey-first disponível se ligado, o store suporta, a conta tem passkeys E auth_methods permite.
134
- const magicLinkAvailable = authMethods.magicLink && magicLinkCapableConfig;
135
145
  const passkeyFirstCapable = cfg.passwordless.passkeyFirst && !!acc && (await this.hasPasskeys(cfg, acc.id));
136
146
  const passkeyFirstAvailable = authMethods.passkey && passkeyFirstCapable;
137
147
  // Effective bot protection: may be overridden at runtime via auth_settings.
@@ -139,16 +149,15 @@ export default class AuthInteractionController {
139
149
  // Session policy: resolve para exibir o checkbox "manter conectado".
140
150
  const sessionPolicyForShow = await resolveEffectiveSessionPolicy(runtimeSettingsForMaintenance, cfg.ttl?.session ? Math.ceil(cfg.ttl.session / 3600) : undefined);
141
151
  return render(ctx, 'login', {
152
+ ...loginMethods,
142
153
  uid: details.uid,
143
154
  csrfToken: ctx.request.csrfToken,
144
155
  step: 'password',
145
156
  email,
146
157
  account,
147
158
  brand,
148
- magicLinkAvailable,
149
159
  passkeyFirstAvailable,
150
160
  botProtection: effectiveBot?.on.includes('login') ? effectiveBot.widget : undefined,
151
- authMethods,
152
161
  rememberEnabled: sessionPolicyForShow.rememberEnabled,
153
162
  rememberDays: sessionPolicyForShow.rememberDays,
154
163
  });
@@ -657,6 +666,8 @@ export default class AuthInteractionController {
657
666
  }
658
667
  }
659
668
  // Resposta uniforme (não vaza existência de conta).
669
+ // `otpEnabled` vem do spread de `#loginMethods` (mesmo valor do `otpEnabled`
670
+ // local usado acima para decidir a emissão) — não re-declarado aqui.
660
671
  return render(ctx, 'login', {
661
672
  ...(await this.#loginMethods(ctx, cfg)),
662
673
  uid,
@@ -666,7 +677,6 @@ export default class AuthInteractionController {
666
677
  account: null,
667
678
  brand,
668
679
  magicLinkSent: true,
669
- otpEnabled,
670
680
  // Prop da tela: qual sub-view do estado `magicLinkSent` mostrar —
671
681
  // 'code' (só o campo de código), 'link' (só o aviso de link) ou 'both'
672
682
  // (ambos, quando o host não escolheu canal). Back-compat: ausente = 'both'.
@@ -764,6 +774,8 @@ export default class AuthInteractionController {
764
774
  maxAttempts: cfg.login.otp.maxAttempts,
765
775
  });
766
776
  // Re-render da tela "link enviado" com o campo de código + erro localizado.
777
+ // `otpEnabled` vem do spread de `#loginMethods` (aqui, após a guarda acima,
778
+ // vale `true`) — não re-declarado inline.
767
779
  const renderOtpError = async (messageKey) => render(ctx, 'login', {
768
780
  ...(await this.#loginMethods(ctx, cfg)),
769
781
  uid,
@@ -773,7 +785,6 @@ export default class AuthInteractionController {
773
785
  account: null,
774
786
  brand,
775
787
  magicLinkSent: true,
776
- otpEnabled: true,
777
788
  magicChannel: magicChannelProp(channel),
778
789
  otpError: translate(cfg.messages, messageKey),
779
790
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.51.0",
3
+ "version": "0.51.1",
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",