@udibo/oauth2 0.24.0 → 0.26.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/README.md +92 -61
- package/dist/adapters/hono/bff/bff.d.ts +13 -5
- package/dist/adapters/hono/bff/bff.js +31 -2
- package/dist/client/_issuer-refusal.d.ts +3 -0
- package/dist/client/_issuer-refusal.js +10 -0
- package/dist/client/direct-client.d.ts +51 -2
- package/dist/client/direct-client.js +101 -17
- package/dist/client/mod.d.ts +1 -1
- package/dist/models/responses.d.ts +5 -0
- package/dist/server/authorization-server.d.ts +8 -1
- package/dist/server/authorization-server.js +26 -6
- package/dist/server/grants/refresh-token.d.ts +8 -4
- package/dist/server/grants/refresh-token.js +15 -4
- package/dist/server/services/token.d.ts +37 -3
- package/dist/testing/contract/mod.d.ts +7 -0
- package/dist/testing/contract/mod.js +7 -0
- package/dist/testing/contract/token-rotation.d.ts +95 -0
- package/dist/testing/contract/token-rotation.js +383 -0
- package/dist/testing/contract/token.d.ts +3 -0
- package/dist/testing/contract/token.js +44 -2
- package/dist/testing/services.d.ts +7 -0
- package/dist/testing/services.js +43 -14
- package/package.json +1 -1
|
@@ -134,6 +134,11 @@ export const ENDPOINT_PATHS = Object.fromEntries(ENDPOINT_ENTRIES.map(([key, { p
|
|
|
134
134
|
* ```
|
|
135
135
|
*/
|
|
136
136
|
export const ENDPOINT_METHODS = Object.fromEntries(ENDPOINT_ENTRIES.map(([key, { methods }]) => [key, methods]));
|
|
137
|
+
function authorizationResponseRedirect(target) {
|
|
138
|
+
if (target.issuer)
|
|
139
|
+
target.url.searchParams.set("iss", target.issuer);
|
|
140
|
+
return Response.redirect(target.url.toString(), 302);
|
|
141
|
+
}
|
|
137
142
|
function isAuthorizationCodeGrant(grant) {
|
|
138
143
|
return grant instanceof AuthorizationCodeGrant;
|
|
139
144
|
}
|
|
@@ -742,6 +747,11 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
742
747
|
* responses from this endpoint, since there is no verified place to send the
|
|
743
748
|
* user back to.
|
|
744
749
|
*
|
|
750
|
+
* Every redirect to the client, the code and the error alike, carries `iss`
|
|
751
|
+
* set to the request's resolved issuer (RFC 9207), replacing any `iss` the
|
|
752
|
+
* registered `redirect_uri` itself carries. A request that resolves no
|
|
753
|
+
* `issuer` sends none.
|
|
754
|
+
*
|
|
745
755
|
* Requires a grant extending {@linkcode AuthorizationCodeGrant} registered
|
|
746
756
|
* under `authorization_code`; without one the endpoint answers 500
|
|
747
757
|
* `server_error` (or rethrows, with `throwOnError`).
|
|
@@ -757,16 +767,17 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
757
767
|
return await this.#authorize(request, target, authenticateUser, handleConsent);
|
|
758
768
|
}
|
|
759
769
|
catch (error) {
|
|
760
|
-
return this.#authorizeErrorResponse(error, target
|
|
770
|
+
return this.#authorizeErrorResponse(error, target);
|
|
761
771
|
}
|
|
762
772
|
}
|
|
763
773
|
async #authorize(request, target, authenticateUser, handleConsent) {
|
|
764
774
|
const params = this.parseAuthorizeParameters(request);
|
|
765
775
|
const grant = this.#authorizationCodeGrant();
|
|
766
|
-
const { services } = await this.authorizationContext(request);
|
|
776
|
+
const { services, issuer } = await this.authorizationContext(request);
|
|
767
777
|
const client = await this.#resolveAuthorizeClient(grant, params.clientId, services);
|
|
768
778
|
const redirectUrl = this.#resolveRedirectTarget(client, params.redirectUri);
|
|
769
779
|
target.url = redirectUrl;
|
|
780
|
+
target.issuer = issuer;
|
|
770
781
|
if (!params.state) {
|
|
771
782
|
throw new InvalidRequestError("state required");
|
|
772
783
|
}
|
|
@@ -807,9 +818,10 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
807
818
|
authenticationContext,
|
|
808
819
|
}, request);
|
|
809
820
|
redirectUrl.searchParams.set("code", authorizationCode.code);
|
|
810
|
-
return
|
|
821
|
+
return authorizationResponseRedirect({ url: redirectUrl, issuer });
|
|
811
822
|
}
|
|
812
|
-
#authorizeErrorResponse(error,
|
|
823
|
+
#authorizeErrorResponse(error, target) {
|
|
824
|
+
const redirectUrl = target.url;
|
|
813
825
|
if (!redirectUrl || !(error instanceof Error) || !isOAuth2Error(error)) {
|
|
814
826
|
return this.#handleEndpointError(error);
|
|
815
827
|
}
|
|
@@ -824,7 +836,10 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
824
836
|
if (errorUri) {
|
|
825
837
|
searchParams.set("error_uri", errorUri);
|
|
826
838
|
}
|
|
827
|
-
return
|
|
839
|
+
return authorizationResponseRedirect({
|
|
840
|
+
url: redirectUrl,
|
|
841
|
+
issuer: target.issuer,
|
|
842
|
+
});
|
|
828
843
|
}
|
|
829
844
|
#authorizationCodeGrant() {
|
|
830
845
|
const grant = this.grants[AUTHORIZATION_CODE_GRANT_TYPE];
|
|
@@ -1136,7 +1151,9 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
1136
1151
|
* authorization code grant's own `challengeMethods`, so a server that
|
|
1137
1152
|
* registers an extra method (or drops `S256`) advertises what it will
|
|
1138
1153
|
* actually accept. It is omitted entirely when no authorization code grant
|
|
1139
|
-
* is registered, since PKCE has nothing to apply to.
|
|
1154
|
+
* is registered, since PKCE has nothing to apply to. The same condition
|
|
1155
|
+
* governs `authorization_response_iss_parameter_supported: true`
|
|
1156
|
+
* (RFC 9207 §3): the authorization endpoint's redirects carry `iss`.
|
|
1140
1157
|
*
|
|
1141
1158
|
* `token_endpoint_auth_methods_supported` always includes `none`, the
|
|
1142
1159
|
* RFC 7591 method for a client with no secret. The token endpoint accepts it
|
|
@@ -1178,6 +1195,9 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
1178
1195
|
metadata.code_challenge_methods_supported = challengeMethods;
|
|
1179
1196
|
}
|
|
1180
1197
|
metadata.response_types_supported = hasAuthCodeGrant ? ["code"] : [];
|
|
1198
|
+
if (hasAuthCodeGrant) {
|
|
1199
|
+
metadata.authorization_response_iss_parameter_supported = true;
|
|
1200
|
+
}
|
|
1181
1201
|
const scopesSupported = context.scopesSupported ?? this.scopesSupported;
|
|
1182
1202
|
if (scopesSupported) {
|
|
1183
1203
|
metadata.scopes_supported = scopesSupported;
|
|
@@ -86,10 +86,14 @@ export declare class RefreshTokenGrant<Client extends ClientInterface, User, S e
|
|
|
86
86
|
* token (and rotates the refresh token when the token service supports it)
|
|
87
87
|
* from a valid, unexpired refresh token.
|
|
88
88
|
*
|
|
89
|
-
* Rotation
|
|
90
|
-
* `
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* Rotation claims the exchanged token and saves its successor in one call
|
|
90
|
+
* to the token service's `rotate` when implemented. Otherwise it takes two
|
|
91
|
+
* calls: it revokes the exchanged token through `revokeRotated` when
|
|
92
|
+
* implemented, falling back to `revoke` — so a service whose `revoke` ends
|
|
93
|
+
* the owning session can keep this bookkeeping step from signing the
|
|
94
|
+
* person out — and then saves the successor. A refresh that presents the
|
|
95
|
+
* exchanged token between those two calls is treated as reuse
|
|
96
|
+
* ({@linkcode TokenServiceInterface.rotate} describes the cost).
|
|
93
97
|
*
|
|
94
98
|
* Rotation renews the refresh token's sliding lifetime, bounded by the
|
|
95
99
|
* family's absolute cap when the token service implements
|
|
@@ -32,10 +32,14 @@ export class RefreshTokenGrant extends AbstractGrant {
|
|
|
32
32
|
* token (and rotates the refresh token when the token service supports it)
|
|
33
33
|
* from a valid, unexpired refresh token.
|
|
34
34
|
*
|
|
35
|
-
* Rotation
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* Rotation claims the exchanged token and saves its successor in one call
|
|
36
|
+
* to the token service's `rotate` when implemented. Otherwise it takes two
|
|
37
|
+
* calls: it revokes the exchanged token through `revokeRotated` when
|
|
38
|
+
* implemented, falling back to `revoke` — so a service whose `revoke` ends
|
|
39
|
+
* the owning session can keep this bookkeeping step from signing the
|
|
40
|
+
* person out — and then saves the successor. A refresh that presents the
|
|
41
|
+
* exchanged token between those two calls is treated as reuse
|
|
42
|
+
* ({@linkcode TokenServiceInterface.rotate} describes the cost).
|
|
39
43
|
*
|
|
40
44
|
* Rotation renews the refresh token's sliding lifetime, bounded by the
|
|
41
45
|
* family's absolute cap when the token service implements
|
|
@@ -122,6 +126,13 @@ export class RefreshTokenGrant extends AbstractGrant {
|
|
|
122
126
|
if (familyExpiresAt) {
|
|
123
127
|
this.capAtFamilyExpiry(nextRefreshToken, familyExpiresAt);
|
|
124
128
|
}
|
|
129
|
+
if (tokenService.rotate) {
|
|
130
|
+
const rotated = await tokenService.rotate(currentToken, nextRefreshToken);
|
|
131
|
+
if (!rotated) {
|
|
132
|
+
throw new InvalidGrantError("refresh_token was already consumed");
|
|
133
|
+
}
|
|
134
|
+
return rotated;
|
|
135
|
+
}
|
|
125
136
|
const claimed = tokenService.revokeRotated
|
|
126
137
|
? await tokenService.revokeRotated(currentToken)
|
|
127
138
|
: await tokenService.revoke(currentToken);
|
|
@@ -129,6 +129,32 @@ export interface TokenServiceInterface<Client extends ClientInterface, User, Sco
|
|
|
129
129
|
* it, so a sign-out racing a refresh still ends the successor.
|
|
130
130
|
*/
|
|
131
131
|
revokeRotated?(token: RefreshToken<Client, User, Scope>): Promise<boolean>;
|
|
132
|
+
/**
|
|
133
|
+
* Rotates a refresh token in one atomic step: claims `current` and persists
|
|
134
|
+
* `next`, its successor in the same family, or does neither. Resolves the
|
|
135
|
+
* saved successor, or `undefined` with nothing persisted when `current` was
|
|
136
|
+
* already claimed or revoked, or its family has been revoked. Concurrent
|
|
137
|
+
* calls for one `current` produce exactly one successor.
|
|
138
|
+
*
|
|
139
|
+
* When implemented, the refresh grant calls it instead of
|
|
140
|
+
* {@linkcode revokeRotated} (or {@linkcode revoke}) followed by
|
|
141
|
+
* {@linkcode save}. Without it, the grant claims and saves in two calls,
|
|
142
|
+
* and a request that presents `current` between them finds it revoked with
|
|
143
|
+
* no live successor: reuse detection revokes the family and fires
|
|
144
|
+
* `onTokenReuse`, so a benign concurrent refresh signs the person out, and a
|
|
145
|
+
* store whose `revokeFamily` does not keep the family revoked leaves the
|
|
146
|
+
* successor live.
|
|
147
|
+
*
|
|
148
|
+
* Treat the claim as a rotation, never an end of grant: stamp the
|
|
149
|
+
* rotated-out record with {@linkcode RefreshToken.rotatedAt} exactly as
|
|
150
|
+
* {@linkcode revokeRotated} does, keep it findable through
|
|
151
|
+
* {@linkcode getRevokedRefreshToken}, and revoke its access token as
|
|
152
|
+
* `revokeRotated` would. Make it linearizable with
|
|
153
|
+
* {@linkcode revokeFamily}, as that method requires.
|
|
154
|
+
* `runTokenRotationContractTests` checks it under concurrent callers, with
|
|
155
|
+
* the store's barrier wired between its claim and its insert.
|
|
156
|
+
*/
|
|
157
|
+
rotate?(current: RefreshToken<Client, User, Scope>, next: RefreshToken<Client, User, Scope>): Promise<RefreshToken<Client, User, Scope> | undefined>;
|
|
132
158
|
/**
|
|
133
159
|
* Revokes all tokens issued with the given authorization code.
|
|
134
160
|
* Returns true if any tokens were revoked (indicating code replay).
|
|
@@ -164,9 +190,17 @@ export interface TokenServiceInterface<Client extends ClientInterface, User, Sco
|
|
|
164
190
|
* `/revoke` ends the family without firing `onTokenReuse`, so any audit
|
|
165
191
|
* trail for that case has to come from this method.
|
|
166
192
|
*
|
|
167
|
-
*
|
|
168
|
-
* after this call must be stored already revoked,
|
|
169
|
-
* `MemoryTokenService` does.
|
|
193
|
+
* Without {@linkcode rotate}, keep the family revoked: a successor that a
|
|
194
|
+
* concurrent rotation saves after this call must be stored already revoked,
|
|
195
|
+
* as `MemoryTokenService` does. A store that implements `rotate` meets this
|
|
196
|
+
* through the linearizability below, since the refresh grant then never
|
|
197
|
+
* saves a rotation's successor separately.
|
|
198
|
+
*
|
|
199
|
+
* With {@linkcode rotate}, make the two linearizable: once this resolves,
|
|
200
|
+
* the family has no live member and no later `rotate` of a member
|
|
201
|
+
* succeeds, and a `rotate` that committed before this began has its
|
|
202
|
+
* successor revoked. A store whose `rotate` can be paused between its claim
|
|
203
|
+
* and its insert meets this by taking one per-family lock in both methods.
|
|
170
204
|
*/
|
|
171
205
|
revokeFamily?(familyId: string): Promise<boolean>;
|
|
172
206
|
/**
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
* also have separate suites. They use app-owned fixture helpers to verify
|
|
25
25
|
* conditional credential writes and immediate, user-scoped session revocation.
|
|
26
26
|
*
|
|
27
|
+
* `TokenServiceInterface.rotate` has a runner of its own for concurrent
|
|
28
|
+
* schedules: two rotations of one refresh token, a family revocation racing a
|
|
29
|
+
* rotation, and a replay racing a refresh. A store that awaits between its
|
|
30
|
+
* claim and its insert wires `TokenRotationBarriers` into `rotate`, so the
|
|
31
|
+
* suite can hold a rotation there.
|
|
32
|
+
*
|
|
27
33
|
* The service runners take a factory plus seed helpers because `add` /
|
|
28
34
|
* `register` methods aren't part of the public service interfaces — the
|
|
29
35
|
* consumer tells the runner how to populate test data.
|
|
@@ -33,6 +39,7 @@
|
|
|
33
39
|
export { runUserServiceContractTests, type UserServiceContractOptions, } from "./user.ts";
|
|
34
40
|
export { type ClientServiceContractOptions, runClientServiceContractTests, } from "./client.ts";
|
|
35
41
|
export { runTokenServiceContractTests, type TokenServiceContractOptions, } from "./token.ts";
|
|
42
|
+
export { runTokenRotationContractTests, type TokenRotationBarriers, type TokenRotationContractOptions, type TokenRotationContractServices, } from "./token-rotation.ts";
|
|
36
43
|
export { runTokenReaderContractTests, type TokenReaderContractFixture, type TokenReaderContractOptions, } from "./token-reader.ts";
|
|
37
44
|
export { type AuthorizationCodeServiceContractOptions, runAuthorizationCodeServiceContractTests, } from "./authorization-code.ts";
|
|
38
45
|
export { type DeviceAuthorizationServiceContractOptions, runDeviceAuthorizationServiceContractTests, } from "./device-authorization.ts";
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
* also have separate suites. They use app-owned fixture helpers to verify
|
|
25
25
|
* conditional credential writes and immediate, user-scoped session revocation.
|
|
26
26
|
*
|
|
27
|
+
* `TokenServiceInterface.rotate` has a runner of its own for concurrent
|
|
28
|
+
* schedules: two rotations of one refresh token, a family revocation racing a
|
|
29
|
+
* rotation, and a replay racing a refresh. A store that awaits between its
|
|
30
|
+
* claim and its insert wires `TokenRotationBarriers` into `rotate`, so the
|
|
31
|
+
* suite can hold a rotation there.
|
|
32
|
+
*
|
|
27
33
|
* The service runners take a factory plus seed helpers because `add` /
|
|
28
34
|
* `register` methods aren't part of the public service interfaces — the
|
|
29
35
|
* consumer tells the runner how to populate test data.
|
|
@@ -33,6 +39,7 @@
|
|
|
33
39
|
export { runUserServiceContractTests, } from "./user.js";
|
|
34
40
|
export { runClientServiceContractTests, } from "./client.js";
|
|
35
41
|
export { runTokenServiceContractTests, } from "./token.js";
|
|
42
|
+
export { runTokenRotationContractTests, } from "./token-rotation.js";
|
|
36
43
|
export { runTokenReaderContractTests, } from "./token-reader.js";
|
|
37
44
|
export { runAuthorizationCodeServiceContractTests, } from "./authorization-code.js";
|
|
38
45
|
export { runDeviceAuthorizationServiceContractTests, } from "./device-authorization.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test suite for {@link TokenServiceInterface.rotate} and its
|
|
3
|
+
* linearizability with {@link TokenServiceInterface.revokeFamily}.
|
|
4
|
+
*
|
|
5
|
+
* `runTokenServiceContractTests` checks what one caller sees. This suite
|
|
6
|
+
* checks what concurrent callers see: two rotations of one refresh token, a
|
|
7
|
+
* family revocation racing a rotation, a replay racing a refresh through the
|
|
8
|
+
* refresh grant, a rotation in an unrelated family, and a rotation whose
|
|
9
|
+
* insert fails. Each case asserts the store's end state, the live tokens of
|
|
10
|
+
* the family, not only the value each call resolved.
|
|
11
|
+
*
|
|
12
|
+
* The store under test awaits {@link TokenRotationBarriers.afterClaim} inside
|
|
13
|
+
* `rotate`, between its claim and its insert, so the suite can hold a rotation
|
|
14
|
+
* there and race the other call against it; a store that never reaches the
|
|
15
|
+
* barrier fails every held case. Only a store whose `rotate` never awaits
|
|
16
|
+
* between its claim and its insert, such as `MemoryTokenService`, opts out with
|
|
17
|
+
* `rotateAwaitsBarrier: false`; the suite then runs those schedules as
|
|
18
|
+
* overlapping calls.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import type { ClientInterface } from "../../models/client.ts";
|
|
23
|
+
import type { AbstractScope, BasicScope } from "../../models/scope.ts";
|
|
24
|
+
import type { ClientServiceInterface } from "../../server/services/client.ts";
|
|
25
|
+
import type { TokenServiceInterface } from "../../server/services/token.ts";
|
|
26
|
+
import type { UserServiceInterface } from "../../server/services/user.ts";
|
|
27
|
+
import type { MemoryUserShape } from "../services.ts";
|
|
28
|
+
/**
|
|
29
|
+
* The point inside a store's {@linkcode TokenServiceInterface.rotate} at which
|
|
30
|
+
* {@link runTokenRotationContractTests} holds a rotation. Wire it into a test
|
|
31
|
+
* build of the store.
|
|
32
|
+
*/
|
|
33
|
+
export interface TokenRotationBarriers {
|
|
34
|
+
/**
|
|
35
|
+
* Await this inside `rotate` after claiming `current` and before persisting
|
|
36
|
+
* `next`, still inside the atomic step: within the transaction and holding
|
|
37
|
+
* the family's lock. When it rejects, fail the rotation as a failed insert
|
|
38
|
+
* would: leave `current` live, persist nothing, and reject or resolve
|
|
39
|
+
* `undefined`.
|
|
40
|
+
*/
|
|
41
|
+
afterClaim(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/** Services built for each case of {@link runTokenRotationContractTests}. */
|
|
44
|
+
export interface TokenRotationContractServices<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope> {
|
|
45
|
+
/** The user service the token service hydrates users from. */
|
|
46
|
+
userService: UserServiceInterface<U>;
|
|
47
|
+
/** The client service the token service hydrates clients from. */
|
|
48
|
+
clientService: ClientServiceInterface<C, U>;
|
|
49
|
+
/** The token service under test. It must implement `rotate`. */
|
|
50
|
+
tokenService: TokenServiceInterface<C, U, S>;
|
|
51
|
+
}
|
|
52
|
+
/** Options for {@link runTokenRotationContractTests}. */
|
|
53
|
+
export interface TokenRotationContractOptions<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope> {
|
|
54
|
+
/**
|
|
55
|
+
* Returns fresh services for each case, with `rotate` awaiting
|
|
56
|
+
* `barriers.afterClaim()` between its claim and its insert. A store that
|
|
57
|
+
* sets `rotateAwaitsBarrier: false` ignores `barriers`.
|
|
58
|
+
*/
|
|
59
|
+
makeServices(barriers: TokenRotationBarriers): Promise<TokenRotationContractServices<C, U, S>> | TokenRotationContractServices<C, U, S>;
|
|
60
|
+
/** Persists a user with the given password into the user service under test. */
|
|
61
|
+
addUser(service: UserServiceInterface<U>, user: U, password: string): Promise<void>;
|
|
62
|
+
/** Persists a client into the client service under test. */
|
|
63
|
+
addClient(service: ClientServiceInterface<C, U>, client: C): Promise<void>;
|
|
64
|
+
/** Builds a distinct user fixture for the given sequence number. Defaults to a minimal `{ id, username }` shape. */
|
|
65
|
+
makeUser?(seq: number): U;
|
|
66
|
+
/** Builds a distinct client fixture for the given sequence number. Defaults to a client granting `authorization_code` and `refresh_token`. */
|
|
67
|
+
makeClient?(seq: number): C;
|
|
68
|
+
/**
|
|
69
|
+
* Whether `rotate` awaits `barriers.afterClaim()`. Defaults to `true`: the
|
|
70
|
+
* suite holds a rotation between its claim and its insert while it runs a
|
|
71
|
+
* family revocation, a second rotation, a replayed refresh, or a rotation in
|
|
72
|
+
* another family, and fails the insert there. Set `false` only when
|
|
73
|
+
* `rotate` never awaits between its claim and its insert, as in
|
|
74
|
+
* `MemoryTokenService`; the suite then runs those schedules as overlapping
|
|
75
|
+
* calls, which cannot reach a gap inside `rotate`.
|
|
76
|
+
*/
|
|
77
|
+
rotateAwaitsBarrier?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Milliseconds the suite lets a call run against a held rotation before it
|
|
80
|
+
* releases the rotation; a call that has not settled by then is taken to
|
|
81
|
+
* be waiting on it. Defaults to 100. Raise it for a store whose calls take
|
|
82
|
+
* longer to reach the lock they wait on.
|
|
83
|
+
*/
|
|
84
|
+
blockedAfterMs?: number;
|
|
85
|
+
/** Overrides the name passed to the outer `describe` block. */
|
|
86
|
+
describeName?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Runs the rotation contract suite: concurrent schedules of
|
|
90
|
+
* {@linkcode TokenServiceInterface.rotate} against itself, against
|
|
91
|
+
* {@linkcode TokenServiceInterface.revokeFamily}, and through the refresh
|
|
92
|
+
* grant. The service must implement `rotate`, `revokeFamily` and
|
|
93
|
+
* `getRevokedRefreshToken`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function runTokenRotationContractTests<C extends ClientInterface, U extends MemoryUserShape, S extends AbstractScope = BasicScope>(options: TokenRotationContractOptions<C, U, S>): void;
|