Skip to content

Commit

Permalink
feat: make refresh sync signing key setting (#909)
Browse files Browse the repository at this point in the history
* feat: make refresh update the signing key type of sessions

* feat: make the refresh and create session apis consistent

* test: remove test log

* chore: update changelog

* test: update tests to use new param
  • Loading branch information
porcellus authored Jan 29, 2024
1 parent b09ea39 commit 9ef8d5f
Show file tree
Hide file tree
Showing 10 changed files with 591 additions and 23 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `VerifyTotpDeviceAPI` changes
- Adds `currentNumberOfFailedAttempts` and `maxNumberOfFailedAttempts` in response when status is
`INVALID_TOTP_ERROR` or `LIMIT_REACHED_ERROR`
- Adds a new required `useDynamicSigningKey` into the request body of `RefreshSessionAPI`
- This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to
change the signing key type of a session

### Migration

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,11 @@ public SessionInfo getSessionInfo_Transaction(TenantIdentifier tenantIdentifier,
@Override
public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
String sessionHandle, String refreshTokenHash2,
long expiry) throws StorageQueryException {
long expiry, boolean useStaticKey) throws StorageQueryException {
Connection sqlCon = (Connection) con.getConnection();
try {
SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle,
refreshTokenHash2, expiry);
refreshTokenHash2, expiry, useStaticKey);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,19 @@ public static SessionInfo getSessionInfo_Transaction(Start start, Connection con

public static void updateSessionInfo_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier,
String sessionHandle,
String refreshTokenHash2, long expiry)
String refreshTokenHash2, long expiry, boolean useStaticKey)
throws SQLException, StorageQueryException {
String QUERY = "UPDATE " + getConfig(start).getSessionInfoTable()
+ " SET refresh_token_hash_2 = ?, expires_at = ?"
+ " SET refresh_token_hash_2 = ?, expires_at = ?, use_static_key= ?"
+ " WHERE app_id = ? AND tenant_id = ? AND session_handle = ?";

update(con, QUERY, pst -> {
pst.setString(1, refreshTokenHash2);
pst.setLong(2, expiry);
pst.setString(3, tenantIdentifier.getAppId());
pst.setString(4, tenantIdentifier.getTenantId());
pst.setString(5, sessionHandle);
pst.setBoolean(3, useStaticKey);
pst.setString(4, tenantIdentifier.getAppId());
pst.setString(5, tenantIdentifier.getTenantId());
pst.setString(6, sessionHandle);
});
}

Expand Down
46 changes: 33 additions & 13 deletions src/main/java/io/supertokens/session/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M
accessToken.sessionHandle,
Utils.hashSHA256(accessToken.refreshTokenHash1),
System.currentTimeMillis() +
config.getRefreshTokenValidity());
config.getRefreshTokenValidity(), sessionInfo.useStaticKey);
}
storage.commitTransaction(con);

Expand Down Expand Up @@ -454,7 +454,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M
Utils.hashSHA256(accessToken.refreshTokenHash1),
System.currentTimeMillis() + Config.getConfig(tenantIdentifierWithStorage, main)
.getRefreshTokenValidity(),
sessionInfo.lastUpdatedSign);
sessionInfo.lastUpdatedSign, sessionInfo.useStaticKey);
if (!success) {
continue;
}
Expand Down Expand Up @@ -509,7 +509,7 @@ public static SessionInformationHolder refreshSession(Main main, @Nonnull String
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError {
try {
return refreshSession(new AppIdentifier(null, null), main, refreshToken, antiCsrfToken,
enableAntiCsrf, accessTokenVersion);
enableAntiCsrf, accessTokenVersion, null);
} catch (TenantOrAppNotFoundException e) {
throw new IllegalStateException(e);
}
Expand All @@ -518,7 +518,7 @@ public static SessionInformationHolder refreshSession(Main main, @Nonnull String
public static SessionInformationHolder refreshSession(AppIdentifier appIdentifier, Main main,
@Nonnull String refreshToken,
@Nullable String antiCsrfToken, boolean enableAntiCsrf,
AccessToken.VERSION accessTokenVersion)
AccessToken.VERSION accessTokenVersion, Boolean shouldUseStaticKey)
throws StorageTransactionLogicException,
UnauthorisedException, StorageQueryException, TokenTheftDetectedException,
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError, TenantOrAppNotFoundException {
Expand All @@ -534,14 +534,14 @@ public static SessionInformationHolder refreshSession(AppIdentifier appIdentifie

return refreshSessionHelper(refreshTokenInfo.tenantIdentifier.withStorage(
StorageLayer.getStorage(refreshTokenInfo.tenantIdentifier, main)),
main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion);
main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey);
}

private static SessionInformationHolder refreshSessionHelper(
TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String refreshToken,
RefreshToken.RefreshTokenInfo refreshTokenInfo,
boolean enableAntiCsrf,
AccessToken.VERSION accessTokenVersion)
AccessToken.VERSION accessTokenVersion, Boolean shouldUseStaticKey)
throws StorageTransactionLogicException, UnauthorisedException, StorageQueryException,
TokenTheftDetectedException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError,
TenantOrAppNotFoundException {
Expand All @@ -565,8 +565,16 @@ private static SessionInformationHolder refreshSessionHelper(
storage.commitTransaction(con);
throw new UnauthorisedException("Session missing in db or has expired");
}
boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey;

if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) {
if (useStaticKey != sessionInfo.useStaticKey) {
// We do not update anything except the static key status
storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle,
sessionInfo.refreshTokenHash2, sessionInfo.expiry,
useStaticKey);
}

// at this point, the input refresh token is the parent one.
storage.commitTransaction(con);

Expand All @@ -580,7 +588,8 @@ private static SessionInformationHolder refreshSessionHelper(
sessionInfo.recipeUserId, sessionInfo.userId,
Utils.hashSHA256(newRefreshToken.token),
Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken,
null, accessTokenVersion, sessionInfo.useStaticKey);
null, accessTokenVersion,
useStaticKey);

TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(),
newRefreshToken.expiry, newRefreshToken.createdTime);
Expand All @@ -600,13 +609,13 @@ private static SessionInformationHolder refreshSessionHelper(
.equals(sessionInfo.refreshTokenHash2))) {
storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle,
Utils.hashSHA256(Utils.hashSHA256(refreshToken)),
System.currentTimeMillis() + config.getRefreshTokenValidity());
System.currentTimeMillis() + config.getRefreshTokenValidity(), useStaticKey);

storage.commitTransaction(con);

return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken,
refreshTokenInfo, enableAntiCsrf,
accessTokenVersion);
accessTokenVersion, shouldUseStaticKey);
}

storage.commitTransaction(con);
Expand Down Expand Up @@ -655,7 +664,18 @@ private static SessionInformationHolder refreshSessionHelper(
throw new UnauthorisedException("Session missing in db or has expired");
}

boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey;

if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) {
if (sessionInfo.useStaticKey != useStaticKey) {
// We do not update anything except the static key status
boolean success = storage.updateSessionInfo_Transaction(sessionHandle,
sessionInfo.refreshTokenHash2, sessionInfo.expiry,
sessionInfo.lastUpdatedSign, useStaticKey);
if (!success) {
continue;
}
}
// at this point, the input refresh token is the parent one.
String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null;

Expand All @@ -666,7 +686,8 @@ private static SessionInformationHolder refreshSessionHelper(
sessionHandle,
sessionInfo.recipeUserId, sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token),
Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken,
null, accessTokenVersion, sessionInfo.useStaticKey);
null, accessTokenVersion,
useStaticKey);

TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(), newRefreshToken.expiry,
newRefreshToken.createdTime);
Expand All @@ -688,13 +709,12 @@ private static SessionInformationHolder refreshSessionHelper(
Utils.hashSHA256(Utils.hashSHA256(refreshToken)),
System.currentTimeMillis() +
Config.getConfig(tenantIdentifierWithStorage, main).getRefreshTokenValidity(),
sessionInfo.lastUpdatedSign);
sessionInfo.lastUpdatedSign, useStaticKey);
if (!success) {
continue;
}
return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken, refreshTokenInfo,
enableAntiCsrf,
accessTokenVersion);
enableAntiCsrf, accessTokenVersion, shouldUseStaticKey);
}

throw new TokenTheftDetectedException(sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ public String getPath() {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
SemVer version = super.getVersionFromRequest(req);

// API is app specific, but session is updated based on tenantId obtained from the refreshToken
JsonObject input = InputParser.parseJsonObjectOrThrowError(req);
String refreshToken = InputParser.parseStringOrThrowError(input, "refreshToken", false);
String antiCsrfToken = InputParser.parseStringOrThrowError(input, "antiCsrfToken", true);
Boolean enableAntiCsrf = InputParser.parseBooleanOrThrowError(input, "enableAntiCsrf", false);
Boolean useDynamicSigningKey = version.greaterThanOrEqualTo(SemVer.v5_0) ?
InputParser.parseBooleanOrThrowError(input, "useDynamicSigningKey", false) : null;
assert enableAntiCsrf != null;
assert refreshToken != null;

Expand All @@ -75,13 +79,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
throw new ServletException(e);
}

SemVer version = super.getVersionFromRequest(req);
try {
AccessToken.VERSION accessTokenVersion = AccessToken.getAccessTokenVersionForCDI(version);

SessionInformationHolder sessionInfo = Session.refreshSession(appIdentifierWithStorage, main,
refreshToken, antiCsrfToken,
enableAntiCsrf, accessTokenVersion);
enableAntiCsrf, accessTokenVersion,
useDynamicSigningKey == null ? null : Boolean.FALSE.equals(useDynamicSigningKey)
);

if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() ==
STORAGE_TYPE.SQL) {
Expand Down
1 change: 1 addition & 0 deletions src/test/java/io/supertokens/test/StorageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ public void storageDeadAndAlive() throws InterruptedException, IOException, Http
jsonBody.addProperty("refreshToken",
sessionCreated.get("refreshToken").getAsJsonObject().get("token").getAsString());
jsonBody.addProperty("enableAntiCsrf", false);
jsonBody.addProperty("useDynamicSigningKey", true);

storage.setStorageLayerEnabled(false);

Expand Down
Loading

0 comments on commit 9ef8d5f

Please sign in to comment.