@adonis-agora/authkit-client 0.18.7 → 0.18.9
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.
|
@@ -28,6 +28,13 @@ export declare class AuthkitClientManager {
|
|
|
28
28
|
* Preserva TAMBÉM o `impersonationBinding`: renovar o token do alvo não muda a
|
|
29
29
|
* sessão, então não pode invalidar o vínculo e trancar o ator na impersonação.
|
|
30
30
|
*
|
|
31
|
+
* Dedup por sessionId: requests concorrentes da mesma sessão (ex.: autosave + vários
|
|
32
|
+
* GETs chegando juntos quando o access token vence) disparam UM refresh só. Quem
|
|
33
|
+
* chegou depois espera o refresh in-flight e grava o TokenSet renovado na SUA
|
|
34
|
+
* PRÓPRIA sessão — cada request tem seu objeto de sessão, e sem isso ele seguiria
|
|
35
|
+
* com o access token vencido (→ 401) e com o refresh token antigo, já invalidado
|
|
36
|
+
* pela rotação, que ele poderia gravar de volta e envenenar a sessão.
|
|
37
|
+
*
|
|
31
38
|
* Best-effort: qualquer falha (token revogado, IdP fora) é silenciosa — o resolver
|
|
32
39
|
* lida com a sessão expirada no fluxo normal. Chamado pelo middleware por request.
|
|
33
40
|
*/
|
|
@@ -23,7 +23,11 @@ function readParked(value) {
|
|
|
23
23
|
export class AuthkitClientManager {
|
|
24
24
|
config;
|
|
25
25
|
#resolverCache;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Refreshes in-flight por sessionId (dedup thundering herd). A promise resolve com o
|
|
28
|
+
* TokenSet renovado (já mesclado) ou `null` se falhou — nunca rejeita. Guarda também
|
|
29
|
+
* o refresh token de origem, pra quem espera saber se é da mesma linhagem.
|
|
30
|
+
*/
|
|
27
31
|
#refreshPromises = new Map();
|
|
28
32
|
constructor(config) {
|
|
29
33
|
this.config = config;
|
|
@@ -60,6 +64,13 @@ export class AuthkitClientManager {
|
|
|
60
64
|
* Preserva TAMBÉM o `impersonationBinding`: renovar o token do alvo não muda a
|
|
61
65
|
* sessão, então não pode invalidar o vínculo e trancar o ator na impersonação.
|
|
62
66
|
*
|
|
67
|
+
* Dedup por sessionId: requests concorrentes da mesma sessão (ex.: autosave + vários
|
|
68
|
+
* GETs chegando juntos quando o access token vence) disparam UM refresh só. Quem
|
|
69
|
+
* chegou depois espera o refresh in-flight e grava o TokenSet renovado na SUA
|
|
70
|
+
* PRÓPRIA sessão — cada request tem seu objeto de sessão, e sem isso ele seguiria
|
|
71
|
+
* com o access token vencido (→ 401) e com o refresh token antigo, já invalidado
|
|
72
|
+
* pela rotação, que ele poderia gravar de volta e envenenar a sessão.
|
|
73
|
+
*
|
|
63
74
|
* Best-effort: qualquer falha (token revogado, IdP fora) é silenciosa — o resolver
|
|
64
75
|
* lida com a sessão expirada no fluxo normal. Chamado pelo middleware por request.
|
|
65
76
|
*/
|
|
@@ -79,14 +90,27 @@ export class AuthkitClientManager {
|
|
|
79
90
|
// juntos fariam N refreshes + N-1 falhas com refresh token já inválido.
|
|
80
91
|
const sessionId = session.sessionId;
|
|
81
92
|
if (sessionId) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
93
|
+
const inFlight = this.#refreshPromises.get(sessionId);
|
|
94
|
+
// Só aproveita o refresh in-flight se ele partiu do MESMO refresh token que esta
|
|
95
|
+
// request tem — senão o TokenSet renovado é de outra linhagem (ex.: a sessão
|
|
96
|
+
// trocou de token set no meio do caminho) e não pode ser gravado aqui.
|
|
97
|
+
if (inFlight && inFlight.refreshToken === tokenSet.refreshToken) {
|
|
98
|
+
const refreshed = await inFlight.promise;
|
|
99
|
+
if (refreshed) {
|
|
100
|
+
session.put(this.config.sessionKey, {
|
|
101
|
+
...refreshed,
|
|
102
|
+
// O vínculo é por sessão (mesmo sessionId → mesmo vínculo); mantém o desta
|
|
103
|
+
// request e só cai no do refresh se esta não tiver.
|
|
104
|
+
impersonationBinding: tokenSet.impersonationBinding ?? refreshed.impersonationBinding,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
85
107
|
return;
|
|
86
108
|
}
|
|
109
|
+
// Linhagem diferente: segue e faz o próprio refresh (a entrada do mapa passa a
|
|
110
|
+
// ser a desta request; a anterior não a apaga — ver o `finally`).
|
|
87
111
|
}
|
|
88
|
-
// Promise que
|
|
89
|
-
//
|
|
112
|
+
// Promise que NUNCA rejeita: resolve com o TokenSet gravado, ou `null` se a
|
|
113
|
+
// renovação falhou — aí o TokenSet original fica como está (o resolver decide).
|
|
90
114
|
const promise = refreshTokens({
|
|
91
115
|
issuer: this.config.issuer,
|
|
92
116
|
clientId: this.config.clientId,
|
|
@@ -96,22 +120,31 @@ export class AuthkitClientManager {
|
|
|
96
120
|
resilience: this.config.resilience,
|
|
97
121
|
})
|
|
98
122
|
.then((next) => {
|
|
99
|
-
|
|
123
|
+
const merged = {
|
|
100
124
|
idToken: next.idToken || tokenSet.idToken,
|
|
101
125
|
accessToken: next.accessToken,
|
|
102
126
|
refreshToken: next.refreshToken ?? tokenSet.refreshToken,
|
|
103
127
|
expiresAt: next.expiresAt,
|
|
104
128
|
impersonationBinding: tokenSet.impersonationBinding,
|
|
105
|
-
}
|
|
129
|
+
};
|
|
130
|
+
session.put(this.config.sessionKey, merged);
|
|
131
|
+
return merged;
|
|
106
132
|
})
|
|
107
|
-
.catch(() =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
.catch(() => null);
|
|
134
|
+
if (!sessionId) {
|
|
135
|
+
await promise;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const entry = { refreshToken: tokenSet.refreshToken, promise };
|
|
139
|
+
this.#refreshPromises.set(sessionId, entry);
|
|
140
|
+
try {
|
|
141
|
+
await promise;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
// Só remove se a entrada ainda é a nossa.
|
|
145
|
+
if (this.#refreshPromises.get(sessionId) === entry)
|
|
146
|
+
this.#refreshPromises.delete(sessionId);
|
|
147
|
+
}
|
|
115
148
|
}
|
|
116
149
|
/**
|
|
117
150
|
* Recebe o POST `application/x-www-form-urlencoded` de OIDC Back-Channel Logout. Lê o
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-client",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.9",
|
|
4
4
|
"description": "AdonisJS OIDC/OAuth2 relying-party (client) toolkit: session-based authentication against an OpenID Connect identity provider, with JWT/PAT user resolvers and OpenTelemetry metrics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|
|
@@ -79,11 +79,11 @@
|
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@adonis-agora/authkit-core": "0.
|
|
82
|
+
"@adonis-agora/authkit-core": "0.9.0",
|
|
83
83
|
"jose": "6.2.12"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@adonis-agora/authkit-server": "0.
|
|
86
|
+
"@adonis-agora/authkit-server": "0.80.0",
|
|
87
87
|
"@adonis-agora/resilience": "0.3.7",
|
|
88
88
|
"@adonisjs/auth": "10.1.0",
|
|
89
89
|
"@adonisjs/core": "7.5.0",
|