Skip to content

Commit a45c47c

Browse files
authored
Merge branch 'master' into dependabot/npm_and_yarn/type-is-2.1.0
2 parents 126cca2 + ac37118 commit a45c47c

13 files changed

Lines changed: 609 additions & 494 deletions

File tree

.github/workflows/deploy_docs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- name: Build with VitePress
4747
run: npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build
4848
- name: Upload artifact
49-
uses: actions/upload-pages-artifact@v4
49+
uses: actions/upload-pages-artifact@v5
5050
with:
5151
path: docs/.vitepress/dist
5252

@@ -61,4 +61,4 @@ jobs:
6161
steps:
6262
- name: Deploy to GitHub Pages
6363
id: deployment
64-
uses: actions/deploy-pages@v4
64+
uses: actions/deploy-pages@v5

SECURITY.md

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
11
# Security Policy
22

3+
This security policy covers the npm package [`@node-oauth/oauth2-server`](https://www.npmjs.com/package/@node-oauth/oauth2-server), maintained in the [`node-oauth/node-oauth2-server`](https://github.com/node-oauth/node-oauth2-server) repository.
4+
5+
Because this project is a security-sensitive OAuth 2.0 authorization server library (RFC 6749 / RFC 6750), we take vulnerability reports seriously and handle them under a coordinated-disclosure process. We are grateful to the security researchers who help keep this project and its users safe.
6+
37
## Supported Versions
48

5-
Use this section to tell people about which versions of your project are
6-
currently being supported with security updates.
9+
Security fixes are developed against the latest `5.x` release. To stay protected, keep your dependency up to date with the most recent published version.
710

811
| Version | Supported |
912
|---------|--------------------------------------------------|
1013
| 5.x.x | :white_check_mark: |
11-
| 4.x.x | :white_check_mark: but only high severity issues |
14+
| 4.x.x | :white_check_mark: but only high-severity issues |
1215
| 3.x.x | :x: |
1316
| < 3 | :x: |
1417

18+
The `5.x` line is actively maintained and receives all security fixes. The `4.x` line is a legacy line that receives backports for high-severity issues only. Versions `3.x` and older are unsupported and will not receive security updates.
19+
1520
## Reporting a Vulnerability
1621

17-
Report security vulnerabilities to info@jankuester.com
22+
**Please report vulnerabilities privately using GitHub's private vulnerability reporting — do not open a public issue, pull request, or discussion for a security bug.** Public disclosure before a fix is available puts every user of the library at risk.
23+
24+
To report a vulnerability:
25+
26+
1. Open the **[Report a vulnerability](https://github.com/node-oauth/node-oauth2-server/security/advisories/new)** form directly, or go to the repository's **[Security tab](https://github.com/node-oauth/node-oauth2-server/security)** and click **"Report a vulnerability"**.
27+
2. Fill in the title and description (only those two fields are required) and submit the report.
28+
29+
You will need to be signed in to GitHub. Submitting through this channel keeps the report private, notifies the maintainers directly, and automatically adds you as a collaborator on the draft advisory so we can discuss the issue with you and credit you when it is published.
30+
31+
Reports are handled by the maintainers, [Jan Küster (@jankapunkt)](https://github.com/jankapunkt) and [Dan Hensby (@dhensby)](https://github.com/dhensby).
32+
33+
## What to Include in a Report
34+
35+
To help us triage, reproduce, and fix the issue quickly, please include as much of the following as you can:
36+
37+
- A clear description of the vulnerability and its **security impact** — what an attacker can achieve by exploiting it.
38+
- The **affected version(s)**, and if known the relevant commit or branch.
39+
- **Steps to reproduce**, ideally with a minimal **proof of concept**.
40+
- Any **configuration or environment** needed to trigger the issue (for example the grant types, model behaviour, or framework adapter in use).
41+
- Optionally: a suggested fix or mitigation, your severity assessment, and whether you would like public credit in the advisory.
42+
43+
We need to be able to **reproduce** the vulnerability — just as we do with ordinary bug reports — before we can safely fix it, so clear reproduction steps and impact details are the most valuable thing you can provide.
1844

19-
Please specify exactly how the vulnerability is to be exploited so we can estimate how severe the consequences can be (unless you also can specify them, too).
45+
## What Happens Next
2046

21-
Please note that we need to reproduce the vulnerability (as like with bugs) in order to safely fix it.
47+
1. **Acknowledgement & triage.** We confirm receipt of your report and begin assessing it privately through the GitHub security advisory.
48+
2. **Reproduction.** We reproduce the issue to understand its impact and estimate severity. We may follow up with you for clarification or additional detail.
49+
3. **Private fix.** A fix is developed in private — within the draft advisory or a temporary private fork — until we can confirm the vulnerability is closed. If you would like to help, let us know and we can collaborate on the fix in a private fork; only a maintainer can merge it.
50+
4. **Validation.** All security fixes must pass the **full test suite and dependency audits** before they ship — a security fix is never released at the expense of correctness.
51+
5. **Release.** Once a fix is ready, we publish a new release promptly and update the supported release lines as appropriate.
52+
6. **Disclosure & credit.** We publish a GitHub Security Advisory (GHSA), request a CVE where warranted, and credit you for the report unless you ask otherwise. The project actively uses GitHub Security Advisories — for example, [GHSA-jhm7-29pj-4xvf](https://github.com/node-oauth/node-oauth2-server/security/advisories/GHSA-jhm7-29pj-4xvf) (CVE-2026-41213, a PKCE brute-force issue) was fixed in `5.3.0`.
2253

23-
A fix will be implemented in private until we can ensure the vulnerability is closed. A new release will immediately be published.
24-
If you want to provide a fix please let us know in the e-mail so we can setup a completely private repository to work on it together.
54+
## Disclosure Expectations
2555

26-
Finally, all security fixes will also require to pass all tests and audits.
56+
We follow **coordinated disclosure**. Please give us a reasonable opportunity to investigate and release a fix before disclosing the vulnerability publicly, and please do not share details — including via public issues, pull requests, or discussions — until a patched release is available and the advisory is published. We will keep you informed of our progress throughout, and we aim to resolve and disclose issues promptly.

docs/api/grant-types/authorization-code-grant-type.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* [new AuthorizationCodeGrantType(options)](#new_AuthorizationCodeGrantType_new)
88
* [.handle(request, client)](#AuthorizationCodeGrantType+handle)
99
* [.getAuthorizationCode(request, client)](#AuthorizationCodeGrantType+getAuthorizationCode) ⇒ <code>Promise.&lt;{user}&gt;</code>
10+
* [.verifyPKCE(request, code)](#AuthorizationCodeGrantType+verifyPKCE)
1011
* [.validateRedirectUri(request, code)](#AuthorizationCodeGrantType+validateRedirectUri)
1112
* [.revokeAuthorizationCode(code)](#AuthorizationCodeGrantType+revokeAuthorizationCode)
1213
* [.saveToken(user, client, authorizationCode, requestedScope)](#AuthorizationCodeGrantType+saveToken)
@@ -44,6 +45,23 @@ Get the authorization code.
4445
| request | <code>Request</code> |
4546
| client | <code>ClientData</code> |
4647

48+
<a name="AuthorizationCodeGrantType+verifyPKCE"></a>
49+
50+
### authorizationCodeGrantType.verifyPKCE(request, code)
51+
Verify PKCE code_verifier against the stored code_challenge.
52+
53+
This is called from handle() AFTER the authorization code has been
54+
revoked, so that a failed verification attempt consumes the code
55+
and prevents online brute-force guessing.
56+
57+
**Kind**: instance method of [<code>AuthorizationCodeGrantType</code>](#AuthorizationCodeGrantType)
58+
**See**: https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
59+
60+
| Param | Type |
61+
| --- | --- |
62+
| request | <code>Request</code> |
63+
| code | <code>AuthorizationCodeData</code> |
64+
4765
<a name="AuthorizationCodeGrantType+validateRedirectUri"></a>
4866

4967
### authorizationCodeGrantType.validateRedirectUri(request, code)

docs/api/handlers/authorize-handler.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,20 @@ Update response with the redirect uri and the state parameter, if available.
125125
<a name="AuthorizeHandler+getCodeChallengeMethod"></a>
126126

127127
### authorizeHandler.getCodeChallengeMethod()
128-
Get code challenge method from request or defaults to plain.
129-
https://www.rfc-editor.org/rfc/rfc7636#section-4.3
128+
Get code challenge method from request.
129+
130+
When `enablePlainPKCE` is false (the default), the "plain" method is
131+
rejected and the default (when no method is provided) is "S256".
132+
When `enablePlainPKCE` is true, "plain" is accepted and used as the
133+
default per RFC 7636 §4.3.
130134

131135
**Kind**: instance method of [<code>AuthorizeHandler</code>](#AuthorizeHandler)
132136
**Throws**:
133137

134138
- <code>InvalidRequestError</code> if request contains unsupported code_challenge_method
135139
(see https://www.rfc-editor.org/rfc/rfc7636#section-4.4)
136140

141+
**See**: https://www.rfc-editor.org/rfc/rfc7636#section-4.3
137142
<a name="responseTypes"></a>
138143

139144
## responseTypes

docs/api/model.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ An `Object` representing the authorization code and associated data. `code.clien
657657

658658
| Name | Type | Description |
659659
| --- | --- | --- |
660-
| code | <code>string</code> | The authorization code passed to `getAuthorizationCode()`. |
660+
| authorizationCode | <code>string</code> | The authorization code passed to `getAuthorizationCode()`. |
661661
| expiresAt | <code>Date</code> | The expiry time of the authorization code. |
662662
| redirectUri | <code>string</code> | The redirect URI of the authorization code. |
663663
| scope | <code>Array.&lt;string&gt;</code> | The authorized scope of the authorization code. |
@@ -674,7 +674,7 @@ An `Object` representing the client and associated data.
674674

675675
| Name | Type | Description |
676676
| --- | --- | --- |
677-
| id | <code>string</code> | The authorization code passed to `getAuthorizationCode()`. |
677+
| id | <code>string</code> | A unique string identifying the client. |
678678
| redirectUris | <code>Array.&lt;string&gt;</code> | Redirect URIs allowed for the client. Required for the `authorization_code` grant. |
679679
| grants | <code>Array.&lt;string&gt;</code> | Grant types allowed for the client. |
680680
| accessTokenLifetime | <code>number</code> | Client-specific lifetime of generated access tokens in seconds. |

docs/api/pkce/pkce.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## Modules
2+
3+
<dl>
4+
<dt><a href="#module_pkce">pkce</a></dt>
5+
<dd></dd>
6+
</dl>
7+
8+
## Constants
9+
10+
<dl>
11+
<dt><a href="#codeChallengeAndVerifierRegexp">codeChallengeAndVerifierRegexp</a> : <code>RegExp</code></dt>
12+
<dd><p>ABNF for &quot;code_verifier&quot; and &quot;code_challenge&quot; is as follows.</p>
13+
<p>code-verifier = 43*128unreserved
14+
unreserved = ALPHA / DIGIT / &quot;-&quot; / &quot;.&quot; / &quot;_&quot; / &quot;~&quot;
15+
ALPHA = %x41-5A / %x61-7A
16+
DIGIT = %x30-39</p>
17+
</dd>
18+
</dl>
19+
120
<a name="module_pkce"></a>
221

322
## pkce
@@ -62,3 +81,14 @@ Checks if the code challenge method is one of the supported methods
6281
| --- | --- |
6382
| method | <code>String</code> |
6483

84+
<a name="codeChallengeAndVerifierRegexp"></a>
85+
86+
## codeChallengeAndVerifierRegexp : <code>RegExp</code>
87+
ABNF for "code_verifier" and "code_challenge" is as follows.
88+
89+
code-verifier = 43*128unreserved
90+
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
91+
ALPHA = %x41-5A / %x61-7A
92+
DIGIT = %x30-39
93+
94+
**Kind**: global constant

docs/api/server.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Instantiates `OAuth2Server` using the supplied model.
4343
| [options.requireClientAuthentication] | <code>object</code> \| <code>boolean</code> | <code>object</code> | Require a client secret for grant types (names as keys). Defaults to `true` for all grant types. |
4444
| [options.alwaysIssueNewRefreshToken] | <code>boolean</code> | <code>true</code> | Always revoke the used refresh token and issue a new one for the `refresh_token` grant. |
4545
| [options.extendedGrantTypes] | <code>object</code> | <code>object</code> | Additional supported grant types. |
46+
| [options.enablePlainPKCE] | <code>boolean</code> | <code>false</code> | Allow the use of the `plain` code challenge method for PKCE. This is not recommended for production environments. |
4647

4748
**Example**
4849
```js

docs/guide/getting-started.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,45 @@ oauth.authenticate(request, response)
8181
});
8282
```
8383

84+
### Sending the response
85+
86+
This library is **framework-agnostic and does not send the HTTP response for you**.
87+
`authenticate()`, `authorize()` and `token()` resolve with their result *and*
88+
populate the `Response` object you passed in — setting `response.status`,
89+
`response.headers` and `response.body`. It is up to you to copy those onto your
90+
framework's real response and send it. For `authorize()` this is how the redirect
91+
happens: the `location` header is set on the `Response`, but you still have to
92+
issue it.
93+
94+
A minimal Express example for the authorization endpoint:
95+
96+
```js
97+
app.get('/authorize', async (req, res) => {
98+
// Wrap the framework objects in NEW variables — see the warning below.
99+
const request = new Request(req);
100+
const response = new Response(res);
101+
102+
try {
103+
await oauth.authorize(request, response, { authenticateHandler });
104+
} catch (err) {
105+
return res.status(err.code || 500).json({ error: err.name, error_description: err.message });
106+
}
107+
108+
// The library populated `response`; now you send it. For authorize that is a redirect:
109+
res.set(response.headers); // includes the `location` header
110+
return res.status(response.status).end(); // 302 -> the browser follows `location`
111+
});
112+
```
113+
114+
> **Do not reassign your framework's `req`/`res`** to the library's objects
115+
> (e.g. `res = new Response(res)`). Doing so discards framework methods such as
116+
> `res.redirect()`, and is a common cause of a request appearing to "hang".
117+
118+
If you use Express or Koa, prefer the official adapters
119+
([express-oauth-server](https://www.npmjs.com/package/@node-oauth/express-oauth-server),
120+
[koa-oauth-server](https://npmjs.org/package/koa-oauth-server)), which take care of
121+
all of this for you.
122+
84123
The most crucial part in this setup is the `model`.
85124
It acts as the bridge between the OAuth2 server library and your system.
86125

index.d.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ declare namespace OAuth2Server {
258258
* Invoked to retrieve a client using a client id or a client id/client secret combination, depending on the grant type.
259259
*
260260
*/
261-
getClient(clientId: string, clientSecret: string): Promise<Client | Falsey>;
261+
getClient(clientId: string, clientSecret?: string): Promise<Client | Falsey>;
262262

263263
/**
264264
* Invoked to save an access token and optionally a refresh token, depending on the grant type.
265265
*
266266
*/
267-
saveToken(token: Token, client: Client, user: User): Promise<Token | Falsey>;
267+
saveToken(token: Omit<Token, 'client' | 'user'>, client: Client, user: User): Promise<Token | Falsey>;
268268
}
269269

270270
interface RequestAuthenticationModel {
@@ -464,6 +464,11 @@ declare namespace OAuth2Server {
464464
* A human-readable error message.
465465
*/
466466
message: string;
467+
468+
/**
469+
* The original error, present when this error was constructed by wrapping another `Error`.
470+
*/
471+
inner?: Error;
467472
}
468473

469474
class AccessDeniedError extends OAuthError {}

lib/grant-types/refresh-token-grant-type.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ class RefreshTokenGrantType extends AbstractGrantType {
5454

5555
let token;
5656
token = await this.getRefreshToken(request, client);
57-
token = await this.revokeToken(token);
5857

58+
// Validate scope before revoking token to prevent destroying tokens on scope validation errors
5959
const scope = this.getScope(request, token);
6060

61+
token = await this.revokeToken(token);
62+
6163
return this.saveToken(token.user, client, scope);
6264
}
6365

0 commit comments

Comments
 (0)