@dudousxd/adonis-authkit-server 0.3.0 → 0.5.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/commands/commands.json +28 -0
- package/build/commands/doctor.d.ts +10 -0
- package/build/commands/doctor.js +66 -0
- package/build/commands/rotate_keys.d.ts +10 -0
- package/build/commands/rotate_keys.js +53 -0
- package/build/host/views/account/email-confirmed.edge +15 -0
- package/build/host/views/account/security.edge +83 -0
- package/build/host/views/account/tokens.edge +7 -4
- package/build/host/views/admin/sessions.edge +89 -0
- package/build/host/views/admin/users.edge +1 -0
- package/build/host/views/mfa-challenge.edge +29 -23
- package/build/index.d.ts +5 -4
- package/build/index.js +3 -3
- package/build/src/accounts/account_store.d.ts +46 -1
- package/build/src/accounts/account_store.js +4 -0
- package/build/src/accounts/lucid_store/core.d.ts +5 -4
- package/build/src/accounts/lucid_store/core.js +67 -2
- package/build/src/adapters/adapter_contract.d.ts +17 -0
- package/build/src/adapters/database_adapter.d.ts +9 -5
- package/build/src/adapters/database_adapter.js +13 -6
- package/build/src/adapters/redis_adapter.d.ts +11 -5
- package/build/src/adapters/redis_adapter.js +16 -7
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/define_config.d.ts +102 -0
- package/build/src/define_config.js +46 -3
- package/build/src/doctor/checks.d.ts +51 -0
- package/build/src/doctor/checks.js +231 -0
- package/build/src/host/admin_clients_service.js +12 -5
- package/build/src/host/admin_sessions_service.d.ts +63 -0
- package/build/src/host/admin_sessions_service.js +127 -0
- package/build/src/host/controllers/account_mfa_controller.js +6 -2
- package/build/src/host/controllers/account_security_controller.d.ts +16 -0
- package/build/src/host/controllers/account_security_controller.js +119 -0
- package/build/src/host/controllers/account_session_controller.js +2 -1
- package/build/src/host/controllers/admin/admin_sessions_controller.d.ts +14 -0
- package/build/src/host/controllers/admin/admin_sessions_controller.js +64 -0
- package/build/src/host/controllers/interaction_controller.d.ts +11 -0
- package/build/src/host/controllers/interaction_controller.js +55 -12
- package/build/src/host/default_mailer.d.ts +17 -0
- package/build/src/host/default_mailer.js +94 -9
- package/build/src/host/email_templates.d.ts +4 -0
- package/build/src/host/email_templates.js +5 -2
- package/build/src/host/i18n.d.ts +358 -11
- package/build/src/host/i18n.js +393 -12
- package/build/src/host/login_notify.d.ts +20 -0
- package/build/src/host/login_notify.js +71 -0
- package/build/src/host/register_auth_host.js +12 -0
- package/build/src/host/validators.d.ts +32 -0
- package/build/src/host/validators.js +14 -0
- package/build/src/keys/keystore.d.ts +43 -0
- package/build/src/keys/keystore.js +74 -0
- package/build/src/observability/metrics_controller.js +4 -4
- package/build/src/provider/build_provider.js +23 -0
- package/build/src/provider/device_sources.d.ts +6 -0
- package/build/src/provider/device_sources.js +65 -0
- package/build/src/provider/interaction_actions.d.ts +6 -1
- package/build/src/provider/interaction_actions.js +9 -2
- package/package.json +2 -2
|
@@ -11,3 +11,17 @@ export const resetPasswordValidator = vine.compile(vine.object({
|
|
|
11
11
|
token: vine.string().trim().minLength(1),
|
|
12
12
|
password: vine.string().minLength(8).maxLength(255),
|
|
13
13
|
}));
|
|
14
|
+
/**
|
|
15
|
+
* Troca de senha no console de conta. A regra da nova senha espelha o
|
|
16
|
+
* signupValidator (min 8, max 255); a senha atual é confirmada à parte via
|
|
17
|
+
* verifyCredentials.
|
|
18
|
+
*/
|
|
19
|
+
export const changePasswordValidator = vine.compile(vine.object({
|
|
20
|
+
currentPassword: vine.string().minLength(1),
|
|
21
|
+
newPassword: vine.string().minLength(8).maxLength(255),
|
|
22
|
+
}));
|
|
23
|
+
/** Troca de e-mail no console de conta: senha atual + o novo e-mail. */
|
|
24
|
+
export const changeEmailValidator = vine.compile(vine.object({
|
|
25
|
+
currentPassword: vine.string().minLength(1),
|
|
26
|
+
newEmail: vine.string().trim().email().normalizeEmail(),
|
|
27
|
+
}));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SigningAlg } from './jwks_manager.js';
|
|
2
|
+
/**
|
|
3
|
+
* Keystore JSON em arquivo para o modo `jwks: { source: 'managed', store }`.
|
|
4
|
+
*
|
|
5
|
+
* O modo `managed` "puro" gera UMA chave efêmera por boot (em
|
|
6
|
+
* {@link generateJwks}) — não persiste, então rotacionar não faz sentido: a cada
|
|
7
|
+
* restart o kid muda e tokens antigos param de validar. Para suportar rotação de
|
|
8
|
+
* verdade, este keystore persiste o JWKS PRIVADO em um arquivo. A rotação gera
|
|
9
|
+
* um novo par (novo kid) e mantém as N chaves mais recentes; o JWKS público
|
|
10
|
+
* servido inclui todas (as antigas continuam validando), e a PRIMEIRA chave do
|
|
11
|
+
* array é a de assinatura corrente (o oidc-provider assina com a primeira chave
|
|
12
|
+
* compatível).
|
|
13
|
+
*/
|
|
14
|
+
/** Estrutura persistida: JWKS privado (chaves com `d`). */
|
|
15
|
+
export interface PersistedKeystore {
|
|
16
|
+
keys: Record<string, any>[];
|
|
17
|
+
}
|
|
18
|
+
/** Gera uma chave de assinatura privada como JWK (com use/alg/kid). */
|
|
19
|
+
export declare function generateSigningJwk(alg: SigningAlg): Promise<Record<string, any>>;
|
|
20
|
+
/** Lê o keystore do arquivo, ou null se não existir/for inválido. */
|
|
21
|
+
export declare function readKeystore(path: string): PersistedKeystore | null;
|
|
22
|
+
/** Escreve o keystore no arquivo (cria o diretório se preciso). */
|
|
23
|
+
export declare function writeKeystore(path: string, store: PersistedKeystore): void;
|
|
24
|
+
/**
|
|
25
|
+
* Garante que o keystore exista: se ausente, cria com uma chave nova e persiste.
|
|
26
|
+
* Retorna o keystore (privado).
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureKeystore(path: string, alg: SigningAlg): Promise<PersistedKeystore>;
|
|
29
|
+
/**
|
|
30
|
+
* Rotaciona o keystore: gera uma chave nova, coloca-a NA FRENTE (vira a chave de
|
|
31
|
+
* assinatura corrente) e mantém apenas as `keep` mais recentes (default 2) para
|
|
32
|
+
* que tokens assinados com a chave anterior continuem validando. Persiste e
|
|
33
|
+
* retorna o keystore atualizado.
|
|
34
|
+
*/
|
|
35
|
+
export declare function rotateKeystore(path: string, alg: SigningAlg, keep?: number): Promise<{
|
|
36
|
+
store: PersistedKeystore;
|
|
37
|
+
newKid: string;
|
|
38
|
+
retiredKids: string[];
|
|
39
|
+
}>;
|
|
40
|
+
/** Deriva o JWKS PÚBLICO (sem `d` e demais campos privados) a partir do privado. */
|
|
41
|
+
export declare function toPublicJwks(store: PersistedKeystore): {
|
|
42
|
+
keys: Record<string, any>[];
|
|
43
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { generateKeyPair, exportJWK } from 'jose';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
/** Gera uma chave de assinatura privada como JWK (com use/alg/kid). */
|
|
6
|
+
export async function generateSigningJwk(alg) {
|
|
7
|
+
const { privateKey } = await generateKeyPair(alg, { extractable: true });
|
|
8
|
+
const jwk = (await exportJWK(privateKey));
|
|
9
|
+
jwk.use = 'sig';
|
|
10
|
+
jwk.alg = alg;
|
|
11
|
+
jwk.kid = randomUUID();
|
|
12
|
+
return jwk;
|
|
13
|
+
}
|
|
14
|
+
/** Lê o keystore do arquivo, ou null se não existir/for inválido. */
|
|
15
|
+
export function readKeystore(path) {
|
|
16
|
+
if (!existsSync(path))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
20
|
+
if (parsed && Array.isArray(parsed.keys))
|
|
21
|
+
return parsed;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Escreve o keystore no arquivo (cria o diretório se preciso). */
|
|
29
|
+
export function writeKeystore(path, store) {
|
|
30
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
31
|
+
writeFileSync(path, JSON.stringify(store, null, 2) + '\n', { mode: 0o600 });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Garante que o keystore exista: se ausente, cria com uma chave nova e persiste.
|
|
35
|
+
* Retorna o keystore (privado).
|
|
36
|
+
*/
|
|
37
|
+
export async function ensureKeystore(path, alg) {
|
|
38
|
+
const existing = readKeystore(path);
|
|
39
|
+
if (existing && existing.keys.length > 0)
|
|
40
|
+
return existing;
|
|
41
|
+
const store = { keys: [await generateSigningJwk(alg)] };
|
|
42
|
+
writeKeystore(path, store);
|
|
43
|
+
return store;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Rotaciona o keystore: gera uma chave nova, coloca-a NA FRENTE (vira a chave de
|
|
47
|
+
* assinatura corrente) e mantém apenas as `keep` mais recentes (default 2) para
|
|
48
|
+
* que tokens assinados com a chave anterior continuem validando. Persiste e
|
|
49
|
+
* retorna o keystore atualizado.
|
|
50
|
+
*/
|
|
51
|
+
export async function rotateKeystore(path, alg, keep = 2) {
|
|
52
|
+
const current = readKeystore(path) ?? { keys: [] };
|
|
53
|
+
const fresh = await generateSigningJwk(alg);
|
|
54
|
+
const next = [fresh, ...current.keys];
|
|
55
|
+
const kept = next.slice(0, Math.max(1, keep));
|
|
56
|
+
const retiredKids = next.slice(Math.max(1, keep)).map((k) => k.kid);
|
|
57
|
+
const store = { keys: kept };
|
|
58
|
+
writeKeystore(path, store);
|
|
59
|
+
return { store, newKid: fresh.kid, retiredKids };
|
|
60
|
+
}
|
|
61
|
+
/** Deriva o JWKS PÚBLICO (sem `d` e demais campos privados) a partir do privado. */
|
|
62
|
+
export function toPublicJwks(store) {
|
|
63
|
+
const PRIVATE_FIELDS = ['d', 'p', 'q', 'dp', 'dq', 'qi'];
|
|
64
|
+
return {
|
|
65
|
+
keys: store.keys.map((jwk) => {
|
|
66
|
+
const pub = {};
|
|
67
|
+
for (const [k, v] of Object.entries(jwk)) {
|
|
68
|
+
if (!PRIVATE_FIELDS.includes(k))
|
|
69
|
+
pub[k] = v;
|
|
70
|
+
}
|
|
71
|
+
return pub;
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -5,11 +5,11 @@ function renderDashboardHtml(snapshot) {
|
|
|
5
5
|
const histograms = Object.entries(snapshot.histograms)
|
|
6
6
|
.map(([k, h]) => `<tr><td>${k}</td><td>${h.count}</td><td>${h.sum}</td><td>${h.min}</td><td>${h.max}</td></tr>`)
|
|
7
7
|
.join('');
|
|
8
|
-
return `<!doctype html><html lang="
|
|
8
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="refresh" content="5"><title>AuthKit — Metrics</title>
|
|
9
9
|
<style>body{font-family:system-ui,sans-serif;margin:2rem;color:#111}h1{font-size:1.2rem}table{border-collapse:collapse;margin:1rem 0;width:100%}th,td{border:1px solid #ddd;padding:.4rem .6rem;text-align:left;font-size:.9rem}th{background:#f5f5f5}</style>
|
|
10
|
-
</head><body><h1>AuthKit —
|
|
11
|
-
<h2>Counters</h2><table><thead><tr><th>
|
|
12
|
-
<h2>Histograms</h2><table><thead><tr><th>
|
|
10
|
+
</head><body><h1>AuthKit — Metrics</h1><p>Updated: ${snapshot.updatedAt ? new Date(snapshot.updatedAt).toISOString() : '—'}</p>
|
|
11
|
+
<h2>Counters</h2><table><thead><tr><th>Metric</th><th>Total</th></tr></thead><tbody>${counters || '<tr><td colspan="2">—</td></tr>'}</tbody></table>
|
|
12
|
+
<h2>Histograms</h2><table><thead><tr><th>Metric</th><th>Count</th><th>Sum</th><th>Min</th><th>Max</th></tr></thead><tbody>${histograms || '<tr><td colspan="5">—</td></tr>'}</tbody></table>
|
|
13
13
|
</body></html>`;
|
|
14
14
|
}
|
|
15
15
|
export default class MetricsController {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as oidc from 'oidc-provider';
|
|
2
|
+
import { createDeviceSources } from './device_sources.js';
|
|
2
3
|
export function buildProvider(config, options) {
|
|
3
4
|
const cookieKeys = config.cookieKeys.length ? config.cookieKeys : [options.appKey];
|
|
4
5
|
// OIDC Dynamic Client Registration (RFC 7591/7592). Só montamos as chaves de feature
|
|
@@ -16,6 +17,22 @@ export function buildProvider(config, options) {
|
|
|
16
17
|
...(dynReg.management ? { registrationManagement: { enabled: true } } : {}),
|
|
17
18
|
}
|
|
18
19
|
: {};
|
|
20
|
+
// Device Authorization Grant (RFC 8628). Quando ligado, montamos a feature com
|
|
21
|
+
// as três sources de UI i18n-izadas (entrada/confirmação/sucesso do user-code).
|
|
22
|
+
const deviceFlowFeatures = config.deviceFlow.enabled
|
|
23
|
+
? { deviceFlow: { enabled: true, ...createDeviceSources(config.messages) } }
|
|
24
|
+
: {};
|
|
25
|
+
// DPoP (RFC 9449). A chave EXATA do oidc-provider v9 é `dPoP`.
|
|
26
|
+
const dpopFeatures = config.dpop.enabled ? { dPoP: { enabled: true } } : {};
|
|
27
|
+
// PAR (RFC 9126).
|
|
28
|
+
const parFeatures = config.par.enabled
|
|
29
|
+
? {
|
|
30
|
+
pushedAuthorizationRequests: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
requirePushedAuthorizationRequests: config.par.requirePushedAuthorizationRequests,
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
: {};
|
|
19
36
|
const provider = new oidc.Provider(config.issuer, {
|
|
20
37
|
adapter: config.AdapterClass,
|
|
21
38
|
clients: config.clients.map((c) => ({
|
|
@@ -79,7 +96,13 @@ export function buildProvider(config, options) {
|
|
|
79
96
|
revocation: { enabled: true },
|
|
80
97
|
introspection: { enabled: true },
|
|
81
98
|
...registrationFeatures,
|
|
99
|
+
...deviceFlowFeatures,
|
|
100
|
+
...dpopFeatures,
|
|
101
|
+
...parFeatures,
|
|
82
102
|
},
|
|
103
|
+
// Step-up auth (acr_values): anuncia os acr suportados para que clients possam
|
|
104
|
+
// solicitá-los. A exigência efetiva do 2º fator acontece na interaction de login.
|
|
105
|
+
acrValues: config.stepUp.acrValues,
|
|
83
106
|
ttl: {
|
|
84
107
|
AccessToken: config.ttl.accessToken,
|
|
85
108
|
RefreshToken: config.ttl.refreshToken,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AuthMessages } from '../host/i18n.js';
|
|
2
|
+
export declare function createDeviceSources(messages: AuthMessages): {
|
|
3
|
+
userCodeInputSource(_ctx: any, form: string, _out: any, err: any): Promise<void>;
|
|
4
|
+
userCodeConfirmSource(_ctx: any, form: string, _client: any, _deviceInfo: any, userCode: string): Promise<void>;
|
|
5
|
+
successSource(_ctx: any): Promise<void>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { translate } from '../host/i18n.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fontes (sources) de renderização do Device Authorization Grant (RFC 8628).
|
|
4
|
+
*
|
|
5
|
+
* O oidc-provider chama estas funções com o KOA ctx (não o HttpContext do Adonis),
|
|
6
|
+
* então elas NÃO têm acesso ao renderer Inertia/Edge do host. Emitimos HTML
|
|
7
|
+
* auto-contido e i18n-izado (mesmo idioma das demais telas), o que também silencia
|
|
8
|
+
* os avisos `shouldChange` dos defaults da lib. As três telas:
|
|
9
|
+
* - userCodeInputSource: entrada do user-code (`/device`)
|
|
10
|
+
* - userCodeConfirmSource: confirmação após o code casar
|
|
11
|
+
* - successSource: tela final pós-aprovação
|
|
12
|
+
*/
|
|
13
|
+
function esc(value) {
|
|
14
|
+
return String(value ?? '')
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"');
|
|
19
|
+
}
|
|
20
|
+
const STYLE = `
|
|
21
|
+
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#f5f5f7;margin:0;padding:48px 16px;color:#1d1d1f}
|
|
22
|
+
.card{max-width:340px;margin:0 auto;background:#fff;border-radius:14px;padding:32px;box-shadow:0 1px 4px rgba(0,0,0,.08)}
|
|
23
|
+
h1{font-size:1.4rem;font-weight:600;margin:0 0 12px;text-align:center}
|
|
24
|
+
p{font-size:.95rem;line-height:1.5;text-align:center;color:#444}
|
|
25
|
+
p.red{color:#c0392b}
|
|
26
|
+
code{display:block;font-size:1.6rem;letter-spacing:.15em;text-align:center;margin:16px 0;font-weight:600}
|
|
27
|
+
input[type=text]{width:100%;box-sizing:border-box;height:46px;font-size:1rem;text-align:center;text-transform:uppercase;border:1px solid #d2d2d7;border-radius:10px;padding:0 12px;margin-bottom:14px}
|
|
28
|
+
button{width:100%;height:44px;font-size:.95rem;font-weight:600;color:#fff;background:#0071e3;border:0;border-radius:10px;cursor:pointer}
|
|
29
|
+
button:hover{background:#0077ed}
|
|
30
|
+
.abort{margin-top:12px;background:none;color:#666;font-weight:400}
|
|
31
|
+
.abort:hover{background:none;text-decoration:underline}
|
|
32
|
+
`;
|
|
33
|
+
function page(title, inner) {
|
|
34
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${esc(title)}</title><style>${STYLE}</style></head><body><div class="card">${inner}</div></body></html>`;
|
|
35
|
+
}
|
|
36
|
+
export function createDeviceSources(messages) {
|
|
37
|
+
const t = (key, params) => translate(messages, key, params);
|
|
38
|
+
return {
|
|
39
|
+
async userCodeInputSource(_ctx, form, _out, err) {
|
|
40
|
+
const ctx = _ctx;
|
|
41
|
+
let msg;
|
|
42
|
+
if (err && (err.userCode || err.name === 'NoCodeError')) {
|
|
43
|
+
msg = `<p class="red">${esc(t('device.input.error_invalid'))}</p>`;
|
|
44
|
+
}
|
|
45
|
+
else if (err && err.name === 'AbortedError') {
|
|
46
|
+
msg = `<p class="red">${esc(t('device.input.error_aborted'))}</p>`;
|
|
47
|
+
}
|
|
48
|
+
else if (err) {
|
|
49
|
+
msg = `<p class="red">${esc(t('device.input.error_generic'))}</p>`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
msg = `<p>${esc(t('device.input.intro'))}</p>`;
|
|
53
|
+
}
|
|
54
|
+
ctx.body = page(t('device.input.title'), `<h1>${esc(t('device.input.title'))}</h1>${msg}${form}<button type="submit" form="op.deviceInputForm">${esc(t('device.input.submit'))}</button>`);
|
|
55
|
+
},
|
|
56
|
+
async userCodeConfirmSource(_ctx, form, _client, _deviceInfo, userCode) {
|
|
57
|
+
const ctx = _ctx;
|
|
58
|
+
ctx.body = page(t('device.confirm.title'), `<h1>${esc(t('device.confirm.title'))}</h1><p>${esc(t('device.confirm.body'))}</p><code>${esc(userCode)}</code>${form}<button autofocus type="submit" form="op.deviceConfirmForm">${esc(t('device.confirm.submit'))}</button><button class="abort" type="submit" form="op.deviceConfirmForm" value="yes" name="abort">${esc(t('device.confirm.abort'))}</button>`);
|
|
59
|
+
},
|
|
60
|
+
async successSource(_ctx) {
|
|
61
|
+
const ctx = _ctx;
|
|
62
|
+
ctx.body = page(t('device.success.title'), `<h1>${esc(t('device.success.title'))}</h1><p>${esc(t('device.success.body'))}</p>`);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -4,6 +4,11 @@ export interface InteractionDeps {
|
|
|
4
4
|
id: string;
|
|
5
5
|
} | null>;
|
|
6
6
|
}
|
|
7
|
+
/** Detalhes opcionais de login (step-up auth): acr alcançado + amr (métodos). */
|
|
8
|
+
export interface CompleteLoginExtra {
|
|
9
|
+
acr?: string;
|
|
10
|
+
amr?: string[];
|
|
11
|
+
}
|
|
7
12
|
export interface InteractionActions {
|
|
8
13
|
details(ctx: HttpContext): Promise<any>;
|
|
9
14
|
login(ctx: HttpContext, input: {
|
|
@@ -12,7 +17,7 @@ export interface InteractionActions {
|
|
|
12
17
|
}): Promise<{
|
|
13
18
|
ok: boolean;
|
|
14
19
|
}>;
|
|
15
|
-
completeLogin(ctx: HttpContext, accountId: string): Promise<{
|
|
20
|
+
completeLogin(ctx: HttpContext, accountId: string, extra?: CompleteLoginExtra): Promise<{
|
|
16
21
|
ok: boolean;
|
|
17
22
|
}>;
|
|
18
23
|
consent(ctx: HttpContext): Promise<unknown>;
|
|
@@ -14,8 +14,15 @@ export function createInteractionActions(provider, deps) {
|
|
|
14
14
|
await provider.interactionFinished(ctx.request.request, ctx.response.response, { login: { accountId: account.id } }, { mergeWithLastSubmission: false });
|
|
15
15
|
return { ok: true };
|
|
16
16
|
},
|
|
17
|
-
async completeLogin(ctx, accountId) {
|
|
18
|
-
|
|
17
|
+
async completeLogin(ctx, accountId, extra) {
|
|
18
|
+
// acr/amr (RFC 8176): quando um step-up de MFA foi efetivamente cumprido,
|
|
19
|
+
// passamos o acr alcançado + os métodos (amr) para que o id_token os carregue.
|
|
20
|
+
const login = { accountId };
|
|
21
|
+
if (extra?.acr)
|
|
22
|
+
login.acr = extra.acr;
|
|
23
|
+
if (extra?.amr && extra.amr.length)
|
|
24
|
+
login.amr = extra.amr;
|
|
25
|
+
await provider.interactionFinished(ctx.request.request, ctx.response.response, { login }, { mergeWithLastSubmission: false });
|
|
19
26
|
return { ok: true };
|
|
20
27
|
},
|
|
21
28
|
async consent(ctx) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/adonis-authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"oidc-provider": "^9.8.4",
|
|
77
77
|
"otplib": "^12.0.1",
|
|
78
78
|
"qrcode": "^1.5.4",
|
|
79
|
-
"@dudousxd/adonis-authkit-core": "0.
|
|
79
|
+
"@dudousxd/adonis-authkit-core": "0.2.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@adonisjs/ally": "6.3.0",
|