@adonis-agora/authkit-server 0.87.0 → 0.88.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.
- package/build/host/views/signup.edge +16 -0
- package/build/src/define_config.d.ts +6 -4
- package/build/src/host/controllers/interaction_controller.js +32 -4
- package/build/src/host/controllers/registration_controller.js +30 -7
- package/build/src/host/signup_code.d.ts +41 -0
- package/build/src/host/signup_code.js +56 -0
- package/package.json +3 -3
|
@@ -66,6 +66,22 @@
|
|
|
66
66
|
{{ t('signup.have_account') }}
|
|
67
67
|
</a>
|
|
68
68
|
</form>
|
|
69
|
+
|
|
70
|
+
{{-- Código do mesmo e-mail: conclui o cadastro neste navegador. --}}
|
|
71
|
+
@if(magicLinkSent && otpEnabled)
|
|
72
|
+
<form method="POST" action="/auth/interaction/{{ uid }}/otp-verify" class="mt-4">
|
|
73
|
+
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
74
|
+
<input type="hidden" name="channel" value="code">
|
|
75
|
+
<label for="otp-code" class="block text-sm font-medium text-gray-700">{{ t('login.otp_label') }}</label>
|
|
76
|
+
<input id="otp-code" name="code" type="text" inputmode="numeric" autocomplete="one-time-code"
|
|
77
|
+
pattern="[0-9]*" placeholder="{{ t('login.otp_placeholder') }}"
|
|
78
|
+
class="mt-2 w-full rounded-lg border border-gray-300 px-3 py-2.5 text-center text-lg tracking-[0.4em] font-mono">
|
|
79
|
+
<button type="submit"
|
|
80
|
+
class="mt-4 w-full rounded-lg bg-gray-900 py-2.5 text-sm font-semibold text-white transition hover:opacity-90">
|
|
81
|
+
{{ t('login.otp_submit') }}
|
|
82
|
+
</button>
|
|
83
|
+
</form>
|
|
84
|
+
@end
|
|
69
85
|
@end
|
|
70
86
|
</div>
|
|
71
87
|
</body></html>
|
|
@@ -76,8 +76,8 @@ export interface MailHooks {
|
|
|
76
76
|
magicUrl: string;
|
|
77
77
|
token: string;
|
|
78
78
|
/**
|
|
79
|
-
* Código OTP de login, presente APENAS quando
|
|
80
|
-
* ligado. O host pode montar o próprio e-mail com link E código. Ausente no
|
|
79
|
+
* Código OTP, de login ou de cadastro (`kind: 'signup'`), presente APENAS quando
|
|
80
|
+
* `login.otp.enabled` está ligado. O host pode montar o próprio e-mail com link E código. Ausente no
|
|
81
81
|
* fluxo só-magic-link (back-compat).
|
|
82
82
|
*/
|
|
83
83
|
code?: string;
|
|
@@ -90,7 +90,7 @@ export interface MailHooks {
|
|
|
90
90
|
channel?: 'code' | 'link';
|
|
91
91
|
/**
|
|
92
92
|
* `'signup'`: link de confirmação do cadastro passwordless (a conta é criada
|
|
93
|
-
* ao abri-lo). Ausente = login por magic link. O host pode trocar o texto do
|
|
93
|
+
* ao abri-lo, ou ao confirmar o `code`). Ausente = login por magic link. O host pode trocar o texto do
|
|
94
94
|
* e-mail ("confirme seu cadastro"); ignorar o campo continua funcionando.
|
|
95
95
|
*/
|
|
96
96
|
kind?: 'signup';
|
|
@@ -523,7 +523,9 @@ export interface PasswordlessConfigInput {
|
|
|
523
523
|
* senha) e envia um link de confirmação (`onMagicLink` com `kind: 'signup'`).
|
|
524
524
|
* A conta só é criada quando o dono do e-mail abre o link: nasce com o e-mail
|
|
525
525
|
* verificado e uma senha aleatória inutilizável, e o login segue pelo magic
|
|
526
|
-
* link. E-mail que já tem conta recebe um magic link de login.
|
|
526
|
+
* link. E-mail que já tem conta recebe um magic link de login. Com `login.otp`
|
|
527
|
+
* ligado, os dois casos também levam um código para concluir no mesmo navegador.
|
|
528
|
+
* Default: false.
|
|
527
529
|
* Exige que o accountStore implemente {@link MagicLinkCapability} e o serviço de
|
|
528
530
|
* encryption do app (APP_KEY), que assina o link; sem a capacidade o cadastro
|
|
529
531
|
* passwordless fica indisponível e o fluxo de senha segue valendo.
|
|
@@ -18,6 +18,7 @@ import { authkitOrigin } from '../origin.js';
|
|
|
18
18
|
import { createOtpLockout, generateOtpUnlockToken, rawToDbOtpUnlockToken, resolveEffectiveOtpLockout, } from '../otp_lockout.js';
|
|
19
19
|
import { resolveRuntimeSettingsOrNoop } from '../runtime_settings.js';
|
|
20
20
|
import { resolveEffectiveAuthMethods, resolveEffectiveMaintenanceMode, resolveEffectiveRegistration, resolveEffectiveSessionPolicy, } from '../runtime_toggles.js';
|
|
21
|
+
import { LOGIN_EMAIL_SESSION_KEY, pendingSignupCode, verifySignupCode } from '../signup_code.js';
|
|
21
22
|
import { SOCIAL_PENDING_KEY, SOCIAL_PENDING_TTL_MS, } from '../social_pending.js';
|
|
22
23
|
import { buildTrustedDevicePayload, isTrustedDeviceValid, TRUSTED_DEVICE_COOKIE, } from '../trusted_device.js';
|
|
23
24
|
import { normalizeUserLoginMethods, resolveEffectiveUserLoginMethods, } from '../user_login_methods.js';
|
|
@@ -58,7 +59,7 @@ function accountStatusErrorKey(reason) {
|
|
|
58
59
|
* `toLowerCase`). É o que a tela SEMPRE mostra — nunca o endereço gravado —,
|
|
59
60
|
* para que a canonicalização não vire um oráculo de "esta conta existe".
|
|
60
61
|
*/
|
|
61
|
-
const SESSION_KEY =
|
|
62
|
+
const SESSION_KEY = LOGIN_EMAIL_SESSION_KEY;
|
|
62
63
|
/** accountId aguardando o 2º fator depois da senha verificada. */
|
|
63
64
|
const MFA_PENDING_KEY = 'authkit_mfa_pending';
|
|
64
65
|
/**
|
|
@@ -1397,13 +1398,11 @@ export default class AuthInteractionController {
|
|
|
1397
1398
|
return ctx.response.redirect(`/auth/interaction/${uid}`);
|
|
1398
1399
|
}
|
|
1399
1400
|
const code = String(ctx.request.input('code', '') ?? '').trim();
|
|
1401
|
+
const pendingSignup = pendingSignupCode(ctx, uid, email);
|
|
1400
1402
|
const brand = brandFor(cfg.branding, clientId ?? undefined, undefined);
|
|
1401
1403
|
// Mantém a sub-view do seletor no re-render de erro (o form de código pode
|
|
1402
1404
|
// POSTar `channel=code`). Ausente = both (histórico).
|
|
1403
1405
|
const channel = normalizeLoginChannel(ctx.request.input('channel'));
|
|
1404
|
-
const result = await cfg.accountStore.verifyLoginCode(email, uid, code, {
|
|
1405
|
-
maxAttempts: cfg.login.otp.maxAttempts,
|
|
1406
|
-
});
|
|
1407
1406
|
// Re-render da tela "link enviado" com o campo de código + erro localizado.
|
|
1408
1407
|
// `otpEnabled` vem do spread (scoped — OTP é extensão do magic link, e a
|
|
1409
1408
|
// preferência do usuário que desligou magic link já foi barrada acima).
|
|
@@ -1419,6 +1418,35 @@ export default class AuthInteractionController {
|
|
|
1419
1418
|
magicChannel: magicChannelProp(channel),
|
|
1420
1419
|
otpError: translate(cfg.messages, messageKey),
|
|
1421
1420
|
});
|
|
1421
|
+
// Cadastro sem conta ainda: o código certo leva ao mesmo link de confirmação do e-mail.
|
|
1422
|
+
if (pendingSignup) {
|
|
1423
|
+
const signupResult = verifySignupCode(ctx, pendingSignup, code, cfg.login.otp.maxAttempts);
|
|
1424
|
+
if (signupResult === 'ok') {
|
|
1425
|
+
forgetLoginEmail(ctx);
|
|
1426
|
+
const confirmUrl = `/auth/interaction/${uid}/signup/confirm?token=${encodeURIComponent(pendingSignup.token)}`;
|
|
1427
|
+
return ctx.response.redirect().status(303).toPath(confirmUrl);
|
|
1428
|
+
}
|
|
1429
|
+
const signupAudit = {
|
|
1430
|
+
email,
|
|
1431
|
+
ip,
|
|
1432
|
+
clientId,
|
|
1433
|
+
metadata: { stage: 'signup', reason: signupResult },
|
|
1434
|
+
};
|
|
1435
|
+
if (signupResult === 'locked') {
|
|
1436
|
+
await cfg.audit?.record({ type: 'login.otp_invalidated', ...signupAudit });
|
|
1437
|
+
}
|
|
1438
|
+
else {
|
|
1439
|
+
await cfg.audit?.record({ type: 'login.otp_failed', ...signupAudit });
|
|
1440
|
+
}
|
|
1441
|
+
return renderOtpError(signupResult === 'locked'
|
|
1442
|
+
? 'login.otp_locked'
|
|
1443
|
+
: signupResult === 'expired'
|
|
1444
|
+
? 'login.otp_expired'
|
|
1445
|
+
: 'login.otp_invalid');
|
|
1446
|
+
}
|
|
1447
|
+
const result = await cfg.accountStore.verifyLoginCode(email, uid, code, {
|
|
1448
|
+
maxAttempts: cfg.login.otp.maxAttempts,
|
|
1449
|
+
});
|
|
1422
1450
|
if (result.status === 'ok') {
|
|
1423
1451
|
// E-mail não verificado (LGPD): mesmo com código válido, não materializa a
|
|
1424
1452
|
// sessão se a política exige verificação. Espelha o magicLinkConsume.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import { supportsAccountDeletion, supportsMagicLink } from '../../accounts/account_store.js';
|
|
3
|
+
import { supportsAccountDeletion, supportsMagicLink, supportsOtpLogin, } from '../../accounts/account_store.js';
|
|
4
4
|
import { PasswordPolicyError } from '../../password/password_manager.js';
|
|
5
5
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
6
6
|
import { guardBotProtection, resolveEffectiveBotProtection } from '../bot_protection.js';
|
|
@@ -12,6 +12,7 @@ import { authkitOrigin } from '../origin.js';
|
|
|
12
12
|
import { RuntimeSettings, resolveRuntimeSettings } from '../runtime_settings.js';
|
|
13
13
|
import { resolveEffectiveAuthMethods, resolveEffectiveMaintenanceMode, resolveEffectiveRegistration, } from '../runtime_toggles.js';
|
|
14
14
|
import { dispatchSecurityNotice } from '../security_notice_service.js';
|
|
15
|
+
import { LOGIN_EMAIL_SESSION_KEY, SIGNUP_CODE_SESSION_KEY, startSignupCode, } from '../signup_code.js';
|
|
15
16
|
import { issueSignupToken, readSignupToken } from '../signup_token.js';
|
|
16
17
|
import { forgotPasswordValidator, passwordlessSignupValidator, resetPasswordValidator, signupValidator, } from '../validators.js';
|
|
17
18
|
/**
|
|
@@ -221,7 +222,8 @@ export default class AuthRegistrationController {
|
|
|
221
222
|
* Cadastro passwordless: valida e-mail + nome e envia um link. Conta existente
|
|
222
223
|
* recebe um magic link de login; e-mail novo recebe o link de confirmação do
|
|
223
224
|
* cadastro (`signupConfirm`), e só então a conta é criada. Sempre responde
|
|
224
|
-
* "link enviado" (anti-enumeração), exista a conta ou não.
|
|
225
|
+
* "link enviado" (anti-enumeração), exista a conta ou não. Com `login.otp` ligado,
|
|
226
|
+
* os dois casos também levam um código de digitar (`otpVerify`).
|
|
225
227
|
*/
|
|
226
228
|
async #passwordlessSignup(ctx, deps) {
|
|
227
229
|
const { cfg, brand } = deps;
|
|
@@ -230,19 +232,30 @@ export default class AuthRegistrationController {
|
|
|
230
232
|
const uid = ctx.request.param('uid');
|
|
231
233
|
const data = await ctx.request.validateUsing(passwordlessSignupValidator);
|
|
232
234
|
const origin = authkitOrigin(cfg);
|
|
235
|
+
// Com OTP ligado, o e-mail leva também um código, para concluir neste navegador.
|
|
236
|
+
const otpEnabled = cfg.login?.otp?.enabled === true && supportsOtpLogin(accountStore);
|
|
233
237
|
const existing = await accountStore.findByEmail(data.email);
|
|
238
|
+
ctx.session.forget(SIGNUP_CODE_SESSION_KEY);
|
|
239
|
+
if (otpEnabled)
|
|
240
|
+
ctx.session.put(LOGIN_EMAIL_SESSION_KEY, data.email);
|
|
234
241
|
if (existing) {
|
|
235
242
|
// Conta já existe: o "cadastro" vira um login por magic link.
|
|
236
243
|
// `data.email` já é a forma normalizada, a mesma do login.
|
|
237
|
-
const issued =
|
|
244
|
+
const issued = otpEnabled
|
|
245
|
+
? await accountStore.issueMagicLinkWithCode(data.email, uid, {
|
|
246
|
+
digits: cfg.login.otp.digits,
|
|
247
|
+
ttlMinutes: cfg.login.otp.ttlMinutes,
|
|
248
|
+
})
|
|
249
|
+
: await accountStore.issueMagicLinkToken(data.email);
|
|
238
250
|
if (issued) {
|
|
251
|
+
const code = 'code' in issued ? issued.code : undefined;
|
|
239
252
|
const magicUrl = `${origin}/auth/interaction/${uid}/magic?token=${encodeURIComponent(issued.token)}`;
|
|
240
253
|
const to = issued.account.email;
|
|
241
254
|
if (cfg.mail?.onMagicLink) {
|
|
242
|
-
await cfg.mail.onMagicLink({ email: to, magicUrl, token: issued.token });
|
|
255
|
+
await cfg.mail.onMagicLink({ email: to, magicUrl, token: issued.token, code });
|
|
243
256
|
}
|
|
244
257
|
else {
|
|
245
|
-
await sendMagicLinkEmail(ctx, { email: to, magicUrl });
|
|
258
|
+
await sendMagicLinkEmail(ctx, { email: to, magicUrl, code });
|
|
246
259
|
}
|
|
247
260
|
}
|
|
248
261
|
}
|
|
@@ -253,11 +266,20 @@ export default class AuthRegistrationController {
|
|
|
253
266
|
// e-mail de outra pessoa não cria nada.
|
|
254
267
|
const token = await issueSignupToken({ email: data.email, fullName: data.fullName ?? null });
|
|
255
268
|
const magicUrl = `${origin}/auth/interaction/${uid}/signup/confirm?token=${encodeURIComponent(token)}`;
|
|
269
|
+
const code = otpEnabled
|
|
270
|
+
? startSignupCode(ctx, {
|
|
271
|
+
uid,
|
|
272
|
+
email: data.email,
|
|
273
|
+
token,
|
|
274
|
+
digits: cfg.login.otp.digits,
|
|
275
|
+
ttlMinutes: cfg.login.otp.ttlMinutes,
|
|
276
|
+
})
|
|
277
|
+
: undefined;
|
|
256
278
|
if (cfg.mail?.onMagicLink) {
|
|
257
|
-
await cfg.mail.onMagicLink({ email: data.email, magicUrl, token, kind: 'signup' });
|
|
279
|
+
await cfg.mail.onMagicLink({ email: data.email, magicUrl, token, code, kind: 'signup' });
|
|
258
280
|
}
|
|
259
281
|
else {
|
|
260
|
-
await sendMagicLinkEmail(ctx, { email: data.email, magicUrl });
|
|
282
|
+
await sendMagicLinkEmail(ctx, { email: data.email, magicUrl, code });
|
|
261
283
|
}
|
|
262
284
|
}
|
|
263
285
|
// Resposta uniforme: "enviamos um link" (não vaza existência da conta).
|
|
@@ -266,6 +288,7 @@ export default class AuthRegistrationController {
|
|
|
266
288
|
csrfToken: ctx.request.csrfToken,
|
|
267
289
|
brand,
|
|
268
290
|
magicLinkSent: true,
|
|
291
|
+
otpEnabled,
|
|
269
292
|
});
|
|
270
293
|
}
|
|
271
294
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Código digitado do cadastro passwordless.
|
|
4
|
+
*
|
|
5
|
+
* Quem abre o link do e-mail noutro navegador termina logado longe de onde começou (um convite
|
|
6
|
+
* aberto dentro do WhatsApp, por exemplo). Com o código, a pessoa conclui no mesmo navegador e na
|
|
7
|
+
* mesma interaction. A conta ainda não existe, então não há slot no store: o pendente fica na sessão
|
|
8
|
+
* deste navegador, e o código correto só libera o mesmo link de confirmação que foi por e-mail.
|
|
9
|
+
*
|
|
10
|
+
* As tentativas ficam na sessão. Num driver de cookie dá para repetir um cookie antigo e zerá-las,
|
|
11
|
+
* e o limite que sobra é o throttle da rota `otp-verify`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SIGNUP_CODE_SESSION_KEY = "authkit_signup_code";
|
|
14
|
+
/** Onde o login guarda o e-mail da interaction: é o que o `otp-verify` lê. */
|
|
15
|
+
export declare const LOGIN_EMAIL_SESSION_KEY = "authkit_login_email";
|
|
16
|
+
export interface PendingSignupCode {
|
|
17
|
+
uid: string;
|
|
18
|
+
email: string;
|
|
19
|
+
/** O token do link de confirmação (já cifrado). */
|
|
20
|
+
token: string;
|
|
21
|
+
codeHash: string;
|
|
22
|
+
/** Epoch em ms. */
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
attempts: number;
|
|
25
|
+
}
|
|
26
|
+
export type SignupCodeResult = 'ok' | 'invalid' | 'expired' | 'locked';
|
|
27
|
+
/** Gera o código e o guarda na sessão, presos à interaction e ao token. Devolve o código. */
|
|
28
|
+
export declare function startSignupCode(ctx: HttpContext, input: {
|
|
29
|
+
uid: string;
|
|
30
|
+
email: string;
|
|
31
|
+
token: string;
|
|
32
|
+
digits: number;
|
|
33
|
+
ttlMinutes: number;
|
|
34
|
+
}): string;
|
|
35
|
+
/**
|
|
36
|
+
* O cadastro pendente desta interaction. Se a pessoa voltou e digitou outro e-mail no login, o
|
|
37
|
+
* e-mail da sessão muda e o código que vale é o do login.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pendingSignupCode(ctx: HttpContext, uid: string, sessionEmail: string | undefined): PendingSignupCode | null;
|
|
40
|
+
/** Confere o código e conta a tentativa. Ao travar, nem o código certo entra; o link continua. */
|
|
41
|
+
export declare function verifySignupCode(ctx: HttpContext, pending: PendingSignupCode, code: string, maxAttempts: number): SignupCodeResult;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash, randomInt, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Código digitado do cadastro passwordless.
|
|
4
|
+
*
|
|
5
|
+
* Quem abre o link do e-mail noutro navegador termina logado longe de onde começou (um convite
|
|
6
|
+
* aberto dentro do WhatsApp, por exemplo). Com o código, a pessoa conclui no mesmo navegador e na
|
|
7
|
+
* mesma interaction. A conta ainda não existe, então não há slot no store: o pendente fica na sessão
|
|
8
|
+
* deste navegador, e o código correto só libera o mesmo link de confirmação que foi por e-mail.
|
|
9
|
+
*
|
|
10
|
+
* As tentativas ficam na sessão. Num driver de cookie dá para repetir um cookie antigo e zerá-las,
|
|
11
|
+
* e o limite que sobra é o throttle da rota `otp-verify`.
|
|
12
|
+
*/
|
|
13
|
+
export const SIGNUP_CODE_SESSION_KEY = 'authkit_signup_code';
|
|
14
|
+
/** Onde o login guarda o e-mail da interaction: é o que o `otp-verify` lê. */
|
|
15
|
+
export const LOGIN_EMAIL_SESSION_KEY = 'authkit_login_email';
|
|
16
|
+
function hashCode(uid, token, code) {
|
|
17
|
+
return createHash('sha256').update(`${uid}\u0000${token}\u0000${code}`).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
/** Gera o código e o guarda na sessão, presos à interaction e ao token. Devolve o código. */
|
|
20
|
+
export function startSignupCode(ctx, input) {
|
|
21
|
+
const code = String(randomInt(0, 10 ** input.digits)).padStart(input.digits, '0');
|
|
22
|
+
const pending = {
|
|
23
|
+
uid: input.uid,
|
|
24
|
+
email: input.email,
|
|
25
|
+
token: input.token,
|
|
26
|
+
codeHash: hashCode(input.uid, input.token, code),
|
|
27
|
+
expiresAt: Date.now() + input.ttlMinutes * 60_000,
|
|
28
|
+
attempts: 0,
|
|
29
|
+
};
|
|
30
|
+
ctx.session.put(SIGNUP_CODE_SESSION_KEY, pending);
|
|
31
|
+
return code;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* O cadastro pendente desta interaction. Se a pessoa voltou e digitou outro e-mail no login, o
|
|
35
|
+
* e-mail da sessão muda e o código que vale é o do login.
|
|
36
|
+
*/
|
|
37
|
+
export function pendingSignupCode(ctx, uid, sessionEmail) {
|
|
38
|
+
const pending = ctx.session.get(SIGNUP_CODE_SESSION_KEY);
|
|
39
|
+
return pending?.uid === uid && pending.email === sessionEmail ? pending : null;
|
|
40
|
+
}
|
|
41
|
+
/** Confere o código e conta a tentativa. Ao travar, nem o código certo entra; o link continua. */
|
|
42
|
+
export function verifySignupCode(ctx, pending, code, maxAttempts) {
|
|
43
|
+
if (pending.attempts >= maxAttempts)
|
|
44
|
+
return 'locked';
|
|
45
|
+
if (Date.now() >= pending.expiresAt)
|
|
46
|
+
return 'expired';
|
|
47
|
+
const expected = Buffer.from(pending.codeHash);
|
|
48
|
+
const given = Buffer.from(hashCode(pending.uid, pending.token, code));
|
|
49
|
+
if (expected.length === given.length && timingSafeEqual(expected, given)) {
|
|
50
|
+
ctx.session.forget(SIGNUP_CODE_SESSION_KEY);
|
|
51
|
+
return 'ok';
|
|
52
|
+
}
|
|
53
|
+
const attempts = pending.attempts + 1;
|
|
54
|
+
ctx.session.put(SIGNUP_CODE_SESSION_KEY, { ...pending, attempts });
|
|
55
|
+
return attempts >= maxAttempts ? 'locked' : 'invalid';
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.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",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"@adonis-agora/authkit-core": "0.10.1",
|
|
109
109
|
"@simplewebauthn/browser": "14.0.0",
|
|
110
110
|
"@simplewebauthn/server": "14.0.3",
|
|
111
|
-
"jose": "6.2.
|
|
111
|
+
"jose": "6.2.14",
|
|
112
112
|
"koa": "3.2.1",
|
|
113
113
|
"koa-mount": "4.2.0",
|
|
114
114
|
"oidc-provider": "9.12.2",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
},
|
|
118
118
|
"devDependencies": {
|
|
119
119
|
"@adonis-agora/authkit-react": "0.26.0",
|
|
120
|
-
"@adonis-agora/durable": "0.
|
|
120
|
+
"@adonis-agora/durable": "0.56.7",
|
|
121
121
|
"@adonis-agora/telescope": "0.23.0",
|
|
122
122
|
"@adonisjs/ally": "6.3.2",
|
|
123
123
|
"@adonisjs/auth": "10.1.0",
|