@udibo/oauth2 0.12.0 → 0.12.1

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.
@@ -489,7 +489,8 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
489
489
  * Supports multiple grant types based on the configured grants.
490
490
  *
491
491
  * A malformed request — wrong method, a content-type other than
492
- * `application/x-www-form-urlencoded`, or no `grant_type` — is a 400
492
+ * `application/x-www-form-urlencoded`, no `grant_type`, or a repeated
493
+ * recognized singleton form parameter — is a 400
493
494
  * `invalid_request`. A `grant_type` with no registered grant is
494
495
  * `unsupported_grant_type`, checked before client authentication; a client
495
496
  * not registered for the grant is `unauthorized_client`. A body over
@@ -643,7 +644,8 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
643
644
  * endpoint cannot be used as an oracle for whether a token value is live or
644
645
  * whose it is. That covers the token only: a malformed request — wrong
645
646
  * method, a content-type other than `application/x-www-form-urlencoded`, or
646
- * no `token` parameter — is a 400 `invalid_request`, as on the token
647
+ * no `token` parameter, or repeated `token`, `token_type_hint`, `client_id` or
648
+ * `client_secret` — is a 400 `invalid_request`, as on the token
647
649
  * endpoint.
648
650
  *
649
651
  * `token_type_hint` orders the lookup rather than restricting it, so a client
@@ -684,6 +686,9 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
684
686
  * `application/x-www-form-urlencoded`, is a 400 `invalid_request`, as on the
685
687
  * token endpoint.
686
688
  *
689
+ * Repeated `token`, `token_type_hint`, `client_id` or `client_secret` form
690
+ * parameters are refused before the context is resolved.
691
+ *
687
692
  * @see https://datatracker.ietf.org/doc/html/rfc7662
688
693
  */
689
694
  handleIntrospectionRequest(request: Request): Promise<Response>;
@@ -739,6 +744,9 @@ export declare class AuthorizationServer<Client extends ClientInterface, User, S
739
744
  * `application/x-www-form-urlencoded`, is a 400 `invalid_request`, as on the
740
745
  * token endpoint.
741
746
  *
747
+ * Repeated `scope`, `client_id` or `client_secret` form parameters are
748
+ * refused before the context is resolved.
749
+ *
742
750
  * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
743
751
  * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
744
752
  */
@@ -348,6 +348,37 @@ export class AuthorizationServer extends ResourceServer {
348
348
  }
349
349
  return grant;
350
350
  }
351
+ #assertSingletonParameters(body, parameters) {
352
+ for (const parameter of parameters) {
353
+ if (body.getAll(parameter).length > 1) {
354
+ throw new InvalidRequestError(`${parameter} parameter must not be repeated`);
355
+ }
356
+ }
357
+ }
358
+ #assertTokenSingletonParameters(body) {
359
+ this.#assertSingletonParameters(body, ["grant_type"]);
360
+ let parameters;
361
+ switch (body.get("grant_type")) {
362
+ case "authorization_code":
363
+ parameters = ["code", "redirect_uri", "code_verifier"];
364
+ break;
365
+ case "refresh_token":
366
+ parameters = ["refresh_token", "scope"];
367
+ break;
368
+ case "password":
369
+ parameters = ["username", "password", "scope"];
370
+ break;
371
+ case "client_credentials":
372
+ parameters = ["scope"];
373
+ break;
374
+ case DEVICE_AUTHORIZATION_GRANT_TYPE:
375
+ parameters = ["device_code"];
376
+ break;
377
+ default:
378
+ parameters = [];
379
+ }
380
+ this.#assertSingletonParameters(body, parameters);
381
+ }
351
382
  async #readBody(request, options = {}) {
352
383
  const bytes = await readBoundedBody(request, this.#maxBodyBytes, options)
353
384
  .catch((cause) => {
@@ -374,6 +405,8 @@ export class AuthorizationServer extends ResourceServer {
374
405
  }).formData().catch(() => {
375
406
  throw new InvalidRequestError("body must be application/x-www-form-urlencoded");
376
407
  });
408
+ this.#assertSingletonParameters(body, ["client_id", "client_secret"]);
409
+ options.validateBody?.(body);
377
410
  const context = await this.authorizationContext(request);
378
411
  const grant = options.grantFromBody?.(body);
379
412
  const client = grant
@@ -389,7 +422,8 @@ export class AuthorizationServer extends ResourceServer {
389
422
  * Supports multiple grant types based on the configured grants.
390
423
  *
391
424
  * A malformed request — wrong method, a content-type other than
392
- * `application/x-www-form-urlencoded`, or no `grant_type` — is a 400
425
+ * `application/x-www-form-urlencoded`, no `grant_type`, or a repeated
426
+ * recognized singleton form parameter — is a 400
393
427
  * `invalid_request`. A `grant_type` with no registered grant is
394
428
  * `unsupported_grant_type`, checked before client authentication; a client
395
429
  * not registered for the grant is `unauthorized_client`. A body over
@@ -401,6 +435,7 @@ export class AuthorizationServer extends ResourceServer {
401
435
  const { context, body, client, grant } = await this
402
436
  .#beginClientAuthenticatedRequest(request, {
403
437
  grantFromBody: (body) => this.#grantFromBody(body),
438
+ validateBody: (body) => this.#assertTokenSingletonParameters(body),
404
439
  });
405
440
  const token = await grant.token(request, client, body);
406
441
  return await this.createTokenResponse(token, context, grant);
@@ -894,7 +929,8 @@ export class AuthorizationServer extends ResourceServer {
894
929
  * endpoint cannot be used as an oracle for whether a token value is live or
895
930
  * whose it is. That covers the token only: a malformed request — wrong
896
931
  * method, a content-type other than `application/x-www-form-urlencoded`, or
897
- * no `token` parameter — is a 400 `invalid_request`, as on the token
932
+ * no `token` parameter, or repeated `token`, `token_type_hint`, `client_id` or
933
+ * `client_secret` — is a 400 `invalid_request`, as on the token
898
934
  * endpoint.
899
935
  *
900
936
  * `token_type_hint` orders the lookup rather than restricting it, so a client
@@ -905,7 +941,12 @@ export class AuthorizationServer extends ResourceServer {
905
941
  async handleRevocationRequest(request) {
906
942
  try {
907
943
  const { context, body, client } = await this
908
- .#beginClientAuthenticatedRequest(request);
944
+ .#beginClientAuthenticatedRequest(request, {
945
+ validateBody: (body) => this.#assertSingletonParameters(body, [
946
+ "token",
947
+ "token_type_hint",
948
+ ]),
949
+ });
909
950
  const token = body.get("token");
910
951
  if (typeof token !== "string") {
911
952
  throw new InvalidRequestError("token parameter required");
@@ -979,12 +1020,20 @@ export class AuthorizationServer extends ResourceServer {
979
1020
  * `application/x-www-form-urlencoded`, is a 400 `invalid_request`, as on the
980
1021
  * token endpoint.
981
1022
  *
1023
+ * Repeated `token`, `token_type_hint`, `client_id` or `client_secret` form
1024
+ * parameters are refused before the context is resolved.
1025
+ *
982
1026
  * @see https://datatracker.ietf.org/doc/html/rfc7662
983
1027
  */
984
1028
  async handleIntrospectionRequest(request) {
985
1029
  try {
986
1030
  const { context, body, client } = await this
987
- .#beginClientAuthenticatedRequest(request);
1031
+ .#beginClientAuthenticatedRequest(request, {
1032
+ validateBody: (body) => this.#assertSingletonParameters(body, [
1033
+ "token",
1034
+ "token_type_hint",
1035
+ ]),
1036
+ });
988
1037
  const tokenValue = body.get("token");
989
1038
  if (typeof tokenValue !== "string") {
990
1039
  throw new InvalidRequestError("token parameter required");
@@ -1164,6 +1213,9 @@ export class AuthorizationServer extends ResourceServer {
1164
1213
  * `application/x-www-form-urlencoded`, is a 400 `invalid_request`, as on the
1165
1214
  * token endpoint.
1166
1215
  *
1216
+ * Repeated `scope`, `client_id` or `client_secret` form parameters are
1217
+ * refused before the context is resolved.
1218
+ *
1167
1219
  * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
1168
1220
  * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
1169
1221
  */
@@ -1172,6 +1224,7 @@ export class AuthorizationServer extends ResourceServer {
1172
1224
  const { context, body, client } = await this
1173
1225
  .#beginClientAuthenticatedRequest(request, {
1174
1226
  requiredGrantType: DEVICE_AUTHORIZATION_GRANT_TYPE,
1227
+ validateBody: (body) => this.#assertSingletonParameters(body, ["scope"]),
1175
1228
  });
1176
1229
  const grant = this.#deviceAuthorizationGrant();
1177
1230
  const scopeText = body.get("scope");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udibo/oauth2",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
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
  "repository": {
6
6
  "type": "git",