Skip to content

Commit 1bd289a

Browse files
authored
feat: backport useDynamicSigningKey in refresh api (#962)
1 parent 56b0f8f commit 1bd289a

File tree

4 files changed

+259
-3
lines changed

4 files changed

+259
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3030
`INVALID_TOTP_ERROR` or `LIMIT_REACHED_ERROR`
3131
- Adds `consumedDevice` in the success response of the `ConsumeCodeAPI`
3232
- Adds `preAuthSessionId` input to `DeleteCodeAPI` to be able to delete codes for a device
33-
- Adds a new required `useDynamicSigningKey` into the request body of `RefreshSessionAPI`
33+
- Adds a new `useDynamicSigningKey` into the request body of `RefreshSessionAPI`
3434
- This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to change the signing key type of a session
35+
- This is available after CDI3.0
36+
- This is required in&after CDI5.0 and optional before
3537
- Adds optional `firstFactors` and `requiredSecondaryFactors` to the create or update connectionUriDomain, app and tenant APIs
3638
- Updates Last active while linking accounts
3739
- Marks fake email in email password sign up as verified

src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
6969
String refreshToken = InputParser.parseStringOrThrowError(input, "refreshToken", false);
7070
String antiCsrfToken = InputParser.parseStringOrThrowError(input, "antiCsrfToken", true);
7171
Boolean enableAntiCsrf = InputParser.parseBooleanOrThrowError(input, "enableAntiCsrf", false);
72-
Boolean useDynamicSigningKey = version.greaterThanOrEqualTo(SemVer.v5_0) ?
73-
InputParser.parseBooleanOrThrowError(input, "useDynamicSigningKey", false) : null;
72+
Boolean useDynamicSigningKey = version.greaterThanOrEqualTo(SemVer.v3_0)
73+
? InputParser.parseBooleanOrThrowError(input, "useDynamicSigningKey", version.lesserThan(SemVer.v5_0))
74+
: null;
75+
7476
assert enableAntiCsrf != null;
7577
assert refreshToken != null;
7678

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
3+
*
4+
* This software is licensed under the Apache License, Version 2.0 (the
5+
* "License") as published by the Apache Software Foundation.
6+
*
7+
* You may not use this file except in compliance with the License. You may
8+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package io.supertokens.test.session.api;
18+
19+
import com.google.gson.JsonNull;
20+
import com.google.gson.JsonObject;
21+
import io.supertokens.ProcessState;
22+
import io.supertokens.session.jwt.JWT;
23+
import io.supertokens.test.TestingProcessManager;
24+
import io.supertokens.test.Utils;
25+
import io.supertokens.test.httpRequest.HttpRequestForTesting;
26+
import io.supertokens.utils.SemVer;
27+
import org.junit.AfterClass;
28+
import org.junit.Before;
29+
import org.junit.Rule;
30+
import org.junit.Test;
31+
import org.junit.rules.TestRule;
32+
33+
import static junit.framework.TestCase.assertEquals;
34+
import static junit.framework.TestCase.assertTrue;
35+
import static org.junit.Assert.assertNotNull;
36+
37+
public class RefreshSessionAPITest3_0 {
38+
@Rule
39+
public TestRule watchman = Utils.getOnFailure();
40+
41+
@AfterClass
42+
public static void afterTesting() {
43+
Utils.afterTesting();
44+
}
45+
46+
@Before
47+
public void beforeEach() {
48+
Utils.reset();
49+
}
50+
51+
@Test
52+
public void successOutputWithValidRefreshTokenTest() throws Exception {
53+
String[] args = { "../" };
54+
55+
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
56+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
57+
58+
String userId = "userId";
59+
JsonObject userDataInJWT = new JsonObject();
60+
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
61+
userDataInJWT.addProperty("key", "value");
62+
JsonObject userDataInDatabase = new JsonObject();
63+
userDataInDatabase.addProperty("key", "value");
64+
65+
JsonObject request = new JsonObject();
66+
request.addProperty("userId", userId);
67+
request.add("userDataInJWT", userDataInJWT);
68+
request.add("userDataInDatabase", userDataInDatabase);
69+
request.addProperty("enableAntiCsrf", false);
70+
71+
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
72+
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(),
73+
"session");
74+
assertEquals(sessionInfo.get("status").getAsString(), "OK");
75+
76+
JsonObject sessionRefreshBody = new JsonObject();
77+
78+
sessionRefreshBody.addProperty("refreshToken",
79+
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
80+
sessionRefreshBody.addProperty("enableAntiCsrf", false);
81+
82+
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
83+
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
84+
SemVer.v3_0.get(), "session");
85+
86+
checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, false);
87+
process.kill();
88+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
89+
90+
}
91+
92+
@Test
93+
public void successOutputUpgradeWithNonStaticKeySessionTest() throws Exception {
94+
String[] args = { "../" };
95+
96+
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
97+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
98+
99+
String userId = "userId";
100+
JsonObject userDataInJWT = new JsonObject();
101+
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
102+
userDataInJWT.addProperty("key", "value");
103+
JsonObject userDataInDatabase = new JsonObject();
104+
userDataInDatabase.addProperty("key", "value");
105+
106+
JsonObject request = new JsonObject();
107+
request.addProperty("userId", userId);
108+
request.add("userDataInJWT", userDataInJWT);
109+
request.add("userDataInDatabase", userDataInDatabase);
110+
request.addProperty("enableAntiCsrf", false);
111+
112+
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
113+
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(),
114+
"session");
115+
assertEquals(sessionInfo.get("status").getAsString(), "OK");
116+
117+
JsonObject sessionRefreshBody = new JsonObject();
118+
119+
sessionRefreshBody.addProperty("refreshToken",
120+
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
121+
sessionRefreshBody.addProperty("enableAntiCsrf", false);
122+
sessionRefreshBody.addProperty("useDynamicSigningKey", true);
123+
124+
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
125+
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
126+
SemVer.v3_0.get(), "session");
127+
128+
checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, false);
129+
process.kill();
130+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
131+
}
132+
133+
@Test
134+
public void successOutputUpgradeWithStaticKeySessionTest() throws Exception {
135+
String[] args = { "../" };
136+
137+
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
138+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
139+
140+
String userId = "userId";
141+
JsonObject userDataInJWT = new JsonObject();
142+
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
143+
userDataInJWT.addProperty("key", "value");
144+
JsonObject userDataInDatabase = new JsonObject();
145+
userDataInDatabase.addProperty("key", "value");
146+
147+
JsonObject request = new JsonObject();
148+
request.addProperty("userId", userId);
149+
request.add("userDataInJWT", userDataInJWT);
150+
request.add("userDataInDatabase", userDataInDatabase);
151+
request.addProperty("enableAntiCsrf", false);
152+
153+
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
154+
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(),
155+
"session");
156+
assertEquals(sessionInfo.get("status").getAsString(), "OK");
157+
158+
JsonObject sessionRefreshBody = new JsonObject();
159+
160+
sessionRefreshBody.addProperty("refreshToken",
161+
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
162+
sessionRefreshBody.addProperty("enableAntiCsrf", false);
163+
sessionRefreshBody.addProperty("useDynamicSigningKey", false);
164+
165+
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
166+
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
167+
SemVer.v3_0.get(), "session");
168+
169+
checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, true);
170+
process.kill();
171+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
172+
}
173+
174+
private static void checkRefreshSessionResponse(JsonObject response, TestingProcessManager.TestingProcess process,
175+
String userId, JsonObject userDataInJWT, boolean hasAntiCsrf, boolean useStaticKey) throws
176+
JWT.JWTException {
177+
178+
assertNotNull(response.get("session").getAsJsonObject().get("handle").getAsString());
179+
assertEquals(response.get("session").getAsJsonObject().get("userId").getAsString(), userId);
180+
assertEquals(response.get("session").getAsJsonObject().get("tenantId").getAsString(), "public");
181+
assertEquals(response.get("session").getAsJsonObject().get("userDataInJWT").getAsJsonObject().toString(),
182+
userDataInJWT.toString());
183+
assertEquals(response.get("session").getAsJsonObject().entrySet().size(), 4);
184+
185+
assertTrue(response.get("accessToken").getAsJsonObject().has("token"));
186+
assertTrue(response.get("accessToken").getAsJsonObject().has("expiry"));
187+
assertTrue(response.get("accessToken").getAsJsonObject().has("createdTime"));
188+
assertEquals(response.get("accessToken").getAsJsonObject().entrySet().size(), 3);
189+
190+
JWT.JWTPreParseInfo tokenInfo = JWT.preParseJWTInfo(response.get("accessToken").getAsJsonObject().get("token").getAsString());
191+
192+
if (useStaticKey) {
193+
assert(tokenInfo.kid.startsWith("s-"));
194+
} else {
195+
assert(tokenInfo.kid.startsWith("d-"));
196+
}
197+
198+
assertTrue(response.get("refreshToken").getAsJsonObject().has("token"));
199+
assertTrue(response.get("refreshToken").getAsJsonObject().has("expiry"));
200+
assertTrue(response.get("refreshToken").getAsJsonObject().has("createdTime"));
201+
assertEquals(response.get("refreshToken").getAsJsonObject().entrySet().size(), 3);
202+
203+
assertEquals(response.has("antiCsrfToken"), hasAntiCsrf);
204+
205+
assertEquals(response.entrySet().size(), hasAntiCsrf ? 5 : 4);
206+
}
207+
}

src/test/java/io/supertokens/test/session/api/RefreshSessionAPITest5_0.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,51 @@ public void beforeEach() {
4949
Utils.reset();
5050
}
5151

52+
@Test
53+
public void badInputMissingUseDynamicSigningKey() throws Exception {
54+
String[] args = { "../" };
55+
56+
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
57+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
58+
59+
String userId = "userId";
60+
JsonObject userDataInJWT = new JsonObject();
61+
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
62+
userDataInJWT.addProperty("key", "value");
63+
JsonObject userDataInDatabase = new JsonObject();
64+
userDataInDatabase.addProperty("key", "value");
65+
66+
JsonObject request = new JsonObject();
67+
request.addProperty("userId", userId);
68+
request.add("userDataInJWT", userDataInJWT);
69+
request.add("userDataInDatabase", userDataInDatabase);
70+
request.addProperty("enableAntiCsrf", false);
71+
72+
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
73+
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v5_0.get(),
74+
"session");
75+
assertEquals(sessionInfo.get("status").getAsString(), "OK");
76+
77+
JsonObject sessionRefreshBody = new JsonObject();
78+
79+
sessionRefreshBody.addProperty("refreshToken",
80+
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
81+
sessionRefreshBody.addProperty("enableAntiCsrf", false);
82+
83+
try {
84+
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
85+
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
86+
SemVer.v5_0.get(), "session");
87+
} catch (io.supertokens.test.httpRequest.HttpResponseException e) {
88+
assertEquals(e.getMessage(),
89+
"Http error. Status Code: 400. Message: Field name 'useDynamicSigningKey' is invalid in JSON input");
90+
91+
}
92+
93+
process.kill();
94+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
95+
}
96+
5297
@Test
5398
public void successOutputUpgradeWithNonStaticKeySessionTest() throws Exception {
5499
String[] args = { "../" };

0 commit comments

Comments
 (0)