@udibo/oauth2 0.25.0 → 0.27.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 +94 -61
- package/dist/adapters/hono/authorization-server.d.ts +21 -8
- package/dist/adapters/hono/authorization-server.js +2 -2
- package/dist/adapters/hono/bff/bff.d.ts +13 -5
- package/dist/adapters/hono/bff/bff.js +31 -2
- package/dist/cli/idp/server.js +35 -14
- 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/errors.d.ts +34 -0
- package/dist/errors.js +58 -0
- package/dist/models/authentication.d.ts +28 -0
- package/dist/models/authorization-code.d.ts +3 -3
- package/dist/models/responses.d.ts +13 -2
- package/dist/server/authentication-request.d.ts +60 -0
- package/dist/server/authentication-request.js +102 -0
- package/dist/server/authorization-server.d.ts +63 -8
- package/dist/server/authorization-server.js +80 -17
- package/dist/server/mod.d.ts +3 -2
- package/dist/server/mod.js +2 -1
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
23
|
import type { AuthorizeParameters } from "../models/authorization-code.ts";
|
|
24
|
-
import type { AuthenticationContext } from "../models/authentication.ts";
|
|
24
|
+
import type { AuthenticationContext, AuthenticationRequest } from "../models/authentication.ts";
|
|
25
25
|
import type { ClientInterface } from "../models/client.ts";
|
|
26
26
|
import type { Token, TokenResponse } from "../models/token.ts";
|
|
27
27
|
import type { AuthorizationServerMetadata } from "../models/responses.ts";
|
|
@@ -288,6 +288,32 @@ export interface AuthorizationServerOptions<Client extends ClientInterface, User
|
|
|
288
288
|
* integer.
|
|
289
289
|
*/
|
|
290
290
|
maxBodyBytes?: number;
|
|
291
|
+
/**
|
|
292
|
+
* The `prompt` values the authorization endpoint accepts, advertised as
|
|
293
|
+
* `prompt_values_supported`. A request carrying any other value is refused
|
|
294
|
+
* with `invalid_request`.
|
|
295
|
+
*
|
|
296
|
+
* The server enforces `none`, `login` and `consent` itself. List another
|
|
297
|
+
* value, such as `select_account` or `create`, only when your
|
|
298
|
+
* `authenticateUser` honors it: the server passes it through in the
|
|
299
|
+
* {@linkcode AuthenticationRequest} and cannot check the result.
|
|
300
|
+
*
|
|
301
|
+
* @default ["none", "login", "consent"]
|
|
302
|
+
* @throws {TypeError} From the constructor when an entry is empty or
|
|
303
|
+
* contains a space or non-ASCII character.
|
|
304
|
+
*/
|
|
305
|
+
promptValuesSupported?: readonly string[];
|
|
306
|
+
/**
|
|
307
|
+
* Authentication context class references your `authenticateUser` can
|
|
308
|
+
* achieve, advertised as `acr_values_supported` in the OIDC discovery
|
|
309
|
+
* document. It does not restrict `acr_values`: the request's preferences
|
|
310
|
+
* reach `authenticateUser` either way, and the ID token's `acr` is what the
|
|
311
|
+
* returned `authenticationContext` records.
|
|
312
|
+
*
|
|
313
|
+
* @throws {TypeError} From the constructor when an entry is empty or
|
|
314
|
+
* contains a space or non-ASCII character.
|
|
315
|
+
*/
|
|
316
|
+
acrValuesSupported?: readonly string[];
|
|
291
317
|
}
|
|
292
318
|
/**
|
|
293
319
|
* Function to authenticate the user during authorization.
|
|
@@ -305,17 +331,29 @@ export interface AuthorizationServerOptions<Client extends ClientInterface, User
|
|
|
305
331
|
* pre-authorize everything, return the requested scope as `authorizedScope`.
|
|
306
332
|
* Supply only verified event claims in `authenticationContext`; the code,
|
|
307
333
|
* token and refresh descendants retain a snapshot. Omit unknown claims.
|
|
334
|
+
* Set `reauthenticated: true` only when the user actively authenticated in
|
|
335
|
+
* response to this authorization request.
|
|
308
336
|
* - `null` — explicit denial. The framework redirects the browser to
|
|
309
337
|
* `redirect_uri` with `error=access_denied`. Use this for "the user said
|
|
310
338
|
* no", not for "the user isn't signed in yet."
|
|
311
339
|
* - A `Response` — short-circuits the rest of the authorize flow and is
|
|
312
340
|
* returned to the browser as-is. Use this to redirect to a login page,
|
|
313
|
-
* render an interstitial, etc.
|
|
341
|
+
* render an interstitial, etc. Under `prompt=none` the server answers
|
|
342
|
+
* `login_required` instead of returning it.
|
|
343
|
+
*
|
|
344
|
+
* The second argument carries the request's validated `prompt`, `max_age` and
|
|
345
|
+
* `acr_values`; the server always passes it. Before issuing a code the server
|
|
346
|
+
* checks the returned user against it with
|
|
347
|
+
* {@linkcode satisfiesAuthenticationRequest} and answers `login_required` when
|
|
348
|
+
* the check fails, so send the user to sign in again (return a `Response`)
|
|
349
|
+
* whenever the session cannot meet it. Report the `acr` the login achieved in
|
|
350
|
+
* `authenticationContext`, never the requested one.
|
|
314
351
|
*/
|
|
315
|
-
export type AuthenticateUserFn<User> = (request: Request) => Promise<{
|
|
352
|
+
export type AuthenticateUserFn<User> = (request: Request, authentication?: AuthenticationRequest) => Promise<{
|
|
316
353
|
user: User;
|
|
317
354
|
authorizedScope?: AbstractScope;
|
|
318
355
|
authenticationContext?: AuthenticationContext;
|
|
356
|
+
reauthenticated?: boolean;
|
|
319
357
|
} | Response | null>;
|
|
320
358
|
/**
|
|
321
359
|
* Function to handle user consent during authorization.
|
|
@@ -330,9 +368,14 @@ export type AuthenticateUserFn<User> = (request: Request) => Promise<{
|
|
|
330
368
|
* `redirect_uri` with `error=access_denied`.
|
|
331
369
|
* - A `Response` — short-circuits the rest of the authorize flow and is
|
|
332
370
|
* returned to the browser as-is. Use this to render a consent page that
|
|
333
|
-
* later resubmits to the authorize endpoint with the user's decision.
|
|
371
|
+
* later resubmits to the authorize endpoint with the user's decision. Under
|
|
372
|
+
* `prompt=none` the server answers `consent_required` instead.
|
|
373
|
+
*
|
|
374
|
+
* With `prompt=consent` the server calls it even when the scope is already
|
|
375
|
+
* authorized, and the fourth argument, always passed, says so: ask again
|
|
376
|
+
* rather than reusing a stored grant.
|
|
334
377
|
*/
|
|
335
|
-
export type HandleConsentFn<Client extends ClientInterface, S extends AbstractScope> = (client: Client, requestedScope: S | undefined, user: unknown) => Promise<{
|
|
378
|
+
export type HandleConsentFn<Client extends ClientInterface, S extends AbstractScope> = (client: Client, requestedScope: S | undefined, user: unknown, authentication?: AuthenticationRequest) => Promise<{
|
|
336
379
|
approved: boolean;
|
|
337
380
|
scope?: S;
|
|
338
381
|
} | Response>;
|
|
@@ -450,7 +493,8 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
|
|
|
450
493
|
* @throws {RangeError} If `maxBodyBytes` or `maxTokenBodyBytes` is not a
|
|
451
494
|
* positive integer.
|
|
452
495
|
* @throws {TypeError} If `realm` contains a character outside printable
|
|
453
|
-
* ASCII
|
|
496
|
+
* ASCII, or a `promptValuesSupported` or `acrValuesSupported` entry is
|
|
497
|
+
* empty or contains a space or non-ASCII character.
|
|
454
498
|
*/
|
|
455
499
|
constructor(options: AuthorizationServerOptions<Client, User, S>);
|
|
456
500
|
/**
|
|
@@ -508,7 +552,9 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
|
|
|
508
552
|
* `openid`, or `undefined` when the OIDC surface is off (no signing keys),
|
|
509
553
|
* the token has no user, the scope lacks `openid`, or no issuer resolved.
|
|
510
554
|
* The id_token's `aud` is the client id and it expires one hour after
|
|
511
|
-
* issue.
|
|
555
|
+
* issue. Its `auth_time` and `acr` are the token's recorded
|
|
556
|
+
* `authenticationContext`, present exactly when the context has them,
|
|
557
|
+
* whatever `userClaims` returns.
|
|
512
558
|
*/
|
|
513
559
|
mintIdToken(token: Token<Client, User, S>, context: AuthorizationServerContext<Client, User, S>): Promise<string | undefined>;
|
|
514
560
|
/**
|
|
@@ -610,6 +656,11 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
|
|
|
610
656
|
* responses from this endpoint, since there is no verified place to send the
|
|
611
657
|
* user back to.
|
|
612
658
|
*
|
|
659
|
+
* Every redirect to the client, the code and the error alike, carries `iss`
|
|
660
|
+
* set to the request's resolved issuer (RFC 9207), replacing any `iss` the
|
|
661
|
+
* registered `redirect_uri` itself carries. A request that resolves no
|
|
662
|
+
* `issuer` sends none.
|
|
663
|
+
*
|
|
613
664
|
* Requires a grant extending {@linkcode AuthorizationCodeGrant} registered
|
|
614
665
|
* under `authorization_code`; without one the endpoint answers 500
|
|
615
666
|
* `server_error` (or rethrows, with `throwOnError`).
|
|
@@ -719,7 +770,9 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
|
|
|
719
770
|
* authorization code grant's own `challengeMethods`, so a server that
|
|
720
771
|
* registers an extra method (or drops `S256`) advertises what it will
|
|
721
772
|
* actually accept. It is omitted entirely when no authorization code grant
|
|
722
|
-
* is registered, since PKCE has nothing to apply to.
|
|
773
|
+
* is registered, since PKCE has nothing to apply to. The same condition
|
|
774
|
+
* governs `authorization_response_iss_parameter_supported: true`
|
|
775
|
+
* (RFC 9207 §3): the authorization endpoint's redirects carry `iss`.
|
|
723
776
|
*
|
|
724
777
|
* `token_endpoint_auth_methods_supported` always includes `none`, the
|
|
725
778
|
* RFC 7591 method for a client with no secret. The token endpoint accepts it
|
|
@@ -821,6 +874,8 @@ export { AbstractAuthorizationCodeService, type AbstractAuthorizationCodeService
|
|
|
821
874
|
export type { DeviceAuthorizationServiceInterface } from "./services/device-authorization.ts";
|
|
822
875
|
export type { UserServiceInterface } from "./services/user.ts";
|
|
823
876
|
export type { AuthorizationCode, AuthorizeParameters, } from "../models/authorization-code.ts";
|
|
877
|
+
export type { AuthenticationRequest } from "../models/authentication.ts";
|
|
878
|
+
export { type AuthenticationOutcome, satisfiesAuthenticationRequest, } from "./authentication-request.ts";
|
|
824
879
|
export type { DeviceAuthorization } from "../models/device-authorization.ts";
|
|
825
880
|
export type { AuthorizationServerMetadata, DeviceAuthorizationResponse, ErrorResponse, OAuth2ErrorCode, } from "../models/responses.ts";
|
|
826
881
|
export { challengeMethods, CODE_VERIFIER_MAX_LENGTH, CODE_VERIFIER_MIN_LENGTH, CODE_VERIFIER_PATTERN, generateCodeChallenge, generateCodeVerifier, generateState, getChallengeMethod, validateCodeChallenge, validateCodeVerifier, verifyCodeChallenge, } from "../utils/pkce.ts";
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
23
|
import { snapshotAuthenticationContext } from "../utils/authentication-context.js";
|
|
24
|
-
import { AccessDeniedError, InvalidRequestError, InvalidTokenError, isOAuth2Error, OAuth2Error, ServerError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, } from "../errors.js";
|
|
24
|
+
import { AccessDeniedError, ConsentRequiredError, InvalidRequestError, InvalidTokenError, isOAuth2Error, LoginRequiredError, OAuth2Error, ServerError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, } from "../errors.js";
|
|
25
25
|
import { authenticateClientCredentials, extractClientCredentials, } from "./client-authentication.js";
|
|
26
|
+
import { assertSupportedValues, authenticationShortfall, DEFAULT_PROMPT_VALUES_SUPPORTED, parseAuthenticationRequest, } from "./authentication-request.js";
|
|
26
27
|
import { isRedirectUriPattern, matchRedirectUri } from "./redirect-uri.js";
|
|
27
28
|
import { AuthorizationCodeGrant } from "./grants/authorization-code.js";
|
|
28
29
|
import { DeviceAuthorizationGrant } from "./grants/device-authorization.js";
|
|
@@ -134,6 +135,11 @@ export const ENDPOINT_PATHS = Object.fromEntries(ENDPOINT_ENTRIES.map(([key, { p
|
|
|
134
135
|
* ```
|
|
135
136
|
*/
|
|
136
137
|
export const ENDPOINT_METHODS = Object.fromEntries(ENDPOINT_ENTRIES.map(([key, { methods }]) => [key, methods]));
|
|
138
|
+
function authorizationResponseRedirect(target) {
|
|
139
|
+
if (target.issuer)
|
|
140
|
+
target.url.searchParams.set("iss", target.issuer);
|
|
141
|
+
return Response.redirect(target.url.toString(), 302);
|
|
142
|
+
}
|
|
137
143
|
function isAuthorizationCodeGrant(grant) {
|
|
138
144
|
return grant instanceof AuthorizationCodeGrant;
|
|
139
145
|
}
|
|
@@ -189,6 +195,8 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
189
195
|
#endSession;
|
|
190
196
|
#isPublicSuffix;
|
|
191
197
|
#maxBodyBytes;
|
|
198
|
+
#promptValuesSupported;
|
|
199
|
+
#acrValuesSupported;
|
|
192
200
|
#subjectOf;
|
|
193
201
|
#userClaims;
|
|
194
202
|
#introspectionClaims;
|
|
@@ -203,7 +211,8 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
203
211
|
* @throws {RangeError} If `maxBodyBytes` or `maxTokenBodyBytes` is not a
|
|
204
212
|
* positive integer.
|
|
205
213
|
* @throws {TypeError} If `realm` contains a character outside printable
|
|
206
|
-
* ASCII
|
|
214
|
+
* ASCII, or a `promptValuesSupported` or `acrValuesSupported` entry is
|
|
215
|
+
* empty or contains a space or non-ASCII character.
|
|
207
216
|
*/
|
|
208
217
|
constructor(options) {
|
|
209
218
|
super({
|
|
@@ -237,6 +246,10 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
237
246
|
this.#isPublicSuffix = options.isPublicSuffix;
|
|
238
247
|
this.#maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
239
248
|
assertMaxBodyBytes(this.#maxBodyBytes, "maxBodyBytes");
|
|
249
|
+
this.#promptValuesSupported = assertSupportedValues("promptValuesSupported", options.promptValuesSupported ?? DEFAULT_PROMPT_VALUES_SUPPORTED);
|
|
250
|
+
if (options.acrValuesSupported) {
|
|
251
|
+
this.#acrValuesSupported = assertSupportedValues("acrValuesSupported", options.acrValuesSupported);
|
|
252
|
+
}
|
|
240
253
|
this.#subjectOf = options.subjectOf ?? defaultSubjectOf;
|
|
241
254
|
this.#userClaims = options.userClaims;
|
|
242
255
|
this.#introspectionClaims = options.introspectionClaims;
|
|
@@ -461,7 +474,9 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
461
474
|
* `openid`, or `undefined` when the OIDC surface is off (no signing keys),
|
|
462
475
|
* the token has no user, the scope lacks `openid`, or no issuer resolved.
|
|
463
476
|
* The id_token's `aud` is the client id and it expires one hour after
|
|
464
|
-
* issue.
|
|
477
|
+
* issue. Its `auth_time` and `acr` are the token's recorded
|
|
478
|
+
* `authenticationContext`, present exactly when the context has them,
|
|
479
|
+
* whatever `userClaims` returns.
|
|
465
480
|
*/
|
|
466
481
|
async mintIdToken(token, context) {
|
|
467
482
|
if (!this.signingKeys || !token.user || !context.issuer)
|
|
@@ -481,6 +496,15 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
481
496
|
claims.nonce = token.nonce;
|
|
482
497
|
else
|
|
483
498
|
delete claims.nonce;
|
|
499
|
+
const event = token.authenticationContext;
|
|
500
|
+
if (event?.auth_time !== undefined)
|
|
501
|
+
claims.auth_time = event.auth_time;
|
|
502
|
+
else
|
|
503
|
+
delete claims.auth_time;
|
|
504
|
+
if (event?.acr !== undefined)
|
|
505
|
+
claims.acr = event.acr;
|
|
506
|
+
else
|
|
507
|
+
delete claims.acr;
|
|
484
508
|
return await signJwt(key, claims);
|
|
485
509
|
}
|
|
486
510
|
async #oidcClaims(user, scope, authenticationContext) {
|
|
@@ -742,6 +766,11 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
742
766
|
* responses from this endpoint, since there is no verified place to send the
|
|
743
767
|
* user back to.
|
|
744
768
|
*
|
|
769
|
+
* Every redirect to the client, the code and the error alike, carries `iss`
|
|
770
|
+
* set to the request's resolved issuer (RFC 9207), replacing any `iss` the
|
|
771
|
+
* registered `redirect_uri` itself carries. A request that resolves no
|
|
772
|
+
* `issuer` sends none.
|
|
773
|
+
*
|
|
745
774
|
* Requires a grant extending {@linkcode AuthorizationCodeGrant} registered
|
|
746
775
|
* under `authorization_code`; without one the endpoint answers 500
|
|
747
776
|
* `server_error` (or rethrows, with `throwOnError`).
|
|
@@ -757,16 +786,17 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
757
786
|
return await this.#authorize(request, target, authenticateUser, handleConsent);
|
|
758
787
|
}
|
|
759
788
|
catch (error) {
|
|
760
|
-
return this.#authorizeErrorResponse(error, target
|
|
789
|
+
return this.#authorizeErrorResponse(error, target);
|
|
761
790
|
}
|
|
762
791
|
}
|
|
763
792
|
async #authorize(request, target, authenticateUser, handleConsent) {
|
|
764
793
|
const params = this.parseAuthorizeParameters(request);
|
|
765
794
|
const grant = this.#authorizationCodeGrant();
|
|
766
|
-
const { services } = await this.authorizationContext(request);
|
|
795
|
+
const { services, issuer } = await this.authorizationContext(request);
|
|
767
796
|
const client = await this.#resolveAuthorizeClient(grant, params.clientId, services);
|
|
768
797
|
const redirectUrl = this.#resolveRedirectTarget(client, params.redirectUri);
|
|
769
798
|
target.url = redirectUrl;
|
|
799
|
+
target.issuer = issuer;
|
|
770
800
|
if (!params.state) {
|
|
771
801
|
throw new InvalidRequestError("state required");
|
|
772
802
|
}
|
|
@@ -774,12 +804,21 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
774
804
|
this.#validateResponseType(params.responseType);
|
|
775
805
|
const requestedScope = grant.parseScope(params.scope);
|
|
776
806
|
this.#validateChallenge(grant, params);
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
807
|
+
const authentication = parseAuthenticationRequest(new URL(request.url).searchParams, this.#promptValuesSupported);
|
|
808
|
+
const silent = authentication.prompt.includes("none");
|
|
809
|
+
const authResult = await authenticateUser(request, authentication);
|
|
810
|
+
if (authResult instanceof Response) {
|
|
811
|
+
if (!silent)
|
|
812
|
+
return authResult;
|
|
813
|
+
await authResult.body?.cancel();
|
|
814
|
+
throw new LoginRequiredError("the user must sign in");
|
|
815
|
+
}
|
|
780
816
|
if (!authResult) {
|
|
781
817
|
throw new AccessDeniedError("authentication required");
|
|
782
818
|
}
|
|
819
|
+
const shortfall = authenticationShortfall(authentication, authResult, Date.now());
|
|
820
|
+
if (shortfall)
|
|
821
|
+
throw new LoginRequiredError(shortfall);
|
|
783
822
|
const { user, authorizedScope } = authResult;
|
|
784
823
|
const authenticationContext = snapshotAuthenticationContext(authResult.authenticationContext);
|
|
785
824
|
if (grant.requirePKCE && !params.challenge) {
|
|
@@ -793,6 +832,7 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
793
832
|
authorizedScope,
|
|
794
833
|
services,
|
|
795
834
|
handleConsent,
|
|
835
|
+
authentication,
|
|
796
836
|
});
|
|
797
837
|
if (consented instanceof Response)
|
|
798
838
|
return consented;
|
|
@@ -807,9 +847,10 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
807
847
|
authenticationContext,
|
|
808
848
|
}, request);
|
|
809
849
|
redirectUrl.searchParams.set("code", authorizationCode.code);
|
|
810
|
-
return
|
|
850
|
+
return authorizationResponseRedirect({ url: redirectUrl, issuer });
|
|
811
851
|
}
|
|
812
|
-
#authorizeErrorResponse(error,
|
|
852
|
+
#authorizeErrorResponse(error, target) {
|
|
853
|
+
const redirectUrl = target.url;
|
|
813
854
|
if (!redirectUrl || !(error instanceof Error) || !isOAuth2Error(error)) {
|
|
814
855
|
return this.#handleEndpointError(error);
|
|
815
856
|
}
|
|
@@ -824,7 +865,10 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
824
865
|
if (errorUri) {
|
|
825
866
|
searchParams.set("error_uri", errorUri);
|
|
826
867
|
}
|
|
827
|
-
return
|
|
868
|
+
return authorizationResponseRedirect({
|
|
869
|
+
url: redirectUrl,
|
|
870
|
+
issuer: target.issuer,
|
|
871
|
+
});
|
|
828
872
|
}
|
|
829
873
|
#authorizationCodeGrant() {
|
|
830
874
|
const grant = this.grants[AUTHORIZATION_CODE_GRANT_TYPE];
|
|
@@ -894,14 +938,23 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
894
938
|
}
|
|
895
939
|
}
|
|
896
940
|
async #resolveScopeAndConsent(options) {
|
|
897
|
-
const { grant, client, user, requestedScope, authorizedScope, services, handleConsent, } = options;
|
|
941
|
+
const { grant, client, user, requestedScope, authorizedScope, services, handleConsent, authentication, } = options;
|
|
898
942
|
const acceptedScope = await grant.acceptedScope(client, user, requestedScope, services.tokenService);
|
|
899
943
|
let scope = acceptedScope ?? undefined;
|
|
900
|
-
const
|
|
944
|
+
const forceConsent = authentication.prompt.includes("consent");
|
|
945
|
+
if (forceConsent && !handleConsent) {
|
|
946
|
+
throw new ConsentRequiredError("prompt=consent requires a consent handler");
|
|
947
|
+
}
|
|
948
|
+
const needsConsent = forceConsent ||
|
|
949
|
+
(scope !== undefined && !(authorizedScope && authorizedScope.has(scope)));
|
|
901
950
|
if (needsConsent && handleConsent) {
|
|
902
|
-
const consent = await handleConsent(client, scope, user);
|
|
903
|
-
if (consent instanceof Response)
|
|
904
|
-
|
|
951
|
+
const consent = await handleConsent(client, scope, user, authentication);
|
|
952
|
+
if (consent instanceof Response) {
|
|
953
|
+
if (!authentication.prompt.includes("none"))
|
|
954
|
+
return consent;
|
|
955
|
+
await consent.body?.cancel();
|
|
956
|
+
throw new ConsentRequiredError("the user must consent");
|
|
957
|
+
}
|
|
905
958
|
if (!consent.approved) {
|
|
906
959
|
throw new AccessDeniedError("user denied consent");
|
|
907
960
|
}
|
|
@@ -1136,7 +1189,9 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
1136
1189
|
* authorization code grant's own `challengeMethods`, so a server that
|
|
1137
1190
|
* registers an extra method (or drops `S256`) advertises what it will
|
|
1138
1191
|
* actually accept. It is omitted entirely when no authorization code grant
|
|
1139
|
-
* is registered, since PKCE has nothing to apply to.
|
|
1192
|
+
* is registered, since PKCE has nothing to apply to. The same condition
|
|
1193
|
+
* governs `authorization_response_iss_parameter_supported: true`
|
|
1194
|
+
* (RFC 9207 §3): the authorization endpoint's redirects carry `iss`.
|
|
1140
1195
|
*
|
|
1141
1196
|
* `token_endpoint_auth_methods_supported` always includes `none`, the
|
|
1142
1197
|
* RFC 7591 method for a client with no secret. The token endpoint accepts it
|
|
@@ -1178,6 +1233,10 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
1178
1233
|
metadata.code_challenge_methods_supported = challengeMethods;
|
|
1179
1234
|
}
|
|
1180
1235
|
metadata.response_types_supported = hasAuthCodeGrant ? ["code"] : [];
|
|
1236
|
+
if (hasAuthCodeGrant) {
|
|
1237
|
+
metadata.authorization_response_iss_parameter_supported = true;
|
|
1238
|
+
metadata.prompt_values_supported = [...this.#promptValuesSupported];
|
|
1239
|
+
}
|
|
1181
1240
|
const scopesSupported = context.scopesSupported ?? this.scopesSupported;
|
|
1182
1241
|
if (scopesSupported) {
|
|
1183
1242
|
metadata.scopes_supported = scopesSupported;
|
|
@@ -1186,6 +1245,9 @@ export class AuthorizationServer extends ResourceServer {
|
|
|
1186
1245
|
this.#advertiseEndpoints(metadata, endpoints, true);
|
|
1187
1246
|
metadata.id_token_signing_alg_values_supported = ["ES256"];
|
|
1188
1247
|
metadata.subject_types_supported = ["public"];
|
|
1248
|
+
if (hasAuthCodeGrant && this.#acrValuesSupported?.length) {
|
|
1249
|
+
metadata.acr_values_supported = [...this.#acrValuesSupported];
|
|
1250
|
+
}
|
|
1189
1251
|
}
|
|
1190
1252
|
return metadata;
|
|
1191
1253
|
}
|
|
@@ -1384,6 +1446,7 @@ export { PasswordGrant } from "./grants/password.js";
|
|
|
1384
1446
|
export { DeviceAuthorizationGrant } from "./grants/device-authorization.js";
|
|
1385
1447
|
export { AbstractTokenService, } from "./services/token.js";
|
|
1386
1448
|
export { AbstractAuthorizationCodeService, } from "./services/authorization-code.js";
|
|
1449
|
+
export { satisfiesAuthenticationRequest, } from "./authentication-request.js";
|
|
1387
1450
|
export { challengeMethods, CODE_VERIFIER_MAX_LENGTH, CODE_VERIFIER_MIN_LENGTH, CODE_VERIFIER_PATTERN, generateCodeChallenge, generateCodeVerifier, generateState, getChallengeMethod, validateCodeChallenge, validateCodeVerifier, verifyCodeChallenge, } from "../utils/pkce.js";
|
|
1388
1451
|
export { encodeBasicAuth, parseBasicAuth, tryParseBasicAuth, } from "../utils/basic-auth.js";
|
|
1389
1452
|
export { checkRedirectUriPattern, isRedirectUriPattern, matchRedirectUri, } from "./redirect-uri.js";
|
package/dist/server/mod.d.ts
CHANGED
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
11
|
export type { ClientCredentials, ClientInterface } from "../models/client.ts";
|
|
12
|
-
export type { AuthenticationContext } from "../models/authentication.ts";
|
|
12
|
+
export type { AuthenticationContext, AuthenticationRequest, } from "../models/authentication.ts";
|
|
13
13
|
export type { IntrospectionResponse, RefreshToken, Token, TokenResponse, } from "../models/token.ts";
|
|
14
14
|
export { AbstractScope, BasicScope } from "../models/scope.ts";
|
|
15
15
|
export type { ScopeConstructor } from "../models/scope.ts";
|
|
16
16
|
export { Authorization, authorizationFromClaims, } from "../models/authorization.ts";
|
|
17
17
|
export type { AuthorizationFailure, AuthorizationInit, OrganizationContext, RequireConditions, } from "../models/authorization.ts";
|
|
18
|
-
export { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InsufficientPermissionsError, InsufficientScopeError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, isOAuth2Error, OAuth2Error, ServerError, SlowDownError, TemporarilyUnavailableError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.ts";
|
|
18
|
+
export { AccessDeniedError, AccountSelectionRequiredError, AuthorizationPendingError, ConsentRequiredError, ExpiredTokenError, InsufficientPermissionsError, InsufficientScopeError, InteractionRequiredError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, isOAuth2Error, LoginRequiredError, OAuth2Error, ServerError, SlowDownError, TemporarilyUnavailableError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.ts";
|
|
19
19
|
export type { OAuth2ErrorClass, OAuth2ProblemDetailsExtensions, } from "../errors.ts";
|
|
20
|
+
export { type AuthenticationOutcome, satisfiesAuthenticationRequest, } from "./authentication-request.ts";
|
|
20
21
|
export type { TokenReaderInterface } from "./services/token.ts";
|
|
21
22
|
export { checkRedirectUriPattern, isRedirectUriPattern, matchRedirectUri, } from "./redirect-uri.ts";
|
|
22
23
|
export type { IsPublicSuffix, RedirectUriPatternRule, RedirectUriPatternViolation, } from "./redirect-uri.ts";
|
package/dist/server/mod.js
CHANGED
|
@@ -10,5 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { AbstractScope, BasicScope } from "../models/scope.js";
|
|
12
12
|
export { Authorization, authorizationFromClaims, } from "../models/authorization.js";
|
|
13
|
-
export { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InsufficientPermissionsError, InsufficientScopeError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, isOAuth2Error, OAuth2Error, ServerError, SlowDownError, TemporarilyUnavailableError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.js";
|
|
13
|
+
export { AccessDeniedError, AccountSelectionRequiredError, AuthorizationPendingError, ConsentRequiredError, ExpiredTokenError, InsufficientPermissionsError, InsufficientScopeError, InteractionRequiredError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, isOAuth2Error, LoginRequiredError, OAuth2Error, ServerError, SlowDownError, TemporarilyUnavailableError, toOAuth2Error, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.js";
|
|
14
|
+
export { satisfiesAuthenticationRequest, } from "./authentication-request.js";
|
|
14
15
|
export { checkRedirectUriPattern, isRedirectUriPattern, matchRedirectUri, } from "./redirect-uri.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@udibo/oauth2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "A flexible OAuth2 toolkit: framework-agnostic authorization & resource servers, a typed client, Hono adapters (including a backend-for-frontend), and React bindings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authorization-server",
|