Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .aiignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# An .aiignore file follows the same syntax as a .gitignore file.
# .gitignore documentation: https://git-scm.com/docs/gitignore

# Dependencies and repository metadata
node_modules/
.git/
Comment thread
jankapunkt marked this conversation as resolved.

# Local secrets and credentials
.env
.env.*
.npmrc
*.pem
*.key

# Local IDE and operating-system files
.idea/
.vscode/
*.iml
.DS_Store

# Logs and temporary files
*.log
*.tmp

# Build output
dist/
build/
out/

# Coverage output (nyc/Istanbul)
coverage/
.nyc_output/
lib-cov/

# Generated documentation and VitePress output; edit source JSDoc and guides instead
/docs/api/
/docs/_build/
/docs/.vitepress/cache/
/docs/.vitepress/dist/
# Generated by docs:setup; read /CONTRIBUTING.md instead

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, take or leave: docs/guide/contributing.md is actually a committed symlink, and docs:setup just re-creates it. Same for AGENTS.md L149-150. "Generated" might send an agent looking for a build step.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I didn't know that symlinks can be commited. In that case I would keep line 41 and rename line 40 to something that will not burn tokens by looking up any build steps when reading this file. However, I think the missing key element is, that /docs/guide/contributing.md should also go in .gitignore

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, git can track symlinks (it stores the target path). Untracking it makes sense since docs:setup re-creates it anyway, but it'd need a bit more than the .gitignore entry: git rm --cached docs/guide/contributing.md to actually untrack it, and docs:dev running docs:setup first, otherwise a fresh clone gets a broken Contributing link in the sidebar (docs:build already does this). I'd do that as a separate small PR. For this one, just rewording line 40 is fine, e.g. # Symlink to /CONTRIBUTING.md (re-created by docs:setup); read the root file instead.

/docs/guide/contributing.md
178 changes: 178 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# AGENTS.md

## Scope

These instructions apply only to the `node-oauth2-server` repository, the framework-agnostic OAuth 2.0 server library
published as `@node-oauth/oauth2-server`.

The sibling `express-oauth-server` repository is outside this file’s scope. Release CI checks out the Express adapter
as an external integration-test target; it is not a runtime dependency of this package. Changes to the adapter’s code,
documentation, security policy, and release process are outside this scope.

Run package commands from the `node-oauth2-server` repository root; do not expect a parent workspace-level install or
test command. Before changing the package, check `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, and `package.json`.

## AI context exclusions

Before exploring the repository, read [`.aiignore`](./.aiignore) and apply its patterns using gitignore syntax,
relative to this repository root. Agents must follow these exclusions explicitly: exclude matching paths from routine
file listings, searches, reads, and manual edits, including when using tools that do not honor `.aiignore` themselves.
Access an excluded path only when the user’s task specifically requires it. Build, test, coverage, and documentation
commands may create or update excluded output; use the source files when making changes to generated content.

## Project priorities

- Preserve OAuth 2.0 interoperability and security. Review behavior against the relevant standards, especially RFC 6749,
RFC 6750, RFC 7636, RFC 8252, and the threat-model guidance in RFC 6819.
- Keep `node-oauth2-server` framework-agnostic. Framework-specific behavior belongs in adapters maintained separately
from this repository.
- Treat authentication, authorization, token, grant, PKCE, parsing, and error-handling changes as security-sensitive.
Avoid weakening validation or disclosure of credentials and tokens in logs, errors, tests, or documentation.
- The public API includes JavaScript entry points and TypeScript declarations. Update `index.d.ts` when a public API or
type contract changes.
- The package uses CommonJS entry points and Promise/`async`-oriented APIs. Preserve existing public behavior unless a
change is intentional, documented, and tested.

## Repository layout

- Runtime code: `index.js` and `lib/`
- Type declarations: `index.d.ts`
- Tests: `test/` (Mocha files matching `test/**/*_test.js`)
- Test suites: `test/unit/`, `test/integration/`, and `test/compliance/`; shared fixtures/helpers: `test/helpers/`
- Mocha configuration: `.mocharc.yml`; shared Chai assertions: `test/assertions.js`
- Hand-authored documentation: `docs/guide/` and the VitePress home/configuration files
- Generated API documentation: `docs/api/`
- Formatting and linting: Biome, configured in `biome.json`
- CI and release validation: `.github/workflows/`

## Library architecture

The library implements OAuth workflows without owning HTTP routing, a database, user sessions, or a consent UI.
Applications and framework adapters supply those pieces. The main flow is: adapter creates `Request`/`Response`
instances, calls `OAuth2Server`, and sends the populated response; handlers delegate persistence and policy to the
application’s model.

- **Public facade:** `index.js` exports the `OAuth2Server` constructor, `Request`, `Response`, `AbstractGrantType`, and
error classes. `lib/server.js` exposes `authenticate()`, `authorize()`, and `token()`. Each call merges method
defaults, constructor options, and per-call options in that order, creates a handler, and returns its Promise.
`index.d.ts` describes the public TypeScript contracts.
- **HTTP boundary:** `lib/request.js` wraps already-parsed headers, method, query, and body; `lib/response.js` holds
status, headers, and body. Headers are normalized to lowercase. Handlers require these wrapper instances and mutate
the response without sending it. The caller must handle both the resolved result or rejected error and the response
state, including redirects and authentication headers.
- **Resource authentication:** `lib/handlers/authenticate-handler.js` extracts a bearer token, rejects conflicting
token sources, calls `model.getAccessToken()`, checks expiry, and calls `model.verifyScope()` when scopes are
requested. It returns the stored token object and manages scope and `WWW-Authenticate` headers.
- **Authorization endpoint:** `lib/handlers/authorize-handler.js` validates the client, redirect URI, state, and scope,
obtains the user through a supplied `authenticateHandler` (or bearer authentication by default), and saves an
authorization code through the model. `lib/response-types/code-response-type.js` builds the code redirect. Only the
`code` response type is registered; `token-response-type.js` is an unimplemented placeholder.
- **Token endpoint:** `lib/handlers/token-handler.js` validates form-encoded POST requests, obtains client credentials,
delegates client authentication to `model.getClient()`, checks the client’s allowed grants, and dispatches by
`grant_type`. The saved token data is returned to the caller; `lib/models/token-model.js` validates and prepares it
for `lib/token-types/bearer-token-type.js` to serialize into the OAuth response, with cache-prevention headers.
Internal scopes are arrays; the token response converts them to a space-delimited string. Bearer is the issued token
type; `mac-token-type.js` is an unimplemented placeholder.
- **Grant implementations:** `lib/grant-types/` contains authorization-code, client-credentials, password, and
refresh-token workflows. `AbstractGrantType` shares token generation, expiration, and scope handling. Grants obtain
users and credentials through the model and persist results through `saveToken()`. Custom grant constructors are
registered through `extendedGrantTypes` and implement `handle(request, client)`; the exported base class provides
reusable helpers. Client-specific token lifetimes override server defaults.
- **Storage and policy boundary:** `options.model` is an application-supplied object whose required methods depend on
the selected workflow. Hooks cover client/user lookup, token/code lookup and persistence, revocation, scope policy,
redirect validation, and optional token/code generation. Hooks may return plain values or Promises. `lib/model.js`
documents this contract and provides throwing stubs and a `Model.from()` helper; it is not a persistence layer and
is not exported by the package entry point. In particular, `getClient()` must enforce per-client secret policy;
PKCE and grant-level secret-presence options do not replace confidential-client authentication.
- **Security-sensitive sequencing:** authorization-code exchange validates the stored code and client, revokes the
code, then checks PKCE and redirect binding before issuing tokens. A failed PKCE attempt therefore consumes the code.
Refresh exchange validates token ownership, expiry, and requested scope before revocation; scopes cannot expand,
and refresh-token rotation is enabled by default. Preserve these orderings when modifying grant logic.
- **Shared protocol support:** `lib/pkce/pkce.js` and `lib/utils/` provide PKCE, cryptographic token generation, scope
parsing, and date/string helpers. Default token generation uses Node’s cryptographic random bytes; model hooks may
override it. PKCE defaults to `S256`, allows `plain` only when explicitly enabled, and can be required through
`requirePKCE`. `lib/errors/` defines the `OAuthError` hierarchy with OAuth names and HTTP status codes; handlers
translate errors into endpoint-specific bodies, redirects, or headers and reject the operation. Token and
authorization handlers normalize caught programming/configuration errors to `ServerError`.

## Setup and validation

Use the lockfile and install dependencies from the repository root:

```bash
npm ci
```
Comment thread
jankapunkt marked this conversation as resolved.

If you change dependencies, regenerate `package-lock.json` with npm 11 (the version CI uses), including when resolving merge conflicts. Older npm versions drop the cross-platform optional dependencies, and `npm ci` then fails in CI even though it works locally.

Validation commands:

```bash
npm test # pretest runs Biome check, then Mocha runs all three test suites
npm run lint
npm run format
npm run test:coverage
npm run docs:build # for documentation or public JSDoc changes
```

Code style is enforced by the Biome linter and formatter configured in `biome.json`. Both `npm run lint` and
`npm run format` must pass before every commit, along with `npm test`. Use `npm run lint:fix` and `npm run format:fix`
only when you intend to apply automated changes. The configured formatter uses single quotes, spaces, trailing
commas, and a 120-column line width for JavaScript.

Biome currently checks `index.js`, `lib/**/*.js`, and `test/**/*.js`. A passing Biome check does not validate Markdown,
TypeScript declarations, documentation tooling, or workflow files; review those changes separately.

Write tests with Mocha and Chai, using Chai’s `should` style assertions. Use Sinon for stubbing, and restore stubs and
fake timers after each test so they cannot affect other tests. Reuse the existing test helpers and shared assertions.

Run focused tests during iteration, but run the full package test, lint, and format commands before submitting a
change. Add or update tests for every bug fix and feature. Include regression coverage for security-sensitive
behavior and boundary cases.

For a focused run, use `npm test -- --grep 'suite or test name'`; this retains the Biome `pretest` check.

Measure test coverage with nyc (Istanbul) via `npm run test:coverage`. Strive for the highest possible test coverage
across statements, branches, functions, and lines, including error paths and boundary cases. The test workflows
currently enforce a minimum coverage of 98% using `coverage/lcov.info`. The coverage command does not run the
`pretest` hook, so it does not replace the required Biome checks.

`package.json` is the source of truth for supported Node.js versions. Follow its `engines.node` declaration when
developing, rather than prescribing a separate version policy here. If a change relies on a newer Node.js feature,
verify it against that declaration and document any intentional compatibility change. Development tools may require a
newer Node.js version than the library’s runtime minimum; check their engine requirements if setup fails without
changing the library’s support policy merely to match the local toolchain.

## Source and documentation workflow

- Add or update JSDoc for new or changed public APIs and keep `index.d.ts` in sync.
- Do not manually edit files under `docs/api/`. Update source JSDoc in `lib/`, then run `npm run docs:api` from the
repository root.
- Edit the guide under `docs/guide/` directly. `npm run docs:setup` creates the contribution-guide link used by the docs
build; edit the root `CONTRIBUTING.md` rather than `docs/guide/contributing.md`.
- Use the `docs:*` scripts in `package.json`; the `npm run docs` example in `CONTRIBUTING.md` is outdated.
- Keep README, API documentation, migration notes, and type declarations aligned with behavior. Do not update generated
output without checking whether the corresponding source change is present.
- Do not edit `CHANGELOG.md` or bump release versions for ordinary feature or bug-fix pull requests; maintainers handle
release bookkeeping.

## Contributions and Git workflow

Follow [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the established contribution process, including opening or discussing
an issue before an ordinary feature or bug-fix pull request. Follow the private reporting process below for security
issues. The branching policy below takes precedence over the guide’s outdated `development` instructions.

Use trunk-based development with `master` as the trunk. The `development` branch is deprecated. Create feature or fix
branches from `master`, keep them closely aligned with it, and target pull requests to `master`. The `master` name is
retained for historical continuity.

Follow the commit message convention in [`CONTRIBUTING.md`](./CONTRIBUTING.md).

Keep commits focused, avoid unrelated dependency updates, describe test/reproduction steps in pull requests, and do not
include secrets or real credentials in fixtures, examples, logs, or documentation.

## Security reporting

Never report a suspected vulnerability in a public issue, pull request, or discussion. Follow
[`SECURITY.md`](./SECURITY.md), which directs reports to GitHub private vulnerability reporting. Before releasing a
security fix, the policy requires the full test suite and dependency audits to pass.
Loading