Skip to content

Commit

Permalink
Add threshold in DateProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
anku255 committed Jan 20, 2024
1 parent f5e6814 commit 5f52287
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 23 deletions.
2 changes: 1 addition & 1 deletion bundle/bundle.js

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions lib/build/claims/primitiveArrayClaim.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions lib/build/claims/primitiveClaim.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/build/utils/dateProvider/defaultImplementation.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion lib/build/utils/dateProvider/defaultImplementation.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 6 additions & 9 deletions lib/build/utils/dateProvider/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion lib/build/utils/dateProvider/types.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions lib/ts/claims/primitiveArrayClaim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export class PrimitiveArrayClaim<ValueType> {
id?: string
): SessionClaimValidator => {
const DateProvider = DateProviderReference.getReferenceOrThrow().dateProvider;
if (maxAgeInSeconds && maxAgeInSeconds < DateProvider.getThresholdInSeconds()) {
throw new Error(
`maxAgeInSeconds must be greater than the DateProvider threshold value -> ${DateProvider.getThresholdInSeconds()}`
);
}
return {
id: id !== undefined ? id : this.id,
refresh: ctx => this.refresh(ctx),
Expand Down Expand Up @@ -75,6 +80,11 @@ export class PrimitiveArrayClaim<ValueType> {
id?: string
): SessionClaimValidator => {
const DateProvider = DateProviderReference.getReferenceOrThrow().dateProvider;
if (maxAgeInSeconds && maxAgeInSeconds < DateProvider.getThresholdInSeconds()) {
throw new Error(
`maxAgeInSeconds must be greater than the DateProvider threshold value -> ${DateProvider.getThresholdInSeconds()}`
);
}
return {
id: id !== undefined ? id : this.id,
refresh: ctx => this.refresh(ctx),
Expand Down Expand Up @@ -121,6 +131,11 @@ export class PrimitiveArrayClaim<ValueType> {
id?: string
): SessionClaimValidator => {
const DateProvider = DateProviderReference.getReferenceOrThrow().dateProvider;
if (maxAgeInSeconds && maxAgeInSeconds < DateProvider.getThresholdInSeconds()) {
throw new Error(
`maxAgeInSeconds must be greater than the DateProvider threshold value -> ${DateProvider.getThresholdInSeconds()}`
);
}
return {
id: id !== undefined ? id : this.id,
refresh: ctx => this.refresh(ctx),
Expand Down Expand Up @@ -164,6 +179,11 @@ export class PrimitiveArrayClaim<ValueType> {
id?: string
): SessionClaimValidator => {
const DateProvider = DateProviderReference.getReferenceOrThrow().dateProvider;
if (maxAgeInSeconds && maxAgeInSeconds < DateProvider.getThresholdInSeconds()) {
throw new Error(
`maxAgeInSeconds must be greater than the DateProvider threshold value -> ${DateProvider.getThresholdInSeconds()}`
);
}
return {
id: id !== undefined ? id : this.id,
refresh: ctx => this.refresh(ctx),
Expand Down Expand Up @@ -211,6 +231,11 @@ export class PrimitiveArrayClaim<ValueType> {
id?: string
): SessionClaimValidator => {
const DateProvider = DateProviderReference.getReferenceOrThrow().dateProvider;
if (maxAgeInSeconds && maxAgeInSeconds < DateProvider.getThresholdInSeconds()) {
throw new Error(
`maxAgeInSeconds must be greater than the DateProvider threshold value -> ${DateProvider.getThresholdInSeconds()}`
);
}
return {
id: id !== undefined ? id : this.id,
refresh: ctx => this.refresh(ctx),
Expand Down
5 changes: 5 additions & 0 deletions lib/ts/claims/primitiveClaim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export class PrimitiveClaim<ValueType> {
id?: string
): SessionClaimValidator => {
const DateProvider = DateProviderReference.getReferenceOrThrow().dateProvider;
if (maxAgeInSeconds && maxAgeInSeconds < DateProvider.getThresholdInSeconds()) {
throw new Error(
`maxAgeInSeconds must be greater than the DateProvider threshold value -> ${DateProvider.getThresholdInSeconds()}`
);
}
return {
id: id !== undefined ? id : this.id,
refresh: ctx => this.refresh(ctx),
Expand Down
9 changes: 8 additions & 1 deletion lib/ts/utils/dateProvider/defaultImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class DateProvider {
private static instance?: DateProvider;
public static readonly CLOCK_SKEW_KEY = "__st_clockSkewInMillis";
private clockSkewInMillis: number = 0;
// Ensure a meaningful clock skew value by setting a threshold. Omitting a threshold would invariably lead to some
// clock skew due to server-client latency, arising from the time it takes for the token to arrive after being issued.
private thresholdInSeconds = 7;

// The static init method is used to create a singleton instance of DateProvider,
// as we require access to localStorage for initializing clockSkewInMillis.
Expand Down Expand Up @@ -56,8 +59,12 @@ export class DateProvider {
return DateProvider.instance;
}

getThresholdInSeconds(): number {
return this.thresholdInSeconds;
}

setClientClockSkewInMillis(clockSkewInMillis: number): void {
this.clockSkewInMillis = clockSkewInMillis;
this.clockSkewInMillis = Math.abs(clockSkewInMillis) > this.thresholdInSeconds * 1000 ? clockSkewInMillis : 0;
const localStorage = WindowHandlerReference.getReferenceOrThrow().windowHandler.localStorage;
localStorage.setItemSync(DateProvider.CLOCK_SKEW_KEY, String(clockSkewInMillis));
}
Expand Down
14 changes: 6 additions & 8 deletions lib/ts/utils/dateProvider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,14 @@ export default class DateProviderReference {
dateProvider: DateProviderInterface;

constructor(dateProviderInput?: DateProviderInput) {
let dateProviderFunc: DateProviderInput = original => original;
if (dateProviderInput !== undefined) {
dateProviderFunc = dateProviderInput;
this.dateProvider = dateProviderInput();
} else {
// Initialize the DateProvider implementation by calling the init method.
// This is done to ensure that the WindowHandler is initialized before we instantiate the DateProvider.
DateProvider.init();
this.dateProvider = DateProvider.getReferenceOrThrow();
}

// Initialize the DateProvider implementation by calling the init method.
// This is done to ensure that the WindowHandler is initialized before we instantiate the DateProvider.
DateProvider.init();
const defaultDateProviderImplementation = DateProvider.getReferenceOrThrow();
this.dateProvider = dateProviderFunc(defaultDateProviderImplementation);
}

static init(dateProviderInput?: DateProviderInput): void {
Expand Down
3 changes: 2 additions & 1 deletion lib/ts/utils/dateProvider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
*/

export interface DateProviderInterface {
getThresholdInSeconds(): number;
now(): number;
setClientClockSkewInMillis(clockSkewInMillis: number): void;
getClientClockSkewInMillis(): number;
}

export type DateProviderInput = (original: DateProviderInterface) => DateProviderInterface;
export type DateProviderInput = () => DateProviderInterface;
74 changes: 73 additions & 1 deletion test/interception.claims.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ addGenericTestCases((name, transferMethod, setupFunc, setupArgs = []) => {
}
});

it("should call the claim refresh endpoint once for multiple `shouldRefresh` calls with adjusted clock skew", async function () {
it("should call the claim refresh endpoint once for multiple `shouldRefresh` calls with adjusted clock skew (client clock ahead)", async function () {
await startST(2 * 60 * 60); // setting accessTokenValidity to 2 hours to avoid refresh issues due to clock skew
try {
let customClaimRefreshCalledCount = 0;
Expand Down Expand Up @@ -266,6 +266,78 @@ addGenericTestCases((name, transferMethod, setupFunc, setupArgs = []) => {
}
});

it("should call the claim refresh endpoint once for multiple `shouldRefresh` calls with adjusted clock skew (client clock behind)", async function () {
await startST(2 * 60 * 60); // setting accessTokenValidity to 2 hours to avoid refresh issues due to clock skew
try {
let customClaimRefreshCalledCount = 0;

// Override Date.now() to return the current time minus 1 hour
await page.evaluate(() => {
globalThis.originalNow = Date.now;
Date.now = function () {
return originalNow() - 60 * 60 * 1000;
};
});

await page.setRequestInterception(true);

page.on("request", req => {
if (req.url() === `${BASE_URL}/update-jwt`) {
customClaimRefreshCalledCount++;
}
req.continue();
});

await page.evaluate(async () => {
const userId = "testing-supertokens-website";

// Create a session
const loginResponse = await toTest({
url: `${BASE_URL}/login`,
method: "post",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({ userId })
});

assertEqual(loginResponse.responseText, userId);

const customSessionClaim = new supertokens.BooleanClaim({
id: "st-custom",
refresh: async () => {
const resp = await toTest({
url: `${BASE_URL}/update-jwt`,
method: "post",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
"st-custom": {
v: true,
t: originalNow()
}
})
});
},
defaultMaxAgeInSeconds: 300 /* 300 seconds */
});

const customSessionClaimValidator = customSessionClaim.validators.isTrue();

await supertokens.validateClaims(() => [customSessionClaimValidator]);
await supertokens.validateClaims(() => [customSessionClaimValidator]);
await supertokens.validateClaims(() => [customSessionClaimValidator]);
});

assert.strictEqual(customClaimRefreshCalledCount, 1);
} finally {
await browser.close();
}
});

it("should call getClockSkewInMillis with appropriate headers", async function () {
await startST();
let clockSkewParams = [];
Expand Down
Loading

0 comments on commit 5f52287

Please sign in to comment.