@adonis-agora/authkit-server 0.56.0 → 0.57.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
@@ -129,7 +129,7 @@ export { resolveRegistration } from './src/define_config.js';
129
129
  export type { RegistrationConfigInput, ResolvedRegistrationConfig, } from './src/define_config.js';
130
130
  export { getAccountId, realAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
131
131
  export { ACCOUNT_SESSION_KEY } from './src/host/middleware/account_auth.js';
132
- export { rememberAccessToken, startImpersonation, impersonationState, stopImpersonation, } from './src/host/impersonation_session.js';
132
+ export { rememberAccessToken, rememberRefreshToken, refreshAccessToken, startImpersonation, impersonationState, stopImpersonation, } from './src/host/impersonation_session.js';
133
133
  export type { StartImpersonationParams, ImpersonationState, } from './src/host/impersonation_session.js';
134
134
  export { SUDO_SESSION_KEY, SUDO_ACCOUNT_SESSION_KEY, SUDO_MODE_DEFAULTS, requireSudo, isSudoActive, markSudo, resolveEffectiveSudoMode, } from './src/host/sudo_mode.js';
135
135
  export type { SudoModeSetting, ResolvedSudoModeSetting, } from './src/host/sudo_mode.js';
package/build/index.js CHANGED
@@ -91,7 +91,7 @@ export { ACCOUNT_SESSION_KEY } from './src/host/middleware/account_auth.js';
91
91
  // Session impersonation — RP-side glue that routes through the IdP's RFC 8693
92
92
  // token-exchange (the IdP validates the admin role + audits). See
93
93
  // src/host/impersonation_session.ts.
94
- export { rememberAccessToken, startImpersonation, impersonationState, stopImpersonation, } from './src/host/impersonation_session.js';
94
+ export { rememberAccessToken, rememberRefreshToken, refreshAccessToken, startImpersonation, impersonationState, stopImpersonation, } from './src/host/impersonation_session.js';
95
95
  // Sudo mode — helpers for host controllers that require step-up authentication.
96
96
  export { SUDO_SESSION_KEY, SUDO_ACCOUNT_SESSION_KEY, SUDO_MODE_DEFAULTS, requireSudo, isSudoActive, markSudo, resolveEffectiveSudoMode, } from './src/host/sudo_mode.js';
97
97
  // SPI de métodos de confirmação de identidade (sudo mode).
@@ -4,10 +4,16 @@ import type { HttpContext } from '@adonisjs/core/http';
4
4
  * após o login). Necessário porque o token-exchange exige o access token do admin
5
5
  * como `subject_token`, e o RP normalmente descarta os tokens após o login.
6
6
  *
7
- * Access tokens são curtos: se expirar, `startImpersonation` falha e o admin
8
- * re-loga (aceitável; refresh fica pra depoisYAGNI).
7
+ * Access tokens são curtos: se expirar, `startImpersonation` renova via refresh
8
+ * token (`rememberRefreshToken`) e tenta de novoo admin não precisa relogar.
9
9
  */
10
10
  export declare function rememberAccessToken(ctx: HttpContext, accessToken: string): void;
11
+ /**
12
+ * Guarda o refresh token do admin (do scope `offline_access`), usado para
13
+ * renovar o access token expirado antes do token-exchange. Chame no callback
14
+ * OIDC do RP junto com `rememberAccessToken`.
15
+ */
16
+ export declare function rememberRefreshToken(ctx: HttpContext, refreshToken: string): void;
11
17
  export interface StartImpersonationParams {
12
18
  /** Id do usuário-alvo a personificar. */
13
19
  targetId: string;
@@ -27,6 +33,20 @@ export interface ImpersonationState {
27
33
  /** O admin real (impersonator), quando `active`. */
28
34
  impersonatorId?: string;
29
35
  }
36
+ /**
37
+ * Renova o access token do admin via refresh grant (RFC 6749 §6), usando o
38
+ * refresh token guardado por `rememberRefreshToken`. Retorna o novo access
39
+ * token (e o refresh token rotacionado, se o IdP emitir outro), ou null se não
40
+ * houver refresh token / o refresh falhar.
41
+ *
42
+ * O IdP deste pacote roda `refresh_token` grant por default
43
+ * (`build_provider.ts`: `grant_types: ['authorization_code', 'refresh_token']`),
44
+ * então o refresh é válido na configuração padrão.
45
+ */
46
+ export declare function refreshAccessToken(ctx: HttpContext, params: Pick<StartImpersonationParams, 'issuer' | 'clientId' | 'clientSecret' | 'tokenEndpoint' | 'fetchImpl'>): Promise<{
47
+ accessToken: string;
48
+ refreshToken?: string;
49
+ } | null>;
30
50
  /**
31
51
  * Inicia a impersonation: lê o access token do admin da sessão, chama o
32
52
  * token-exchange (o IdP valida a role admin e audita) e SÓ em sucesso guarda o
@@ -27,17 +27,32 @@ const IMPERSONATOR_SESSION_KEY = 'impersonator_user_id';
27
27
  * `subject_token` do token-exchange. Interna: NÃO exporte o literal.
28
28
  */
29
29
  const ADMIN_ACCESS_TOKEN_SESSION_KEY = 'admin_access_token';
30
+ /**
31
+ * Key de sessão que guarda o refresh token do admin (do `offline_access`),
32
+ * usado para renovar o access token expirado antes do token-exchange. Interna:
33
+ * NÃO exporte o literal.
34
+ */
35
+ const ADMIN_REFRESH_TOKEN_SESSION_KEY = 'admin_refresh_token';
36
+ const REFRESH_TOKEN_GRANT = 'refresh_token';
30
37
  /**
31
38
  * Guarda o access token do admin na sessão (chame no callback OIDC do RP, logo
32
39
  * após o login). Necessário porque o token-exchange exige o access token do admin
33
40
  * como `subject_token`, e o RP normalmente descarta os tokens após o login.
34
41
  *
35
- * Access tokens são curtos: se expirar, `startImpersonation` falha e o admin
36
- * re-loga (aceitável; refresh fica pra depoisYAGNI).
42
+ * Access tokens são curtos: se expirar, `startImpersonation` renova via refresh
43
+ * token (`rememberRefreshToken`) e tenta de novoo admin não precisa relogar.
37
44
  */
38
45
  export function rememberAccessToken(ctx, accessToken) {
39
46
  ctx.session.put(ADMIN_ACCESS_TOKEN_SESSION_KEY, accessToken);
40
47
  }
48
+ /**
49
+ * Guarda o refresh token do admin (do scope `offline_access`), usado para
50
+ * renovar o access token expirado antes do token-exchange. Chame no callback
51
+ * OIDC do RP junto com `rememberAccessToken`.
52
+ */
53
+ export function rememberRefreshToken(ctx, refreshToken) {
54
+ ctx.session.put(ADMIN_REFRESH_TOKEN_SESSION_KEY, refreshToken);
55
+ }
41
56
  /**
42
57
  * POST inline do RFC 8693 token-exchange. Inline (em vez de depender de
43
58
  * `@adonis-agora/authkit-client`) porque o client NÃO é dependência do server e
@@ -68,6 +83,43 @@ async function requestTokenExchange(params, subjectToken) {
68
83
  throw new Error(`Token exchange failed: ${res.status}`);
69
84
  }
70
85
  }
86
+ /**
87
+ * Renova o access token do admin via refresh grant (RFC 6749 §6), usando o
88
+ * refresh token guardado por `rememberRefreshToken`. Retorna o novo access
89
+ * token (e o refresh token rotacionado, se o IdP emitir outro), ou null se não
90
+ * houver refresh token / o refresh falhar.
91
+ *
92
+ * O IdP deste pacote roda `refresh_token` grant por default
93
+ * (`build_provider.ts`: `grant_types: ['authorization_code', 'refresh_token']`),
94
+ * então o refresh é válido na configuração padrão.
95
+ */
96
+ export async function refreshAccessToken(ctx, params) {
97
+ const refreshToken = ctx.session.get(ADMIN_REFRESH_TOKEN_SESSION_KEY);
98
+ if (!refreshToken)
99
+ return null;
100
+ const body = new URLSearchParams({
101
+ grant_type: REFRESH_TOKEN_GRANT,
102
+ refresh_token: refreshToken,
103
+ client_id: params.clientId,
104
+ });
105
+ if (params.clientSecret)
106
+ body.set('client_secret', params.clientSecret);
107
+ const fetchImpl = params.fetchImpl ?? fetch;
108
+ const res = await fetchImpl(params.tokenEndpoint ?? `${params.issuer}/token`, {
109
+ method: 'POST',
110
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
111
+ body: body.toString(),
112
+ });
113
+ if (!res.ok)
114
+ return null;
115
+ const data = (await res.json());
116
+ if (!data.access_token)
117
+ return null;
118
+ const result = { accessToken: data.access_token };
119
+ if (data.refresh_token)
120
+ result.refreshToken = data.refresh_token;
121
+ return result;
122
+ }
71
123
  /**
72
124
  * Inicia a impersonation: lê o access token do admin da sessão, chama o
73
125
  * token-exchange (o IdP valida a role admin e audita) e SÓ em sucesso guarda o
@@ -94,7 +146,22 @@ export async function startImpersonation(ctx, params) {
94
146
  }
95
147
  // O IdP é o gatekeeper: lança se o admin não puder personificar. Chamado ANTES
96
148
  // de qualquer mutação de sessão — em caso de erro nada é trocado.
97
- await requestTokenExchange(params, adminAccessToken);
149
+ try {
150
+ await requestTokenExchange(params, adminAccessToken);
151
+ }
152
+ catch (err) {
153
+ // Access token expirado (4xx do exchange): renova via refresh token e tenta
154
+ // de novo. Sem refresh token / refresh falho → propaga o erro original.
155
+ if (!(err instanceof Error) || !/exchange failed: 4\d\d/.test(err.message))
156
+ throw err;
157
+ const refreshed = await refreshAccessToken(ctx, params);
158
+ if (!refreshed)
159
+ throw err;
160
+ rememberAccessToken(ctx, refreshed.accessToken);
161
+ if (refreshed.refreshToken)
162
+ rememberRefreshToken(ctx, refreshed.refreshToken);
163
+ await requestTokenExchange(params, refreshed.accessToken);
164
+ }
98
165
  // Anti-fixation: rotaciona o id da sessão (mantém os dados) antes de gravar a
99
166
  // nova identidade. Mesmo padrão do consumidor real no RP.
100
167
  await ctx.session.regenerate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.56.0",
3
+ "version": "0.57.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",