@gydr-ai/mcp-oauth 0.1.3 → 0.1.4

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.
@@ -1 +1,5 @@
1
- export {};
1
+ /**
2
+ * `Strict-Transport-Security` value: two years, subdomains included
3
+ * (RFC 6797). No `preload`: that is a decision for the domain owner.
4
+ */
5
+ export declare const STRICT_TRANSPORT_SECURITY = "max-age=63072000; includeSubDomains";
@@ -335,8 +335,10 @@ export interface McpAuthOptions {
335
335
  readonly lifetimes?: Lifetimes;
336
336
  /**
337
337
  * Seconds after a refresh token is used during which presenting it again
338
- * returns another token pair (a lost response, a retried request) instead
339
- * of ending the grant; `0` turns the window off. Default: 10.
338
+ * (a lost response, a retried request) returns the pair the first use
339
+ * issued instead of ending the grant; with a store that lacks
340
+ * `putRefreshTokenReplacement`, a new access token only. Never a second
341
+ * refresh token chain. `0` turns the window off. Default: 10.
340
342
  */
341
343
  readonly refreshReuseGraceSeconds?: number;
342
344
  /** Accept Client ID Metadata Document client ids. Default: `true`. */
@@ -583,6 +585,14 @@ export interface McpAuthOptions {
583
585
  * issuer and resource, for development. Default: `false`.
584
586
  */
585
587
  readonly allowInsecureLocalhost?: boolean;
588
+ /**
589
+ * Send `Strict-Transport-Security: max-age=63072000; includeSubDomains`
590
+ * on every response this package builds (endpoints, pages, redirects and
591
+ * `authenticate` / `unauthorized` refusals). Set `false` only when a proxy
592
+ * in front already sets the header, or when the issuer's subdomains must
593
+ * stay reachable over plain HTTP. Default: `true`.
594
+ */
595
+ readonly strictTransportSecurity?: boolean;
586
596
  /**
587
597
  * Clock in milliseconds since the epoch, for tests. A non-finite value
588
598
  * makes the request fail instead of disabling expiry checks.
@@ -366,6 +366,14 @@ export interface RefreshTokenRecord {
366
366
  readonly expiresAt: number;
367
367
  /** Set once, atomically, by `OAuthStore.useRefreshToken`. */
368
368
  readonly usedAt?: number;
369
+ /**
370
+ * The access and refresh token pair this token was exchanged for,
371
+ * encrypted by the `SecretSealer`. Set by the optional
372
+ * `OAuthStore.putRefreshTokenReplacement` after the token is spent, and
373
+ * returned by `getRefreshToken` and `useRefreshToken` from then on, so a
374
+ * retry inside `refreshReuseGraceSeconds` receives that same pair.
375
+ */
376
+ readonly sealedReplacement?: string;
369
377
  }
370
378
  /**
371
379
  * Result of `OAuthStore.useRefreshToken`.
@@ -35,8 +35,9 @@ import type { AccessTokenRecord, AuthorizationCodeRecord, ClientRecord, ConsentR
35
35
  * an idle period, and when a size cap is reached evict the least recently
36
36
  * used grant-less dynamic client first; never evict `static` clients.
37
37
  *
38
- * Only hashes of tokens and codes reach the store. Upstream tokens arrive
39
- * already encrypted by the `SecretSealer`.
38
+ * Only hashes of tokens and codes reach the store. Upstream tokens, and the
39
+ * pair a refresh token was exchanged for (`putRefreshTokenReplacement`),
40
+ * arrive already encrypted by the `SecretSealer`.
40
41
  *
41
42
  * @example
42
43
  * ```ts
@@ -179,6 +180,20 @@ export interface OAuthStore {
179
180
  getRefreshToken(tokenHash: string): Promise<RefreshTokenRecord | undefined>;
180
181
  /** Atomically set `usedAt` if it is not set yet. */
181
182
  useRefreshToken(tokenHash: string, now: number): Promise<UsedRefreshToken | undefined>;
183
+ /**
184
+ * Optional, recommended: save `sealedReplacement` on the (already used)
185
+ * refresh token record `tokenHash`, keeping every other field, `usedAt`
186
+ * included. `getRefreshToken` and `useRefreshToken` must return it from
187
+ * then on. A later write replaces an earlier one; nothing happens when the
188
+ * record is gone.
189
+ *
190
+ * With it, a refresh token presented again inside
191
+ * `refreshReuseGraceSeconds` receives exactly the pair its first exchange
192
+ * issued, so a copied refresh token can never start a second token chain.
193
+ * Without it, such a retry receives a new access token only, with no
194
+ * refresh token.
195
+ */
196
+ putRefreshTokenReplacement?(tokenHash: string, sealedReplacement: string): Promise<void>;
182
197
  /**
183
198
  * Optional: remember the answer to a consent submission for a few
184
199
  * seconds, keyed by a hash of the browser cookie and the form token, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gydr-ai/mcp-oauth",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "OAuth 2.1 authorization server and resource-server guard for remote MCP servers, in front of any OIDC identity provider. No token passthrough, consent on every authorization, DCR and Client ID Metadata Documents.",
5
5
  "keywords": [
6
6
  "mcp",