Skip to content

Commit

Permalink
Merge pull request #254 from supertokens/feat/serialize_claim_refreshing
Browse files Browse the repository at this point in the history
feat: serialize claim refresh calls
  • Loading branch information
porcellus authored May 22, 2024
2 parents 8fcd65e + 5b38fab commit 77923ce
Show file tree
Hide file tree
Showing 10 changed files with 499 additions and 43 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [20.0.1] - 2024-05-22

### Changes

- Now we use the locking to make sure that refreshing claims happens only once even for concurrent validateClaims calls
- The locking mechanism is configurable through by providing a `lockFactory` function in the configuration

## [20.0.0] - 2024-04-03

### Breaking changes
Expand Down
2 changes: 1 addition & 1 deletion bundle/bundle.js

Large diffs are not rendered by default.

119 changes: 90 additions & 29 deletions lib/build/recipeImplementation.js

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

2 changes: 1 addition & 1 deletion lib/build/version.d.ts

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

2 changes: 1 addition & 1 deletion lib/build/version.js

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

51 changes: 44 additions & 7 deletions lib/ts/recipeImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { STGeneralError } from "./error";
import { addInterceptorsToXMLHttpRequest } from "./xmlhttprequest";
import { matchesDomainOrSubdomain, normaliseSessionScopeOrThrowError, normaliseURLDomainOrThrowError } from "./utils";
import DateProviderReference from "./utils/dateProvider";
import LockFactoryReference from "./utils/lockFactory";

const MAX_REFRESH_LOCK_TRY_COUNT = 100;
const CLAIM_REFRESH_LOCK_NAME = "CLAIM_REFRESH_LOCK";

export default function RecipeImplementation(recipeImplInput: {
preAPIHook: RecipePreAPIHookFunction;
Expand Down Expand Up @@ -223,7 +227,8 @@ export default function RecipeImplementation(recipeImplInput: {
claimValidators: SessionClaimValidator[];
userContext: any;
}): Promise<ClaimValidationError[]> {
let accessTokenPayload = await this.getAccessTokenPayloadSecurely({ userContext: input.userContext });
// We only load accessTokenPayload after acquiring the lock, since it could change until then
let accessTokenPayload;
// We first refresh all claims that may need to be refreshed, before running any validators,
// to avoid a situation where:
// 1. The payload passes claimValidators[0].
Expand All @@ -232,17 +237,49 @@ export default function RecipeImplementation(recipeImplInput: {
// 4. We return no errors since both claimValidators[0] and claimValidators[1] passed (but different states of the payload)
// Running all refreshes before validation avoids this.

for (const validator of input.claimValidators) {
if (await validator.shouldRefresh(accessTokenPayload, input.userContext)) {
let tryCount = 0;
while (++tryCount < MAX_REFRESH_LOCK_TRY_COUNT) {
const lockFactory = await LockFactoryReference.getReferenceOrThrow().lockFactory();
logDebugMessage("validateClaims: trying to acquire claim refresh lock");
const claimRefreshLock = await lockFactory.acquireLock(CLAIM_REFRESH_LOCK_NAME);
if (claimRefreshLock) {
accessTokenPayload = await this.getAccessTokenPayloadSecurely({ userContext: input.userContext });
logDebugMessage("validateClaims: claim refresh lock acquired");
// to sync across tabs. the 1000 ms wait is for how much time to try and acquire the lock
try {
await validator.refresh(input.userContext);
} catch (err) {
console.error(`Encountered an error while refreshing validator ${validator.id}`, err);
for (const validator of input.claimValidators) {
if (await validator.shouldRefresh(accessTokenPayload, input.userContext)) {
try {
await validator.refresh(input.userContext);
} catch (err) {
console.error(
`Encountered an error while refreshing validator ${validator.id}`,
err
);
}
accessTokenPayload = await this.getAccessTokenPayloadSecurely({
userContext: input.userContext
});
}
}
} finally {
logDebugMessage("validateClaims: releasing claim refresh lock");
await lockFactory.releaseLock(CLAIM_REFRESH_LOCK_NAME);
}
accessTokenPayload = await this.getAccessTokenPayloadSecurely({ userContext: input.userContext });
break;
} else {
logDebugMessage(`validateClaims: Retrying refresh lock ${tryCount}/${MAX_REFRESH_LOCK_TRY_COUNT}`);
}
}

if (tryCount === MAX_REFRESH_LOCK_TRY_COUNT) {
logDebugMessage("validateClaims: ran out of retries while trying to acquire claim refresh lock");
// We can just load the access token payload (that doesn't happen above if we never got inside the lock)
accessTokenPayload = await this.getAccessTokenPayloadSecurely({ userContext: input.userContext });
// and let the claim validation proceed. This matches our behaviour of letting the validation proceed
// even if a refresh function threw or failed to refresh a claim.
}

const errors = [];
for (const validator of input.claimValidators) {
const validationRes = await validator.validate(accessTokenPayload, input.userContext);
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
export const package_version = "20.0.0";
export const package_version = "20.0.1";

export const supported_fdi = ["1.16", "1.17", "1.18", "1.19"];
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supertokens-website",
"version": "20.0.0",
"version": "20.0.1",
"description": "frontend sdk for website to be used for auth solution.",
"main": "index.js",
"dependencies": {
Expand Down
Loading

0 comments on commit 77923ce

Please sign in to comment.