@adonis-agora/authkit-server 1.0.0-rc.13 → 1.0.0-rc.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @adonis-agora/authkit-server
2
2
 
3
+ ## 1.0.0-rc.14
4
+
5
+ ### Patch Changes
6
+
7
+ - [#476](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/476) [`b9f9119`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/b9f9119a4a030b1fb03befd73d018abd88e8bf6c) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Default emails (new login / new device alerts, password reset, verification, magic link, sudo link, OTP unlock, email change, security notices, org invitations, `sendAuthkitCodeEmail`) now always use the host's `i18n.locale` and `branding`. They used to read the config through `ctx.containerResolver.app`, which a real request's container resolver does not have, so they silently fell back to English and the "AuthKit" brand. The mailer now uses the resolved config the caller already has, or resolves the `authkit.server` binding, and logs a warning once when it cannot find the config. The header falls back to `branding.company` when `branding.default` is absent.
8
+
3
9
  ## 1.0.0-rc.13
4
10
 
5
11
  ### Patch Changes
@@ -404,7 +404,7 @@ export default class AccountApiController {
404
404
  });
405
405
  }
406
406
  else {
407
- await sendEmailChangeConfirmationEmail(ctx, { email: newEmail, confirmUrl });
407
+ await sendEmailChangeConfirmationEmail(ctx, { email: newEmail, confirmUrl }, cfg);
408
408
  }
409
409
  if (account) {
410
410
  if (cfg.mail?.onEmailChangeNotice) {
@@ -272,7 +272,7 @@ export default class AccountOrgsApiController {
272
272
  if (c.cfg.mail?.onOrgInvitation)
273
273
  await c.cfg.mail.onOrgInvitation(payload);
274
274
  else
275
- await sendOrgInvitationEmail(ctx, payload);
275
+ await sendOrgInvitationEmail(ctx, payload, c.cfg);
276
276
  }
277
277
  catch {
278
278
  /* best-effort */
@@ -314,7 +314,7 @@ export class AdminOrgsService {
314
314
  await this.cfg.mail.onOrgInvitation(payload);
315
315
  }
316
316
  else if (ctx) {
317
- await sendOrgInvitationEmail(ctx, payload);
317
+ await sendOrgInvitationEmail(ctx, payload, this.cfg);
318
318
  }
319
319
  }
320
320
  catch {
@@ -312,7 +312,7 @@ export class AdminUsersService {
312
312
  });
313
313
  }
314
314
  else {
315
- await sendPasswordResetEmail(ctx, { email, resetUrl });
315
+ await sendPasswordResetEmail(ctx, { email, resetUrl }, this.cfg);
316
316
  }
317
317
  }
318
318
  }
@@ -226,7 +226,7 @@ export default class AccountOrgsController {
226
226
  await cfg.mail.onOrgInvitation(payload);
227
227
  }
228
228
  else {
229
- await sendOrgInvitationEmail(ctx, payload);
229
+ await sendOrgInvitationEmail(ctx, payload, cfg);
230
230
  }
231
231
  }
232
232
  catch {
@@ -439,7 +439,7 @@ export default class AccountSecurityController {
439
439
  await sendEmailChangeConfirmationEmail(ctx, {
440
440
  email: newEmail,
441
441
  confirmUrl,
442
- });
442
+ }, cfg);
443
443
  }
444
444
  // 2. Aviso de segurança → e-mail ATUAL (best-effort, fail-safe).
445
445
  if (account) {
@@ -1098,7 +1098,7 @@ export default class AuthInteractionController {
1098
1098
  await cfg.mail.onOtpUnlock({ email: account.email, unlockUrl, token: raw }).catch(() => { });
1099
1099
  }
1100
1100
  else {
1101
- await sendOtpUnlockEmail(ctx, { email: account.email, unlockUrl }).catch(() => { });
1101
+ await sendOtpUnlockEmail(ctx, { email: account.email, unlockUrl }, cfg).catch(() => { });
1102
1102
  }
1103
1103
  }
1104
1104
  catch {
@@ -1200,7 +1200,7 @@ export default class AuthInteractionController {
1200
1200
  await cfg.mail.onMagicLink({ email: to, magicUrl, token: issued.token, code, channel });
1201
1201
  }
1202
1202
  else {
1203
- await sendMagicLinkEmail(ctx, { email: to, magicUrl, code, channel });
1203
+ await sendMagicLinkEmail(ctx, { email: to, magicUrl, code, channel }, cfg);
1204
1204
  }
1205
1205
  }
1206
1206
  }
@@ -210,7 +210,7 @@ export default class AuthRegistrationController {
210
210
  await cfg.mail.onEmailVerification({ email: data.email, verifyUrl, token: issued.token });
211
211
  }
212
212
  else {
213
- await sendEmailVerificationEmail(ctx, { email: data.email, verifyUrl });
213
+ await sendEmailVerificationEmail(ctx, { email: data.email, verifyUrl }, cfg);
214
214
  }
215
215
  }
216
216
  }
@@ -255,7 +255,7 @@ export default class AuthRegistrationController {
255
255
  await cfg.mail.onMagicLink({ email: to, magicUrl, token: issued.token, code });
256
256
  }
257
257
  else {
258
- await sendMagicLinkEmail(ctx, { email: to, magicUrl, code });
258
+ await sendMagicLinkEmail(ctx, { email: to, magicUrl, code }, cfg);
259
259
  }
260
260
  }
261
261
  }
@@ -279,7 +279,7 @@ export default class AuthRegistrationController {
279
279
  await cfg.mail.onMagicLink({ email: data.email, magicUrl, token, code, kind: 'signup' });
280
280
  }
281
281
  else {
282
- await sendMagicLinkEmail(ctx, { email: data.email, magicUrl, code });
282
+ await sendMagicLinkEmail(ctx, { email: data.email, magicUrl, code }, cfg);
283
283
  }
284
284
  }
285
285
  // Resposta uniforme: "enviamos um link" (não vaza existência da conta).
@@ -436,7 +436,7 @@ export default class AuthRegistrationController {
436
436
  await cfg.mail.onPasswordReset({ email: to, resetUrl: url, token: result.token });
437
437
  }
438
438
  else {
439
- await sendPasswordResetEmail(ctx, { email: to, resetUrl: url });
439
+ await sendPasswordResetEmail(ctx, { email: to, resetUrl: url }, cfg);
440
440
  }
441
441
  }
442
442
  return render(ctx, 'forgot', {
@@ -21,6 +21,11 @@ type MailService = any;
21
21
  * @internal
22
22
  */
23
23
  export declare function __setMailLoaderForTests(fn: (() => Promise<MailService | null>) | undefined): void;
24
+ /**
25
+ * Permite rearmar o warning "config não resolvido" (usado em testes).
26
+ * @internal
27
+ */
28
+ export declare function __resetMailConfigWarningForTests(): void;
24
29
  /**
25
30
  * Envia o e-mail de redefinição de senha pelo mailer default do host.
26
31
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
@@ -28,7 +33,7 @@ export declare function __setMailLoaderForTests(fn: (() => Promise<MailService |
28
33
  export declare function sendPasswordResetEmail(ctx: HttpContext, data: {
29
34
  email: string;
30
35
  resetUrl: string;
31
- }): Promise<void>;
36
+ }, cfg?: ResolvedServerConfig): Promise<void>;
32
37
  /**
33
38
  * Envia o e-mail de confirmação de TROCA de e-mail para o NOVO endereço.
34
39
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
@@ -36,7 +41,7 @@ export declare function sendPasswordResetEmail(ctx: HttpContext, data: {
36
41
  export declare function sendEmailChangeConfirmationEmail(ctx: HttpContext, data: {
37
42
  email: string;
38
43
  confirmUrl: string;
39
- }): Promise<void>;
44
+ }, cfg?: ResolvedServerConfig): Promise<void>;
40
45
  /**
41
46
  * Envia o e-mail de alerta de NOVO acesso à conta (login de um IP novo).
42
47
  * Best-effort: no fallback (sem mail) loga o evento; nunca lança.
@@ -65,7 +70,7 @@ export declare function sendMagicLinkEmail(ctx: HttpContext, data: {
65
70
  magicUrl: string;
66
71
  code?: string;
67
72
  channel?: 'code' | 'link';
68
- }): Promise<void>;
73
+ }, cfg?: ResolvedServerConfig): Promise<void>;
69
74
  /**
70
75
  * Envia um e-mail só com um código (sem link: quem digita é a pessoa, em outro lugar) pelo mailer
71
76
  * default do host, com o branding e o i18n do authkit. Os textos saem do catálogo pelas chaves
@@ -81,7 +86,7 @@ export declare function sendAuthkitCodeEmail(ctx: HttpContext, data: {
81
86
  /** Prefixo das chaves no catálogo (`'mail.poppy_sign_in'`). */
82
87
  messages: string;
83
88
  params?: Record<string, string | number>;
84
- }): Promise<void>;
89
+ }, cfg?: ResolvedServerConfig): Promise<void>;
85
90
  /**
86
91
  * Envia o link de CONFIRMAÇÃO DE IDENTIDADE (sudo) pelo mailer default do host.
87
92
  *
@@ -111,7 +116,7 @@ export declare function sendAuthkitCodeEmail(ctx: HttpContext, data: {
111
116
  export declare function sendSudoLinkEmail(ctx: HttpContext, data: {
112
117
  email: string;
113
118
  sudoUrl: string;
114
- }): Promise<boolean>;
119
+ }, cfg?: ResolvedServerConfig): Promise<boolean>;
115
120
  /**
116
121
  * Envia o e-mail de aviso de segurança ao e-mail ATUAL quando uma troca de
117
122
  * e-mail é solicitada ("Alguém pediu troca para X — se não foi você...").
@@ -149,7 +154,7 @@ export declare function sendSecurityNoticeEmail(ctx: HttpContext, cfg: ResolvedS
149
154
  export declare function sendOtpUnlockEmail(ctx: HttpContext, data: {
150
155
  email: string;
151
156
  unlockUrl: string;
152
- }): Promise<void>;
157
+ }, cfg?: ResolvedServerConfig): Promise<void>;
153
158
  /**
154
159
  * Sends the organization invitation email through the host default mailer.
155
160
  *
@@ -170,7 +175,7 @@ export declare function sendOrgInvitationEmail(ctx: HttpContext, data: {
170
175
  role: string;
171
176
  acceptUrl: string;
172
177
  token: string;
173
- }): Promise<void>;
178
+ }, cfg?: ResolvedServerConfig): Promise<void>;
174
179
  /**
175
180
  * Envia o e-mail de verificação pelo mailer default do host.
176
181
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
@@ -178,5 +183,5 @@ export declare function sendOrgInvitationEmail(ctx: HttpContext, data: {
178
183
  export declare function sendEmailVerificationEmail(ctx: HttpContext, data: {
179
184
  email: string;
180
185
  verifyUrl: string;
181
- }): Promise<void>;
186
+ }, cfg?: ResolvedServerConfig): Promise<void>;
182
187
  export {};
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
+ import { getBootedApp } from '../../services/booted_app.js';
9
10
  import { accountPath } from './account_paths.js';
10
11
  import { renderTransactionalEmail } from './email_templates.js';
11
12
  import { resolveMessages, translate } from './i18n.js';
@@ -38,76 +39,116 @@ export function __setMailLoaderForTests(fn) {
38
39
  mailServicePromise = undefined;
39
40
  }
40
41
  }
42
+ let warnedUnresolved = false;
41
43
  /**
42
- * Tenta descobrir o `from` default da config de mail do host. Se não houver,
43
- * retorna `undefined` (deixa o @adonisjs/mail aplicar o default da própria config).
44
+ * Permite rearmar o warning "config não resolvido" (usado em testes).
45
+ * @internal
44
46
  */
45
- function defaultFrom(ctx) {
46
- try {
47
- // Alcançamos as configs do HOST via o resolver do container. Esse formato é
48
- // específico do app (a lib não conhece o shape das configs nem `containerResolver.app`
49
- // é público), então tipamos apenas ESTE acesso como `unknown`/cast estreito — não o
50
- // `HttpContext` inteiro.
51
- const resolver = ctx.containerResolver;
52
- const get = resolver.app?.config?.get;
53
- // 1) `from` específico do authkit (defineConfig({ mail: { from } })) — PRIORIDADE:
54
- // permite que o auth use um remetente próprio, diferente do e-mail geral do app.
55
- const authkitFrom = get?.('authkit')?.mail?.from;
56
- if (authkitFrom)
57
- return authkitFrom;
58
- // 2) `from` global do host (config/mail.ts).
59
- const hostFrom = get?.('mail')?.from;
60
- if (hostFrom)
61
- return hostFrom;
62
- }
63
- catch {
64
- // sem config resolvível — deixa o @adonisjs/mail decidir.
47
+ export function __resetMailConfigWarningForTests() {
48
+ warnedUnresolved = false;
49
+ }
50
+ /**
51
+ * Encontra o config RESOLVIDO do authkit (`ResolvedServerConfig`).
52
+ *
53
+ * Por que não `ctx.containerResolver.app.config.get('authkit')`, como antes: o
54
+ * `ContainerResolver` do `@adonisjs/fold` não tem `.app` — então numa request real
55
+ * esse caminho dava `undefined` SEMPRE e todo e-mail default saía em `en` com a marca
56
+ * "AuthKit". E mesmo onde há um `app` (o ctx do SDK embedded), `config.get('authkit')`
57
+ * devolve o `ConfigProvider` preguiçoso do `defineConfig`, não o config resolvido —
58
+ * `.i18n`/`.branding`/`.mail` nunca existem nele.
59
+ *
60
+ * Ordem: o `cfg` que o chamador já tem → o binding `authkit.server` pelo container da
61
+ * request → pelo `app.container` do ctx → pelo app bootado capturado pelo provider.
62
+ */
63
+ async function resolveServerConfig(ctx, cfg) {
64
+ if (cfg)
65
+ return cfg;
66
+ const resolver = ctx?.containerResolver;
67
+ const containers = [
68
+ () => resolver,
69
+ () => resolver?.app?.container,
70
+ () => getBootedApp().container,
71
+ ];
72
+ for (const container of containers) {
73
+ try {
74
+ const maker = container();
75
+ if (typeof maker?.make !== 'function')
76
+ continue;
77
+ const service = (await maker.make('authkit.server'));
78
+ if (service?.config)
79
+ return service.config;
80
+ }
81
+ catch {
82
+ // tenta o próximo; a falha final é avisada em `resolveMailSettings`.
83
+ }
65
84
  }
66
85
  return undefined;
67
86
  }
68
87
  /**
69
- * Resolve a marca default a partir do `config/authkit.ts` (branding.default +
70
- * company). Usada para o cabeçalho/cor de acento/rodapé dos e-mails. Cai num
71
- * default neutro se a config não for resolvível.
88
+ * O `from` global do `config/mail.ts` do host (um objeto simples, não um provider),
89
+ * lido do `app` do ctx (SDK embedded) ou do app bootado. Ausente → `undefined`, e o
90
+ * `@adonisjs/mail` decide.
72
91
  */
73
- function resolveBrand(ctx) {
74
- try {
75
- const resolver = ctx.containerResolver;
76
- const branding = resolver.app?.config?.get?.('authkit')?.branding;
77
- if (branding) {
78
- return {
79
- appName: branding.default?.appName || branding.company || 'AuthKit',
80
- accent: branding.default?.accent,
81
- company: branding.company,
82
- };
92
+ function hostMailFrom(ctx) {
93
+ const apps = [
94
+ () => ctx?.containerResolver?.app,
95
+ () => getBootedApp(),
96
+ ];
97
+ for (const app of apps) {
98
+ try {
99
+ const from = app()?.config?.get?.('mail')?.from;
100
+ if (from)
101
+ return from;
102
+ }
103
+ catch {
104
+ // sem app/config de mail — tenta o próximo.
83
105
  }
84
106
  }
85
- catch {
86
- // sem config authkit resolvível — usa default neutro.
87
- }
88
- return { appName: 'AuthKit' };
107
+ return undefined;
89
108
  }
90
109
  /**
91
- * Resolve o catálogo de mensagens i18n a partir do `config/authkit.ts` (i18n).
92
- * Cai no default (`en`) se a config não for resolvível. Usado para localizar
93
- * os e-mails transacionais (assunto/cabeçalho/corpo).
110
+ * Marca, i18n e `from` dos e-mails default, a partir do config resolvido.
111
+ *
112
+ * Sem config resolvível cai num default neutro (`en`, "AuthKit") — mas NUNCA em
113
+ * silêncio: avisa uma vez por processo, porque um e-mail no idioma/marca errados é
114
+ * exatamente o tipo de bug que só aparece na caixa de entrada de um usuário.
94
115
  */
95
- function resolveMailMessages(ctx) {
96
- try {
97
- const resolver = ctx.containerResolver;
98
- const i18n = resolver.app?.config?.get?.('authkit')?.i18n;
99
- return { messages: resolveMessages(i18n), locale: i18n?.locale ?? 'en' };
100
- }
101
- catch {
102
- // sem config authkit resolvível — usa o default `en`.
103
- return { messages: resolveMessages(), locale: 'en' };
104
- }
116
+ async function resolveMailSettings(ctx, cfg) {
117
+ const resolved = await resolveServerConfig(ctx, cfg);
118
+ if (!resolved) {
119
+ if (!warnedUnresolved) {
120
+ warnedUnresolved = true;
121
+ const message = 'authkit: config do authkit não resolvido para os e-mails default — usando locale `en` e a marca "AuthKit". Registre o AuthkitServerProvider ou passe o config resolvido.';
122
+ if (typeof ctx?.logger?.warn === 'function')
123
+ ctx.logger.warn(message);
124
+ else
125
+ console.warn(message);
126
+ }
127
+ // `en` de propósito: aqui NÃO há config nenhum (nem o default `pt-BR` do `defineConfig`,
128
+ // que um host sem `i18n` recebe pelo `cfg.locale` acima). Sem saber de quem é o app,
129
+ // o catálogo base — o único com todas as chaves — é o fallback honesto.
130
+ return { brand: { appName: 'AuthKit' }, messages: resolveMessages(), locale: 'en' };
131
+ }
132
+ const branding = resolved.branding;
133
+ return {
134
+ brand: {
135
+ // `branding.default` é opcional na prática (hosts declaram só `company` + `clients`):
136
+ // sem ele, o nome do app é a `company`.
137
+ appName: branding?.default?.appName || branding?.company || 'AuthKit',
138
+ accent: branding?.default?.accent,
139
+ company: branding?.company,
140
+ },
141
+ messages: resolved.messages ?? resolveMessages(),
142
+ locale: resolved.locale ?? 'en',
143
+ // `from` próprio do authkit tem prioridade; senão o `from` global do `config/mail.ts`.
144
+ from: resolved.mail?.from ?? hostMailFrom(ctx),
145
+ };
105
146
  }
106
- async function sendEmail(ctx, to, content) {
147
+ async function sendEmail(settings, to, content) {
107
148
  const mail = await loadMail();
108
149
  if (!mail)
109
150
  return false;
110
- const from = defaultFrom(ctx);
151
+ const { from } = settings;
111
152
  await mail.send((message) => {
112
153
  if (from)
113
154
  message.from(from);
@@ -119,10 +160,10 @@ async function sendEmail(ctx, to, content) {
119
160
  * Envia o e-mail de redefinição de senha pelo mailer default do host.
120
161
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
121
162
  */
122
- export async function sendPasswordResetEmail(ctx, data) {
163
+ export async function sendPasswordResetEmail(ctx, data, cfg) {
123
164
  try {
124
- const brand = resolveBrand(ctx);
125
- const { messages: t, locale } = resolveMailMessages(ctx);
165
+ const settings = await resolveMailSettings(ctx, cfg);
166
+ const { brand, messages: t, locale } = settings;
126
167
  const content = renderTransactionalEmail({
127
168
  brand,
128
169
  locale,
@@ -134,7 +175,7 @@ export async function sendPasswordResetEmail(ctx, data) {
134
175
  ctaUrl: data.resetUrl,
135
176
  footnote: translate(t, 'mail.reset.fallback'),
136
177
  });
137
- const sent = await sendEmail(ctx, data.email, content);
178
+ const sent = await sendEmail(settings, data.email, content);
138
179
  if (!sent) {
139
180
  ctx.logger.info({ resetUrl: data.resetUrl, email: data.email }, 'authkit: link de redefinição de senha (dev — @adonisjs/mail ausente)');
140
181
  }
@@ -147,10 +188,10 @@ export async function sendPasswordResetEmail(ctx, data) {
147
188
  * Envia o e-mail de confirmação de TROCA de e-mail para o NOVO endereço.
148
189
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
149
190
  */
150
- export async function sendEmailChangeConfirmationEmail(ctx, data) {
191
+ export async function sendEmailChangeConfirmationEmail(ctx, data, cfg) {
151
192
  try {
152
- const brand = resolveBrand(ctx);
153
- const { messages: t, locale } = resolveMailMessages(ctx);
193
+ const settings = await resolveMailSettings(ctx, cfg);
194
+ const { brand, messages: t, locale } = settings;
154
195
  const content = renderTransactionalEmail({
155
196
  brand,
156
197
  locale,
@@ -162,7 +203,7 @@ export async function sendEmailChangeConfirmationEmail(ctx, data) {
162
203
  ctaUrl: data.confirmUrl,
163
204
  footnote: translate(t, 'mail.email_change.fallback'),
164
205
  });
165
- const sent = await sendEmail(ctx, data.email, content);
206
+ const sent = await sendEmail(settings, data.email, content);
166
207
  if (!sent) {
167
208
  ctx.logger.info({ confirmUrl: data.confirmUrl, email: data.email }, 'authkit: link de confirmação de troca de e-mail (dev — @adonisjs/mail ausente)');
168
209
  }
@@ -177,8 +218,8 @@ export async function sendEmailChangeConfirmationEmail(ctx, data) {
177
218
  */
178
219
  export async function sendNewLoginEmail(ctx, cfg, data) {
179
220
  try {
180
- const brand = resolveBrand(ctx);
181
- const { messages: t, locale } = resolveMailMessages(ctx);
221
+ const settings = await resolveMailSettings(ctx, cfg);
222
+ const { brand, messages: t, locale } = settings;
182
223
  const origin = authkitOrigin(cfg);
183
224
  const intro = [
184
225
  translate(t, 'mail.new_login.intro'),
@@ -196,7 +237,7 @@ export async function sendNewLoginEmail(ctx, cfg, data) {
196
237
  ctaUrl: `${origin}${accountPath('security')}`,
197
238
  footnote: translate(t, 'mail.new_login.fallback'),
198
239
  });
199
- const sent = await sendEmail(ctx, data.email, content);
240
+ const sent = await sendEmail(settings, data.email, content);
200
241
  if (!sent) {
201
242
  ctx.logger.info({ ip: data.ip, email: data.email }, 'authkit: alerta de novo acesso (dev — @adonisjs/mail ausente)');
202
243
  }
@@ -211,8 +252,8 @@ export async function sendNewLoginEmail(ctx, cfg, data) {
211
252
  */
212
253
  export async function sendNewDeviceLoginEmail(ctx, cfg, data) {
213
254
  try {
214
- const brand = resolveBrand(ctx);
215
- const { messages: t, locale } = resolveMailMessages(ctx);
255
+ const settings = await resolveMailSettings(ctx, cfg);
256
+ const { brand, messages: t, locale } = settings;
216
257
  const origin = authkitOrigin(cfg);
217
258
  const lines = [
218
259
  translate(t, 'mail.new_login.intro'),
@@ -233,7 +274,7 @@ export async function sendNewDeviceLoginEmail(ctx, cfg, data) {
233
274
  ctaUrl: `${origin}${accountPath('security')}`,
234
275
  footnote: translate(t, 'mail.new_login.fallback'),
235
276
  });
236
- const sent = await sendEmail(ctx, data.email, content);
277
+ const sent = await sendEmail(settings, data.email, content);
237
278
  if (!sent) {
238
279
  ctx.logger.info({ ip: data.ip, email: data.email, userAgent: data.userAgent }, 'authkit: alerta de novo dispositivo (dev — @adonisjs/mail ausente)');
239
280
  }
@@ -246,10 +287,10 @@ export async function sendNewDeviceLoginEmail(ctx, cfg, data) {
246
287
  * Envia o e-mail de magic link (login passwordless) pelo mailer default do host.
247
288
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
248
289
  */
249
- export async function sendMagicLinkEmail(ctx, data) {
290
+ export async function sendMagicLinkEmail(ctx, data, cfg) {
250
291
  try {
251
- const brand = resolveBrand(ctx);
252
- const { messages: t, locale } = resolveMailMessages(ctx);
292
+ const settings = await resolveMailSettings(ctx, cfg);
293
+ const { brand, messages: t, locale } = settings;
253
294
  // Login choose-first: o `channel` decide o que o e-mail SURFA (os dois tokens
254
295
  // continuam emitidos co-locados a montante — isto é só renderização).
255
296
  // - 'code' → e-mail SÓ com o código (sem botão/link), quando há código;
@@ -285,7 +326,7 @@ export async function sendMagicLinkEmail(ctx, data) {
285
326
  code: linkOnly ? undefined : data.code,
286
327
  codeLabel: translate(t, 'mail.magic_link.code_label'),
287
328
  });
288
- const sent = await sendEmail(ctx, data.email, content);
329
+ const sent = await sendEmail(settings, data.email, content);
289
330
  if (!sent) {
290
331
  ctx.logger.info({ magicUrl: data.magicUrl, email: data.email }, 'authkit: magic link de login (dev — @adonisjs/mail ausente)');
291
332
  }
@@ -303,10 +344,10 @@ export async function sendMagicLinkEmail(ctx, data) {
303
344
  *
304
345
  * Estável no 1.x (é o mailer das `personalAgents.extensions`).
305
346
  */
306
- export async function sendAuthkitCodeEmail(ctx, data) {
347
+ export async function sendAuthkitCodeEmail(ctx, data, cfg) {
307
348
  try {
308
- const brand = resolveBrand(ctx);
309
- const { messages: t, locale } = resolveMailMessages(ctx);
349
+ const settings = await resolveMailSettings(ctx, cfg);
350
+ const { brand, messages: t, locale } = settings;
310
351
  const key = (name) => `${data.messages}.${name}`;
311
352
  const content = renderTransactionalEmail({
312
353
  brand,
@@ -318,7 +359,7 @@ export async function sendAuthkitCodeEmail(ctx, data) {
318
359
  code: data.code,
319
360
  codeLabel: translate(t, key('code_label'), data.params),
320
361
  });
321
- const sent = await sendEmail(ctx, data.email, content);
362
+ const sent = await sendEmail(settings, data.email, content);
322
363
  if (!sent) {
323
364
  ctx.logger.info({ email: data.email, code: data.code, messages: data.messages, ...(data.params ?? {}) }, 'authkit: código por e-mail (dev — @adonisjs/mail ausente)');
324
365
  }
@@ -353,10 +394,10 @@ export async function sendAuthkitCodeEmail(ctx, data) {
353
394
  * apagar o pendente da sessão e recusar — flashar "link enviado" deixaria o
354
395
  * usuário esperando um e-mail que nunca vem, na tela que já é o gargalo dele.
355
396
  */
356
- export async function sendSudoLinkEmail(ctx, data) {
397
+ export async function sendSudoLinkEmail(ctx, data, cfg) {
357
398
  try {
358
- const brand = resolveBrand(ctx);
359
- const { messages: t, locale } = resolveMailMessages(ctx);
399
+ const settings = await resolveMailSettings(ctx, cfg);
400
+ const { brand, messages: t, locale } = settings;
360
401
  const content = renderTransactionalEmail({
361
402
  brand,
362
403
  locale,
@@ -368,7 +409,7 @@ export async function sendSudoLinkEmail(ctx, data) {
368
409
  ctaUrl: data.sudoUrl,
369
410
  footnote: translate(t, 'mail.sudo_link.fallback'),
370
411
  });
371
- const sent = await sendEmail(ctx, data.email, content);
412
+ const sent = await sendEmail(settings, data.email, content);
372
413
  if (!sent) {
373
414
  ctx.logger?.info({ sudoUrl: data.sudoUrl, email: data.email }, 'authkit: link de confirmação de identidade (dev — @adonisjs/mail ausente)');
374
415
  }
@@ -386,8 +427,8 @@ export async function sendSudoLinkEmail(ctx, data) {
386
427
  */
387
428
  export async function sendEmailChangeNoticeEmail(ctx, cfg, data) {
388
429
  try {
389
- const brand = resolveBrand(ctx);
390
- const { messages: t, locale } = resolveMailMessages(ctx);
430
+ const settings = await resolveMailSettings(ctx, cfg);
431
+ const { brand, messages: t, locale } = settings;
391
432
  const origin = authkitOrigin(cfg);
392
433
  const content = renderTransactionalEmail({
393
434
  brand,
@@ -400,7 +441,7 @@ export async function sendEmailChangeNoticeEmail(ctx, cfg, data) {
400
441
  ctaUrl: `${origin}${accountPath('security')}`,
401
442
  footnote: translate(t, 'mail.email_change_notice.fallback'),
402
443
  });
403
- const sent = await sendEmail(ctx, data.email, content);
444
+ const sent = await sendEmail(settings, data.email, content);
404
445
  if (!sent) {
405
446
  ctx.logger.info({ newEmail: data.newEmail, email: data.email }, 'authkit: aviso de troca de e-mail (dev — @adonisjs/mail ausente)');
406
447
  }
@@ -416,8 +457,8 @@ export async function sendEmailChangeNoticeEmail(ctx, cfg, data) {
416
457
  */
417
458
  export async function sendEmailChangedCompletedEmail(ctx, cfg, data) {
418
459
  try {
419
- const brand = resolveBrand(ctx);
420
- const { messages: t, locale } = resolveMailMessages(ctx);
460
+ const settings = await resolveMailSettings(ctx, cfg);
461
+ const { brand, messages: t, locale } = settings;
421
462
  const origin = authkitOrigin(cfg);
422
463
  const content = renderTransactionalEmail({
423
464
  brand,
@@ -433,7 +474,7 @@ export async function sendEmailChangedCompletedEmail(ctx, cfg, data) {
433
474
  ctaUrl: `${origin}${accountPath('security')}`,
434
475
  footnote: translate(t, 'mail.email_changed_completed.fallback'),
435
476
  });
436
- const sent = await sendEmail(ctx, data.oldEmail, content);
477
+ const sent = await sendEmail(settings, data.oldEmail, content);
437
478
  if (!sent) {
438
479
  ctx.logger.info({ oldEmail: data.oldEmail, newEmail: data.newEmail }, 'authkit: confirmação de troca de e-mail concluída (dev — @adonisjs/mail ausente)');
439
480
  }
@@ -449,8 +490,8 @@ export async function sendEmailChangedCompletedEmail(ctx, cfg, data) {
449
490
  */
450
491
  export async function sendSecurityNoticeEmail(ctx, cfg, data) {
451
492
  try {
452
- const brand = resolveBrand(ctx);
453
- const { messages: t, locale } = resolveMailMessages(ctx);
493
+ const settings = await resolveMailSettings(ctx, cfg);
494
+ const { brand, messages: t, locale } = settings;
454
495
  const origin = authkitOrigin(cfg);
455
496
  const kindKey = `mail.security_notice.kind_${data.kind}`;
456
497
  const kindLabel = translate(t, kindKey);
@@ -471,7 +512,7 @@ export async function sendSecurityNoticeEmail(ctx, cfg, data) {
471
512
  ctaUrl: `${origin}${accountPath('security')}`,
472
513
  footnote: translate(t, 'mail.security_notice.fallback'),
473
514
  });
474
- const sent = await sendEmail(ctx, data.email, content);
515
+ const sent = await sendEmail(settings, data.email, content);
475
516
  if (!sent) {
476
517
  ctx.logger.info({ kind: data.kind, email: data.email }, 'authkit: notificação de segurança (dev — @adonisjs/mail ausente)');
477
518
  }
@@ -484,10 +525,10 @@ export async function sendSecurityNoticeEmail(ctx, cfg, data) {
484
525
  * Envia o e-mail de desbloqueio do fator OTP (quando TOTP/recovery excede maxAttempts).
485
526
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
486
527
  */
487
- export async function sendOtpUnlockEmail(ctx, data) {
528
+ export async function sendOtpUnlockEmail(ctx, data, cfg) {
488
529
  try {
489
- const brand = resolveBrand(ctx);
490
- const { messages: t, locale } = resolveMailMessages(ctx);
530
+ const settings = await resolveMailSettings(ctx, cfg);
531
+ const { brand, messages: t, locale } = settings;
491
532
  const content = renderTransactionalEmail({
492
533
  brand,
493
534
  locale,
@@ -499,7 +540,7 @@ export async function sendOtpUnlockEmail(ctx, data) {
499
540
  ctaUrl: data.unlockUrl,
500
541
  footnote: translate(t, 'mail.otp_unlock.fallback'),
501
542
  });
502
- const sent = await sendEmail(ctx, data.email, content);
543
+ const sent = await sendEmail(settings, data.email, content);
503
544
  if (!sent) {
504
545
  ctx.logger.info({ unlockUrl: data.unlockUrl, email: data.email }, 'authkit: link de desbloqueio OTP (dev — @adonisjs/mail ausente)');
505
546
  }
@@ -520,10 +561,10 @@ export async function sendOtpUnlockEmail(ctx, data) {
520
561
  *
521
562
  * Best-effort: never throws — invitation creation must not depend on delivery.
522
563
  */
523
- export async function sendOrgInvitationEmail(ctx, data) {
564
+ export async function sendOrgInvitationEmail(ctx, data, cfg) {
524
565
  try {
525
- const brand = resolveBrand(ctx);
526
- const { messages: t, locale } = resolveMailMessages(ctx);
566
+ const settings = await resolveMailSettings(ctx, cfg);
567
+ const { brand, messages: t, locale } = settings;
527
568
  const content = renderTransactionalEmail({
528
569
  brand,
529
570
  locale,
@@ -535,7 +576,7 @@ export async function sendOrgInvitationEmail(ctx, data) {
535
576
  ctaUrl: data.acceptUrl,
536
577
  footnote: translate(t, 'mail.org_invitation.fallback'),
537
578
  });
538
- const sent = await sendEmail(ctx, data.email, content);
579
+ const sent = await sendEmail(settings, data.email, content);
539
580
  if (!sent) {
540
581
  ctx.logger?.info({
541
582
  acceptUrl: data.acceptUrl,
@@ -553,10 +594,10 @@ export async function sendOrgInvitationEmail(ctx, data) {
553
594
  * Envia o e-mail de verificação pelo mailer default do host.
554
595
  * Best-effort: no fallback (sem mail) loga o link; nunca lança.
555
596
  */
556
- export async function sendEmailVerificationEmail(ctx, data) {
597
+ export async function sendEmailVerificationEmail(ctx, data, cfg) {
557
598
  try {
558
- const brand = resolveBrand(ctx);
559
- const { messages: t, locale } = resolveMailMessages(ctx);
599
+ const settings = await resolveMailSettings(ctx, cfg);
600
+ const { brand, messages: t, locale } = settings;
560
601
  const content = renderTransactionalEmail({
561
602
  brand,
562
603
  locale,
@@ -567,7 +608,7 @@ export async function sendEmailVerificationEmail(ctx, data) {
567
608
  ctaLabel: translate(t, 'mail.verify.cta'),
568
609
  ctaUrl: data.verifyUrl,
569
610
  });
570
- const sent = await sendEmail(ctx, data.email, content);
611
+ const sent = await sendEmail(settings, data.email, content);
571
612
  if (!sent) {
572
613
  ctx.logger.info({ verifyUrl: data.verifyUrl, email: data.email }, 'authkit: link de verificação de e-mail (dev — @adonisjs/mail ausente)');
573
614
  }
@@ -154,7 +154,7 @@ export function magicLink() {
154
154
  delivered = true;
155
155
  }
156
156
  else {
157
- delivered = await sendSudoLinkEmail(ctx, { email: c.account.email, sudoUrl });
157
+ delivered = await sendSudoLinkEmail(ctx, { email: c.account.email, sudoUrl }, c.cfg);
158
158
  }
159
159
  }
160
160
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "1.0.0-rc.13",
3
+ "version": "1.0.0-rc.14",
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",