oauth4webapi
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -124,2 +124,6 @@ type JsonObject = { | ||
export type JWSAlgorithm = 'PS256' | 'ES256' | 'RS256' | 'EdDSA' | 'ES384' | 'PS384' | 'RS384' | 'ES512' | 'PS512' | 'RS512'; | ||
/** @ignore during Documentation generation but part of the public API */ | ||
export declare const clockSkew: unique symbol; | ||
/** @ignore during Documentation generation but part of the public API */ | ||
export declare const clockTolerance: unique symbol; | ||
/** | ||
@@ -446,2 +450,46 @@ * Authorization Server Metadata | ||
default_max_age?: number; | ||
/** | ||
* Use to adjust the client's assumed current time. Positive and negative finite values | ||
* representing seconds are allowed. Default is `0` (Date.now() + 0 seconds is used). | ||
* | ||
* @ignore during Documentation generation but part of the public API | ||
* | ||
* @example Client's local clock is mistakenly 1 hour in the past | ||
* | ||
* ```ts | ||
* const client: oauth.Client = { | ||
* client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428', | ||
* // ... other metadata | ||
* [oauth.clockSkew]: +(60 * 60), | ||
* } | ||
* ``` | ||
* | ||
* @example Client's local clock is mistakenly 1 hour in the future | ||
* | ||
* ```ts | ||
* const client: oauth.Client = { | ||
* client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428', | ||
* // ... other metadata | ||
* [oauth.clockSkew]: -(60 * 60), | ||
* } | ||
* ``` | ||
*/ | ||
[clockSkew]?: number; | ||
/** | ||
* Use to set allowed client's clock tolerance when checking DateTime JWT Claims. Only positive | ||
* finite values representing seconds are allowed. Default is `30` (30 seconds). | ||
* | ||
* @ignore during Documentation generation but part of the public API | ||
* | ||
* @example Tolerate 30 seconds clock skew when validating JWT claims like `exp` or `nbf`. | ||
* | ||
* ```ts | ||
* const client: oauth.Client = { | ||
* client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428', | ||
* // ... other metadata | ||
* [oauth.clockTolerance]: 30, | ||
* } | ||
* ``` | ||
*/ | ||
[clockTolerance]?: number; | ||
[metadata: string]: JsonValue | undefined; | ||
@@ -634,2 +682,12 @@ } | ||
export interface ProtectedResourceRequestOptions extends Omit<HttpRequestOptions, 'headers'>, DPoPRequestOptions { | ||
/** | ||
* Use to adjust the client's assumed current time. Positive and negative finite values | ||
* representing seconds are allowed. Default is `0` (Date.now() + 0 seconds is used). | ||
* | ||
* This option only affects the request if the {@link ProtectedResourceRequestOptions.DPoP DPoP} | ||
* option is also used. | ||
* | ||
* @ignore during Documentation generation but part of the public API | ||
*/ | ||
clockSkew?: number; | ||
} | ||
@@ -636,0 +694,0 @@ /** |
let USER_AGENT; | ||
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) { | ||
const NAME = 'oauth4webapi'; | ||
const VERSION = 'v2.1.0'; | ||
const VERSION = 'v2.2.0'; | ||
USER_AGENT = `${NAME}/${VERSION}`; | ||
} | ||
export const clockSkew = Symbol(); | ||
export const clockTolerance = Symbol(); | ||
const encoder = new TextEncoder(); | ||
@@ -324,2 +326,15 @@ const decoder = new TextDecoder(); | ||
} | ||
function getClockSkew(client) { | ||
if (Number.isFinite(client[clockSkew])) { | ||
return client[clockSkew]; | ||
} | ||
return 0; | ||
} | ||
function getClockTolerance(client) { | ||
const tolerance = client[clockTolerance]; | ||
if (Number.isFinite(tolerance) && Math.sign(tolerance) !== -1) { | ||
return tolerance; | ||
} | ||
return 30; | ||
} | ||
function epochTime() { | ||
@@ -329,3 +344,3 @@ return Math.floor(Date.now() / 1000); | ||
function clientAssertion(as, client) { | ||
const now = epochTime(); | ||
const now = epochTime() + getClockSkew(client); | ||
return { | ||
@@ -442,3 +457,3 @@ jti: randomBytes(), | ||
parameters.set('client_id', client.client_id); | ||
const now = epochTime(); | ||
const now = epochTime() + getClockSkew(client); | ||
const claims = { | ||
@@ -480,3 +495,3 @@ ...Object.fromEntries(parameters.entries()), | ||
} | ||
async function dpopProofJwt(headers, options, url, htm, accessToken) { | ||
async function dpopProofJwt(headers, options, url, htm, clockSkew, accessToken) { | ||
const { privateKey, publicKey, nonce = dpopNonces.get(url.origin) } = options; | ||
@@ -495,3 +510,3 @@ if (!isPrivateKey(privateKey)) { | ||
} | ||
const now = epochTime(); | ||
const now = epochTime() + clockSkew; | ||
const proof = await jwt({ | ||
@@ -539,3 +554,3 @@ alg: keyToJws(privateKey), | ||
if (options?.DPoP !== undefined) { | ||
await dpopProofJwt(headers, options.DPoP, url, 'POST'); | ||
await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client)); | ||
} | ||
@@ -653,3 +668,3 @@ return authenticatedRequest(as, client, 'POST', url, body, headers, options); | ||
else { | ||
await dpopProofJwt(headers, options.DPoP, url, 'GET', accessToken); | ||
await dpopProofJwt(headers, options.DPoP, url, 'GET', getClockSkew({ [clockSkew]: options?.clockSkew }), accessToken); | ||
headers.set('authorization', `DPoP ${accessToken}`); | ||
@@ -680,3 +695,6 @@ } | ||
} | ||
return protectedResourceRequest(accessToken, 'GET', url, headers, null, options); | ||
return protectedResourceRequest(accessToken, 'GET', url, headers, null, { | ||
...options, | ||
clockSkew: getClockSkew(client), | ||
}); | ||
} | ||
@@ -782,3 +800,3 @@ let jwksCache; | ||
assertReadableResponse(response); | ||
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck) | ||
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client)) | ||
.then(validateOptionalAudience.bind(undefined, client.client_id)) | ||
@@ -839,3 +857,3 @@ .then(validateOptionalIssuer.bind(undefined, as.issuer)); | ||
if (options?.DPoP !== undefined) { | ||
await dpopProofJwt(headers, options.DPoP, url, 'POST'); | ||
await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client)); | ||
} | ||
@@ -912,3 +930,3 @@ return authenticatedRequest(as, client, 'POST', url, parameters, headers, options); | ||
if (json.id_token) { | ||
const { claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck) | ||
const { claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client)) | ||
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iat', 'iss', 'sub'])) | ||
@@ -1017,4 +1035,4 @@ .then(validateIssuer.bind(undefined, as.issuer)) | ||
} | ||
const now = epochTime(); | ||
const tolerance = 30; | ||
const now = epochTime() + getClockSkew(client); | ||
const tolerance = getClockTolerance(client); | ||
if (claims.auth_time + maxAge < now - tolerance) { | ||
@@ -1146,3 +1164,3 @@ throw new OPE('too much time has elapsed since the last End-User authentication'); | ||
assertReadableResponse(response); | ||
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck) | ||
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client)) | ||
.then(checkJwtType.bind(undefined, 'token-introspection+jwt')) | ||
@@ -1295,3 +1313,3 @@ .then(validatePresence.bind(undefined, ['aud', 'iat', 'iss'])) | ||
const noSignatureCheck = Symbol(); | ||
async function validateJwt(jws, checkAlg, getKey) { | ||
async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance) { | ||
const { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.'); | ||
@@ -1337,4 +1355,3 @@ if (length === 5) { | ||
} | ||
const now = epochTime(); | ||
const tolerance = 30; | ||
const now = epochTime() + clockSkew; | ||
if (claims.exp !== undefined) { | ||
@@ -1344,3 +1361,3 @@ if (typeof claims.exp !== 'number') { | ||
} | ||
if (claims.exp <= now - tolerance) { | ||
if (claims.exp <= now - clockTolerance) { | ||
throw new OPE('unexpected JWT "exp" (expiration time) claim value, timestamp is <= now()'); | ||
@@ -1363,3 +1380,3 @@ } | ||
} | ||
if (claims.nbf > now + tolerance) { | ||
if (claims.nbf > now + clockTolerance) { | ||
throw new OPE('unexpected JWT "nbf" (not before) claim value, timestamp is > now()'); | ||
@@ -1391,3 +1408,3 @@ } | ||
} | ||
const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options)) | ||
const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(client), getClockTolerance(client)) | ||
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iss'])) | ||
@@ -1394,0 +1411,0 @@ .then(validateIssuer.bind(undefined, as.issuer)) |
{ | ||
"name": "oauth4webapi", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "OAuth 2 / OpenID Connect for Web Platform API JavaScript runtimes", | ||
@@ -65,19 +65,19 @@ "keywords": [ | ||
"devDependencies": { | ||
"@esbuild-kit/esm-loader": "^2.5.1", | ||
"@types/node": "^18.11.9", | ||
"@types/qunit": "^2.19.3", | ||
"ava": "^5.1.0", | ||
"edge-runtime": "^2.0.4", | ||
"esbuild": "^0.17.0", | ||
"jose": "^4.11.1", | ||
"patch-package": "^6.5.0", | ||
"prettier": "^2.8.0", | ||
"@esbuild-kit/esm-loader": "^2.5.5", | ||
"@types/node": "^18.15.0", | ||
"@types/qunit": "^2.19.4", | ||
"ava": "^5.2.0", | ||
"edge-runtime": "^2.1.2", | ||
"esbuild": "^0.17.11", | ||
"jose": "^4.13.1", | ||
"patch-package": "^6.5.1", | ||
"prettier": "^2.8.4", | ||
"prettier-plugin-jsdoc": "^0.4.2", | ||
"qunit": "^2.19.3", | ||
"qunit": "^2.19.4", | ||
"timekeeper": "^2.2.0", | ||
"typedoc": "^0.23.21", | ||
"typedoc-plugin-markdown": "^3.13.6", | ||
"typescript": "^4.9.3", | ||
"undici": "^5.13.0" | ||
"typedoc": "^0.23.26", | ||
"typedoc-plugin-markdown": "^3.14.0", | ||
"typescript": "^4.9.5", | ||
"undici": "^5.20.0" | ||
} | ||
} |
@@ -42,3 +42,3 @@ # OAuth 2 / OpenID Connect for Web Platform API JavaScript runtimes | ||
```js | ||
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.1.0/mod.ts' | ||
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.2.0/mod.ts' | ||
``` | ||
@@ -45,0 +45,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
123909
2794