@better-auth/oauth-provider 1.7.0-beta.9 → 1.7.0-rc.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.
- package/dist/client-resource.d.mts +1 -1
- package/dist/client-resource.mjs +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +15 -2
- package/dist/{introspect-Bzbar3iY.mjs → introspect-DvHp2a64.mjs} +161 -11
- package/dist/{oauth-C7baUP-L.d.mts → oauth-BrNRbP2A.d.mts} +22 -2
- package/dist/{oauth-Di-k6QeJ.d.mts → oauth-ScTJEcFV.d.mts} +41 -1
- package/dist/{version-M87fotrf.mjs → version--aseHtsu.mjs} +1 -1
- package/package.json +7 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as ResourceServerMetadata } from "./oauth-
|
|
1
|
+
import { c as ResourceServerMetadata } from "./oauth-ScTJEcFV.mjs";
|
|
2
2
|
import { ResourceRequestInput, VerifyAccessTokenRequestOptions } from "better-auth/oauth2";
|
|
3
3
|
import { JWTPayload, JWTVerifyOptions } from "jose";
|
|
4
4
|
import { BetterAuthOptions } from "better-auth/types";
|
package/dist/client-resource.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-DO8lmoDw.mjs";
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version--aseHtsu.mjs";
|
|
3
3
|
import { t as raiseResourceServerChallenge } from "./resource-challenge-B-cqv4ur.mjs";
|
|
4
4
|
import { APIError } from "better-call";
|
|
5
5
|
import { logger } from "@better-auth/core/env";
|
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as buildSignedOAuthQuery } from "./signed-query-Df1MNiSH.mjs";
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version--aseHtsu.mjs";
|
|
3
3
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
4
4
|
//#region src/client.ts
|
|
5
5
|
const oauthProviderClient = () => {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as OAuthProviderExtension, B as StoreTokenType, C as OAuthConsent, D as OAuthOpaqueAccessToken, E as OAuthMetadataExtensionInput, F as OAuthTokenResponse, H as VerificationValue, I as OAuthUserInfoExtensionInput, L as Prompt, M as OAuthResource, N as OAuthResourceInput, O as OAuthOptions, P as OAuthTokenIssueParams, R as SchemaClient, S as OAuthClientResource, T as OAuthExtensionGrantHandlerInput, U as ClientRegistrationRequest, V as StoredAuthorizationQuery, W as ResourceUriSchema, _ as OAuthClaimExtensionInput, a as GrantType, b as OAuthClientAuthenticationResult, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthorizationQuery, h as OAuthAuthenticatedClient, i as Confirmation, j as OAuthRefreshToken, k as OAuthProviderApi, l as TokenEndpointAuthMethod, m as InitialAccessTokenAuthorization, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClientAuthenticationInput, w as OAuthExtensionGrantHandler, x as OAuthClientAuthenticationStrategy, y as OAuthClientAuthenticationRequest, z as Scope } from "./oauth-
|
|
2
|
-
import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-
|
|
1
|
+
import { A as OAuthProviderExtension, B as StoreTokenType, C as OAuthConsent, D as OAuthOpaqueAccessToken, E as OAuthMetadataExtensionInput, F as OAuthTokenResponse, H as VerificationValue, I as OAuthUserInfoExtensionInput, L as Prompt, M as OAuthResource, N as OAuthResourceInput, O as OAuthOptions, P as OAuthTokenIssueParams, R as SchemaClient, S as OAuthClientResource, T as OAuthExtensionGrantHandlerInput, U as ClientRegistrationRequest, V as StoredAuthorizationQuery, W as ResourceUriSchema, _ as OAuthClaimExtensionInput, a as GrantType, b as OAuthClientAuthenticationResult, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthorizationQuery, h as OAuthAuthenticatedClient, i as Confirmation, j as OAuthRefreshToken, k as OAuthProviderApi, l as TokenEndpointAuthMethod, m as InitialAccessTokenAuthorization, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClientAuthenticationInput, w as OAuthExtensionGrantHandler, x as OAuthClientAuthenticationStrategy, y as OAuthClientAuthenticationRequest, z as Scope } from "./oauth-ScTJEcFV.mjs";
|
|
2
|
+
import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-BrNRbP2A.mjs";
|
|
3
3
|
import { getSessionFromCtx } from "better-auth/api";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
5
|
import { AuthContext, GenericEndpointContext } from "@better-auth/core";
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { C as STANDARD_CLAIM_NAMES, D as getRequestedUserInfoClaims, E as filterClaimsRequestUserInfoClaims, S as STANDARD_CLAIMS, T as claimsRequestParameterSchema, _ as invalidateResourceCache, a as invalidateRefreshFamily, b as resolveResourcePolicy, c as ResourceUriSchema, d as clientRegistrationRequestSchema, f as JWS_ALGORITHMS, g as getResource, h as extractRepeatedResourceFromForm, i as getOAuthProviderApi, l as SafeUrlSchema, m as buildClientResourceLinkId, o as tokenEndpoint, p as assertIdentifierValid, r as decodeRefreshToken, s as userInfoEndpoint, t as introspectEndpoint, u as authorizationQuerySchema, v as isAudienceClaimAllowed, w as getSupportedClaims, x as seedResources, y as logEnforcePerClientResourcesResolution } from "./introspect-
|
|
1
|
+
import { C as STANDARD_CLAIM_NAMES, D as getRequestedUserInfoClaims, E as filterClaimsRequestUserInfoClaims, S as STANDARD_CLAIMS, T as claimsRequestParameterSchema, _ as invalidateResourceCache, a as invalidateRefreshFamily, b as resolveResourcePolicy, c as ResourceUriSchema, d as clientRegistrationRequestSchema, f as JWS_ALGORITHMS, g as getResource, h as extractRepeatedResourceFromForm, i as getOAuthProviderApi, l as SafeUrlSchema, m as buildClientResourceLinkId, o as tokenEndpoint, p as assertIdentifierValid, r as decodeRefreshToken, s as userInfoEndpoint, t as introspectEndpoint, u as authorizationQuerySchema, v as isAudienceClaimAllowed, w as getSupportedClaims, x as seedResources, y as logEnforcePerClientResourcesResolution } from "./introspect-DvHp2a64.mjs";
|
|
2
2
|
import { D as applyOAuthProviderMetadataExtensions, E as verifyOAuthQueryParams, F as getSupportedGrantTypes, L as isExtensionTokenEndpointAuthMethod, M as getClientDiscoveries, P as getSupportedAuthMethods, R as validateOAuthProviderExtensions, S as storeToken, T as validateClientCredentials, _ as removePromptFromQuery, a as getClient, b as searchParamsToQuery, c as getStoredToken, d as mergeDiscoveryMetadata, g as removeMaxAgeFromQuery, h as parsePrompt, i as extractClientCredentials, j as extendOAuthProvider, l as isPKCERequired, m as parseClientMetadata, n as decryptStoredClientSecret, o as getJwtPlugin, p as parseBearerToken, r as destructureCredentials, t as clientAllowsGrant, u as isSessionFreshForSignedQuery, w as toResourceList, x as storeClientSecret, y as resolveSubjectIdentifier } from "./utils-DO8lmoDw.mjs";
|
|
3
3
|
import { a as setSignedOAuthQueryParameterNames, i as postLoginClearedParam, n as canonicalizeOAuthQueryParams, o as signedQueryIssuedAtParam, r as getSignedQueryIssuedAt } from "./signed-query-Df1MNiSH.mjs";
|
|
4
4
|
import { n as consumeClientAssertion, r as isPrivateHostname } from "./client-assertion-D-tAYsKC.mjs";
|
|
5
|
-
import { t as PACKAGE_VERSION } from "./version
|
|
5
|
+
import { t as PACKAGE_VERSION } from "./version--aseHtsu.mjs";
|
|
6
6
|
import { t as raiseResourceServerChallenge } from "./resource-challenge-B-cqv4ur.mjs";
|
|
7
7
|
import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
|
|
8
8
|
import { isLoopbackHost, isLoopbackIP } from "@better-auth/core/utils/host";
|
|
@@ -2829,6 +2829,18 @@ const schema = {
|
|
|
2829
2829
|
type: "date",
|
|
2830
2830
|
required: false
|
|
2831
2831
|
},
|
|
2832
|
+
rotatedAt: {
|
|
2833
|
+
type: "date",
|
|
2834
|
+
required: false
|
|
2835
|
+
},
|
|
2836
|
+
rotationReplayResponse: {
|
|
2837
|
+
type: "string",
|
|
2838
|
+
required: false
|
|
2839
|
+
},
|
|
2840
|
+
rotationReplayExpiresAt: {
|
|
2841
|
+
type: "date",
|
|
2842
|
+
required: false
|
|
2843
|
+
},
|
|
2832
2844
|
authTime: {
|
|
2833
2845
|
type: "date",
|
|
2834
2846
|
required: false
|
|
@@ -3023,6 +3035,7 @@ const oauthProvider = (options) => {
|
|
|
3023
3035
|
accessTokenExpiresIn: 3600,
|
|
3024
3036
|
m2mAccessTokenExpiresIn: 3600,
|
|
3025
3037
|
refreshTokenExpiresIn: 2592e3,
|
|
3038
|
+
refreshTokenReuseInterval: 0,
|
|
3026
3039
|
allowUnauthenticatedClientRegistration: false,
|
|
3027
3040
|
allowDynamicClientRegistration: false,
|
|
3028
3041
|
disableJwtPlugin: false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
2
|
import { A as collectExtensionUserInfoClaims, C as toAudienceClaim, F as getSupportedGrantTypes, I as hasUserInfoClaimExtension, N as getExtensionGrantHandler, O as collectExtensionAccessTokenClaims, S as storeToken, T as validateClientCredentials, a as getClient, c as getStoredToken, f as normalizeTimestampValue, i as extractClientCredentials, k as collectExtensionIdTokenClaims, l as isPKCERequired, m as parseClientMetadata, n as decryptStoredClientSecret, o as getJwtPlugin, r as destructureCredentials, t as clientAllowsGrant, v as resolveSessionAuthTime, w as toResourceList, y as resolveSubjectIdentifier } from "./utils-DO8lmoDw.mjs";
|
|
3
3
|
import { APIError } from "better-auth/api";
|
|
4
|
-
import { generateRandomString } from "better-auth/crypto";
|
|
4
|
+
import { generateRandomString, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
|
5
5
|
import { APIError as APIError$1 } from "better-call";
|
|
6
6
|
import { logger } from "@better-auth/core/env";
|
|
7
7
|
import * as z from "zod";
|
|
@@ -1524,6 +1524,13 @@ async function createRefreshToken(ctx, opts, user, referenceId, authorizationCod
|
|
|
1524
1524
|
})).id,
|
|
1525
1525
|
token: await encodeRefreshToken(opts, token, sessionId)
|
|
1526
1526
|
};
|
|
1527
|
+
const rotatedAt = /* @__PURE__ */ new Date(iat * 1e3);
|
|
1528
|
+
const rotationUpdate = {
|
|
1529
|
+
revoked: rotatedAt,
|
|
1530
|
+
rotatedAt
|
|
1531
|
+
};
|
|
1532
|
+
const reuseInterval = opts.refreshTokenReuseInterval ?? 0;
|
|
1533
|
+
if (reuseInterval > 0) rotationUpdate.rotationReplayExpiresAt = /* @__PURE__ */ new Date((iat + reuseInterval) * 1e3);
|
|
1527
1534
|
if (!await ctx.context.adapter.incrementOne({
|
|
1528
1535
|
model: "oauthRefreshToken",
|
|
1529
1536
|
where: [{
|
|
@@ -1535,7 +1542,7 @@ async function createRefreshToken(ctx, opts, user, referenceId, authorizationCod
|
|
|
1535
1542
|
value: null
|
|
1536
1543
|
}],
|
|
1537
1544
|
increment: {},
|
|
1538
|
-
set:
|
|
1545
|
+
set: rotationUpdate
|
|
1539
1546
|
})) throw new APIError("BAD_REQUEST", {
|
|
1540
1547
|
error_description: "invalid refresh token",
|
|
1541
1548
|
error: "invalid_grant"
|
|
@@ -1604,6 +1611,126 @@ async function resolveDpopTokenBinding(ctx, opts, params) {
|
|
|
1604
1611
|
throw error;
|
|
1605
1612
|
}
|
|
1606
1613
|
}
|
|
1614
|
+
function normalizeReplayValues(values) {
|
|
1615
|
+
return values ? [...new Set(values)].sort() : void 0;
|
|
1616
|
+
}
|
|
1617
|
+
function sameReplayValues(left, right) {
|
|
1618
|
+
const normalizedLeft = normalizeReplayValues(left);
|
|
1619
|
+
const normalizedRight = normalizeReplayValues(right);
|
|
1620
|
+
if (normalizedLeft === void 0 || normalizedRight === void 0) return normalizedLeft === normalizedRight;
|
|
1621
|
+
if (normalizedLeft.length !== normalizedRight.length) return false;
|
|
1622
|
+
return normalizedLeft.every((value, index) => value === normalizedRight[index]);
|
|
1623
|
+
}
|
|
1624
|
+
function confirmationReplayKey(confirmation) {
|
|
1625
|
+
if (!confirmation) return;
|
|
1626
|
+
if ("jkt" in confirmation) return `jkt:${confirmation.jkt}`;
|
|
1627
|
+
return `x5t#S256:${confirmation["x5t#S256"]}`;
|
|
1628
|
+
}
|
|
1629
|
+
function sameConfirmation(left, right) {
|
|
1630
|
+
return confirmationReplayKey(left) === confirmationReplayKey(right);
|
|
1631
|
+
}
|
|
1632
|
+
function isConfirmation(value) {
|
|
1633
|
+
if (!value || typeof value !== "object") return false;
|
|
1634
|
+
const confirmation = value;
|
|
1635
|
+
return typeof confirmation.jkt === "string" && confirmation["x5t#S256"] === void 0 || typeof confirmation["x5t#S256"] === "string" && confirmation.jkt === void 0;
|
|
1636
|
+
}
|
|
1637
|
+
function isTokenType(value) {
|
|
1638
|
+
return value === "Bearer" || value === "DPoP";
|
|
1639
|
+
}
|
|
1640
|
+
function isOAuthTokenResponse(value) {
|
|
1641
|
+
if (!value || typeof value !== "object") return false;
|
|
1642
|
+
const response = value;
|
|
1643
|
+
return typeof response.access_token === "string" && typeof response.expires_in === "number" && typeof response.expires_at === "number" && isTokenType(response.token_type) && typeof response.scope === "string" && (response.refresh_token === void 0 || typeof response.refresh_token === "string") && (response.id_token === void 0 || typeof response.id_token === "string");
|
|
1644
|
+
}
|
|
1645
|
+
function isRefreshTokenRotationReplay(value) {
|
|
1646
|
+
if (!value || typeof value !== "object") return false;
|
|
1647
|
+
const replay = value;
|
|
1648
|
+
const request = replay.request;
|
|
1649
|
+
return !!request && Array.isArray(request.effectiveScopes) && request.effectiveScopes.every((scope) => typeof scope === "string") && (request.requestedResources === void 0 || Array.isArray(request.requestedResources) && request.requestedResources.every((resource) => typeof resource === "string")) && (request.confirmation === void 0 || isConfirmation(request.confirmation)) && isOAuthTokenResponse(replay.response);
|
|
1650
|
+
}
|
|
1651
|
+
function buildRefreshTokenRotationReplayRequest(params) {
|
|
1652
|
+
const requestedResources = normalizeReplayValues(params.requestedResources);
|
|
1653
|
+
return {
|
|
1654
|
+
effectiveScopes: normalizeReplayValues(params.effectiveScopes) ?? [],
|
|
1655
|
+
...requestedResources ? { requestedResources } : {},
|
|
1656
|
+
...params.confirmation ? { confirmation: params.confirmation } : {}
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
function sameRefreshTokenRotationReplayRequest(left, right) {
|
|
1660
|
+
return sameReplayValues(left.effectiveScopes, right.effectiveScopes) && sameReplayValues(left.requestedResources, right.requestedResources) && sameConfirmation(left.confirmation, right.confirmation);
|
|
1661
|
+
}
|
|
1662
|
+
async function encryptRefreshTokenRotationReplay(ctx, replay) {
|
|
1663
|
+
return symmetricEncrypt({
|
|
1664
|
+
key: ctx.context.secretConfig,
|
|
1665
|
+
data: JSON.stringify(replay)
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
async function decryptRefreshTokenRotationReplay(ctx, value) {
|
|
1669
|
+
const decrypted = await symmetricDecrypt({
|
|
1670
|
+
key: ctx.context.secretConfig,
|
|
1671
|
+
data: value
|
|
1672
|
+
});
|
|
1673
|
+
const parsed = JSON.parse(decrypted);
|
|
1674
|
+
if (!isRefreshTokenRotationReplay(parsed)) return;
|
|
1675
|
+
return parsed;
|
|
1676
|
+
}
|
|
1677
|
+
function isWithinRefreshTokenReuseInterval(refreshToken) {
|
|
1678
|
+
return !!refreshToken.rotatedAt && !!refreshToken.rotationReplayExpiresAt && refreshToken.rotationReplayExpiresAt >= /* @__PURE__ */ new Date();
|
|
1679
|
+
}
|
|
1680
|
+
async function storeRefreshTokenRotationReplay(ctx, opts, refreshToken, request, response) {
|
|
1681
|
+
if ((opts.refreshTokenReuseInterval ?? 0) <= 0) return;
|
|
1682
|
+
await ctx.context.adapter.update({
|
|
1683
|
+
model: "oauthRefreshToken",
|
|
1684
|
+
where: [{
|
|
1685
|
+
field: "id",
|
|
1686
|
+
value: refreshToken.id
|
|
1687
|
+
}],
|
|
1688
|
+
update: { rotationReplayResponse: await encryptRefreshTokenRotationReplay(ctx, {
|
|
1689
|
+
request,
|
|
1690
|
+
response
|
|
1691
|
+
}) }
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
async function getRefreshTokenRotationReplay(ctx, refreshToken, request) {
|
|
1695
|
+
if (!isWithinRefreshTokenReuseInterval(refreshToken)) return;
|
|
1696
|
+
if (!refreshToken.rotationReplayResponse) return;
|
|
1697
|
+
try {
|
|
1698
|
+
const replay = await decryptRefreshTokenRotationReplay(ctx, refreshToken.rotationReplayResponse);
|
|
1699
|
+
if (!replay || !sameRefreshTokenRotationReplayRequest(replay.request, request)) return;
|
|
1700
|
+
return replay.response;
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
ctx.context.logger.error("refresh token rotation replay failed", error);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
async function resolveRefreshTokenRotationReplayRequest(ctx, opts, params) {
|
|
1707
|
+
const iat = Math.floor(Date.now() / 1e3);
|
|
1708
|
+
const grantIssuance = await resolveResourceGrantIssuance(ctx, opts, {
|
|
1709
|
+
clientId: params.client.clientId,
|
|
1710
|
+
requestedScopes: params.scopes,
|
|
1711
|
+
resources: params.resources,
|
|
1712
|
+
refreshToken: params.refreshToken,
|
|
1713
|
+
iat,
|
|
1714
|
+
scopeExpiresAtSeconds: iat + (opts.accessTokenExpiresIn ?? 3600)
|
|
1715
|
+
});
|
|
1716
|
+
const confirmation = await resolveDpopTokenBinding(ctx, opts, {
|
|
1717
|
+
client: params.client,
|
|
1718
|
+
grantIssuance,
|
|
1719
|
+
refreshToken: params.refreshToken
|
|
1720
|
+
});
|
|
1721
|
+
return buildRefreshTokenRotationReplayRequest({
|
|
1722
|
+
effectiveScopes: grantIssuance.effectiveScopes,
|
|
1723
|
+
requestedResources: params.resources,
|
|
1724
|
+
confirmation
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
function replayRefreshTokenRotationResponse(ctx, response) {
|
|
1728
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1729
|
+
return ctx.json({
|
|
1730
|
+
...response,
|
|
1731
|
+
expires_in: Math.max(0, response.expires_at - now)
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1607
1734
|
async function createUserTokens(ctx, opts, params) {
|
|
1608
1735
|
const { client, scopes, user, grantType, referenceId, sessionId, nonce, refreshToken: existingRefreshToken, authTime, verificationValue } = params;
|
|
1609
1736
|
const iat = Math.floor(Date.now() / 1e3);
|
|
@@ -1695,7 +1822,7 @@ async function createUserTokens(ctx, opts, params) {
|
|
|
1695
1822
|
sid: sessionId
|
|
1696
1823
|
}, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims) : void 0]);
|
|
1697
1824
|
const idToken = isIdToken ? await createIdToken(ctx, opts, user, client, effectiveScopes, nonce, sessionId, authTime, accessToken, additionalIdTokenClaims) : void 0;
|
|
1698
|
-
|
|
1825
|
+
const responseBody = {
|
|
1699
1826
|
...customFields,
|
|
1700
1827
|
...params.tokenResponse ?? {},
|
|
1701
1828
|
access_token: accessToken,
|
|
@@ -1705,7 +1832,17 @@ async function createUserTokens(ctx, opts, params) {
|
|
|
1705
1832
|
refresh_token: refreshToken?.token,
|
|
1706
1833
|
scope: effectiveScopes.join(" "),
|
|
1707
1834
|
id_token: idToken
|
|
1708
|
-
}
|
|
1835
|
+
};
|
|
1836
|
+
if (existingRefreshToken?.id && refreshToken?.id) try {
|
|
1837
|
+
await storeRefreshTokenRotationReplay(ctx, opts, existingRefreshToken, buildRefreshTokenRotationReplayRequest({
|
|
1838
|
+
effectiveScopes,
|
|
1839
|
+
requestedResources: params.resources,
|
|
1840
|
+
confirmation
|
|
1841
|
+
}), responseBody);
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
ctx.context.logger.error("failed to store refresh token rotation replay", error);
|
|
1844
|
+
}
|
|
1845
|
+
return ctx.json(responseBody);
|
|
1709
1846
|
}
|
|
1710
1847
|
/** Checks verification value */
|
|
1711
1848
|
async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri, resource) {
|
|
@@ -1948,13 +2085,6 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
1948
2085
|
error_description: "invalid refresh token",
|
|
1949
2086
|
error: "invalid_grant"
|
|
1950
2087
|
});
|
|
1951
|
-
if (refreshToken.revoked) {
|
|
1952
|
-
await invalidateRefreshFamily(ctx, client_id, refreshToken.userId);
|
|
1953
|
-
throw new APIError("BAD_REQUEST", {
|
|
1954
|
-
error_description: "invalid refresh token",
|
|
1955
|
-
error: "invalid_grant"
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
2088
|
if (resources && refreshToken.resources && !resources.every((v) => refreshToken.resources?.includes(v))) throw new APIError("BAD_REQUEST", {
|
|
1959
2089
|
error_description: "requested resource invalid",
|
|
1960
2090
|
error: "invalid_target"
|
|
@@ -1969,6 +2099,26 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
1969
2099
|
});
|
|
1970
2100
|
}
|
|
1971
2101
|
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, requestedScopes ?? scopes, preVerified, "refresh_token", authMethod);
|
|
2102
|
+
if (refreshToken.revoked) {
|
|
2103
|
+
if (isWithinRefreshTokenReuseInterval(refreshToken)) {
|
|
2104
|
+
const replay = await getRefreshTokenRotationReplay(ctx, refreshToken, await resolveRefreshTokenRotationReplayRequest(ctx, opts, {
|
|
2105
|
+
client,
|
|
2106
|
+
refreshToken,
|
|
2107
|
+
scopes: requestedScopes ?? scopes,
|
|
2108
|
+
resources: resources ?? refreshToken.resources
|
|
2109
|
+
}));
|
|
2110
|
+
if (replay) return replayRefreshTokenRotationResponse(ctx, replay);
|
|
2111
|
+
throw new APIError("BAD_REQUEST", {
|
|
2112
|
+
error_description: "invalid refresh token",
|
|
2113
|
+
error: "invalid_grant"
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
await invalidateRefreshFamily(ctx, client_id, refreshToken.userId);
|
|
2117
|
+
throw new APIError("BAD_REQUEST", {
|
|
2118
|
+
error_description: "invalid refresh token",
|
|
2119
|
+
error: "invalid_grant"
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
1972
2122
|
const user = await ctx.context.internalAdapter.findUserById(refreshToken.userId);
|
|
1973
2123
|
if (!user) throw new APIError("BAD_REQUEST", {
|
|
1974
2124
|
error_description: "user not found",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as OAuthConsent,
|
|
1
|
+
import { C as OAuthConsent, L as Prompt, M as OAuthResource, O as OAuthOptions, a as GrantType, l as TokenEndpointAuthMethod, n as AuthServerMetadata, o as OAuthClient, u as TokenType, z as Scope } from "./oauth-ScTJEcFV.mjs";
|
|
2
2
|
import * as better_call0 from "better-call";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import * as jose from "jose";
|
|
@@ -561,7 +561,15 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
561
561
|
};
|
|
562
562
|
};
|
|
563
563
|
};
|
|
564
|
-
},
|
|
564
|
+
}, {
|
|
565
|
+
expires_in: number;
|
|
566
|
+
access_token: string;
|
|
567
|
+
expires_at: number;
|
|
568
|
+
token_type: TokenType;
|
|
569
|
+
refresh_token: string | undefined;
|
|
570
|
+
scope: string;
|
|
571
|
+
id_token: string | undefined;
|
|
572
|
+
}>;
|
|
565
573
|
oauth2Introspect: better_call0.StrictEndpoint<"/oauth2/introspect", {
|
|
566
574
|
method: "POST";
|
|
567
575
|
body: z.ZodObject<{
|
|
@@ -2301,6 +2309,18 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
2301
2309
|
type: "date";
|
|
2302
2310
|
required: false;
|
|
2303
2311
|
};
|
|
2312
|
+
rotatedAt: {
|
|
2313
|
+
type: "date";
|
|
2314
|
+
required: false;
|
|
2315
|
+
};
|
|
2316
|
+
rotationReplayResponse: {
|
|
2317
|
+
type: "string";
|
|
2318
|
+
required: false;
|
|
2319
|
+
};
|
|
2320
|
+
rotationReplayExpiresAt: {
|
|
2321
|
+
type: "date";
|
|
2322
|
+
required: false;
|
|
2323
|
+
};
|
|
2304
2324
|
authTime: {
|
|
2305
2325
|
type: "date";
|
|
2306
2326
|
required: false;
|
|
@@ -358,6 +358,18 @@ declare const schema: {
|
|
|
358
358
|
type: "date";
|
|
359
359
|
required: false;
|
|
360
360
|
};
|
|
361
|
+
rotatedAt: {
|
|
362
|
+
type: "date";
|
|
363
|
+
required: false;
|
|
364
|
+
};
|
|
365
|
+
rotationReplayResponse: {
|
|
366
|
+
type: "string";
|
|
367
|
+
required: false;
|
|
368
|
+
};
|
|
369
|
+
rotationReplayExpiresAt: {
|
|
370
|
+
type: "date";
|
|
371
|
+
required: false;
|
|
372
|
+
};
|
|
361
373
|
authTime: {
|
|
362
374
|
type: "date";
|
|
363
375
|
required: false;
|
|
@@ -1118,6 +1130,18 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
1118
1130
|
* @default 2592000 (30 days)
|
|
1119
1131
|
*/
|
|
1120
1132
|
refreshTokenExpiresIn?: number;
|
|
1133
|
+
/**
|
|
1134
|
+
* Seconds that a rotated refresh token can be reused to receive the same
|
|
1135
|
+
* token response for the same effective scopes, requested resources, and
|
|
1136
|
+
* sender constraint.
|
|
1137
|
+
*
|
|
1138
|
+
* Matching reuse inside this interval is treated as refresh-response replay,
|
|
1139
|
+
* not refresh-token replay. Set to `0` to treat any rotated refresh token
|
|
1140
|
+
* reuse as replay.
|
|
1141
|
+
*
|
|
1142
|
+
* @default 0
|
|
1143
|
+
*/
|
|
1144
|
+
refreshTokenReuseInterval?: number;
|
|
1121
1145
|
/**
|
|
1122
1146
|
* The amount of time in seconds that the authorization code is valid for.
|
|
1123
1147
|
*
|
|
@@ -2323,9 +2347,25 @@ interface OAuthRefreshToken<Scopes extends readonly Scope[] = InternallySupporte
|
|
|
2323
2347
|
expiresAt: Date;
|
|
2324
2348
|
createdAt: Date;
|
|
2325
2349
|
/**
|
|
2326
|
-
* When
|
|
2350
|
+
* When this token stopped being active.
|
|
2351
|
+
*
|
|
2352
|
+
* A token can be revoked by /oauth2/revoke or consumed by rotation. Use
|
|
2353
|
+
* `rotatedAt` to distinguish rotation from explicit revocation.
|
|
2327
2354
|
*/
|
|
2328
2355
|
revoked?: Date;
|
|
2356
|
+
/**
|
|
2357
|
+
* When this refresh token was consumed by rotation.
|
|
2358
|
+
*/
|
|
2359
|
+
rotatedAt?: Date;
|
|
2360
|
+
/**
|
|
2361
|
+
* Encrypted token endpoint response and request fingerprint to replay during
|
|
2362
|
+
* the configured refresh-token reuse interval.
|
|
2363
|
+
*/
|
|
2364
|
+
rotationReplayResponse?: string;
|
|
2365
|
+
/**
|
|
2366
|
+
* When the cached rotation response stops being replayable.
|
|
2367
|
+
*/
|
|
2368
|
+
rotationReplayExpiresAt?: Date;
|
|
2329
2369
|
/**
|
|
2330
2370
|
* The time the user originally authenticated.
|
|
2331
2371
|
* Persisted so refreshed ID tokens can include a correct `auth_time` claim.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/oauth-provider",
|
|
3
|
-
"version": "1.7.0-
|
|
3
|
+
"version": "1.7.0-rc.1",
|
|
4
4
|
"description": "An oauth provider plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,21 +57,21 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"jose": "^6.
|
|
60
|
+
"jose": "^6.2.3",
|
|
61
61
|
"zod": "^4.3.6"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"listhen": "^1.9.0",
|
|
65
65
|
"tsdown": "0.21.1",
|
|
66
|
-
"@better-auth/core": "1.7.0-
|
|
67
|
-
"better-auth": "1.7.0-
|
|
66
|
+
"@better-auth/core": "1.7.0-rc.1",
|
|
67
|
+
"better-auth": "1.7.0-rc.1"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"@better-auth/utils": "0.4.2",
|
|
71
71
|
"@better-fetch/fetch": "1.3.1",
|
|
72
|
-
"better-call": "1.3.
|
|
73
|
-
"@better-auth/core": "^1.7.0-
|
|
74
|
-
"better-auth": "^1.7.0-
|
|
72
|
+
"better-call": "1.3.7",
|
|
73
|
+
"@better-auth/core": "^1.7.0-rc.1",
|
|
74
|
+
"better-auth": "^1.7.0-rc.1"
|
|
75
75
|
},
|
|
76
76
|
"scripts": {
|
|
77
77
|
"build": "tsdown",
|