Skip to content

Commit e8f1c3f

Browse files
Merge pull request #859 from supertokens/fix-anomaly-detection-frontend-import
Attack protection suite improvements
2 parents 0c7aeba + 770c798 commit e8f1c3f

File tree

3 files changed

+127
-39
lines changed

3 files changed

+127
-39
lines changed

v2/attackprotectionsuite/backend-setup.mdx

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ async function handleSecurityChecks(input: {
401401
});
402402
} catch (err) {
403403
// silently fail in order to not break the auth flow
404+
console.error(err);
404405
return;
405406
}
406407

@@ -505,10 +506,11 @@ SuperTokens.init({
505506
const actionType = 'emailpassword-sign-up';
506507
const ip = getIpFromRequest(input.options.req.original);
507508
let email = input.formFields.filter((f) => f.id === "email")[0].value;
509+
let password = input.formFields.filter((f) => f.id === "password")[0].value;
508510
const bruteForceConfig = getBruteForceConfig(email, ip, actionType);
509511

510512
// we check the anomaly detection service before calling the original implementation of signUp
511-
let securityCheckResponse = await handleSecurityChecks({ ...input, requestId, email, bruteForceConfig, actionType });
513+
let securityCheckResponse = await handleSecurityChecks({ requestId, email, password, bruteForceConfig, actionType });
512514
if(securityCheckResponse !== undefined) {
513515
return securityCheckResponse;
514516
}
@@ -531,7 +533,7 @@ SuperTokens.init({
531533
const bruteForceConfig = getBruteForceConfig(email, ip, actionType);
532534

533535
// we check the anomaly detection service before calling the original implementation of signIn
534-
let securityCheckResponse = await handleSecurityChecks({ ...input, requestId, email, bruteForceConfig, actionType });
536+
let securityCheckResponse = await handleSecurityChecks({ requestId, email, bruteForceConfig, actionType });
535537
if(securityCheckResponse !== undefined) {
536538
return securityCheckResponse;
537539
}
@@ -554,12 +556,20 @@ SuperTokens.init({
554556
const bruteForceConfig = getBruteForceConfig(email, ip, actionType);
555557

556558
// we check the anomaly detection service before calling the original implementation of generatePasswordResetToken
557-
let securityCheckResponse = await handleSecurityChecks({ ...input, requestId, email, bruteForceConfig, actionType });
559+
let securityCheckResponse = await handleSecurityChecks({ requestId, email, bruteForceConfig, actionType });
558560
if(securityCheckResponse !== undefined) {
559561
return securityCheckResponse;
560562
}
561563

562564
return originalImplementation.generatePasswordResetTokenPOST!(input);
565+
},
566+
passwordResetPOST: async function (input) {
567+
let password = input.formFields.filter((f) => f.id === "password")[0].value;
568+
let securityCheckResponse = await handleSecurityChecks({ password });
569+
if (securityCheckResponse !== undefined) {
570+
return securityCheckResponse;
571+
}
572+
return originalImplementation.passwordResetPOST!(input);
563573
}
564574
}
565575
}
@@ -888,7 +898,7 @@ func main() {
888898
return resp, nil
889899
}
890900

891-
// rewrite the original implementation of SignInPOST
901+
// rewrite the original implementation of GeneratePasswordResetTokenPOST
892902
originalGeneratePasswordResetTokenPOST := *originalImplementation.GeneratePasswordResetTokenPOST
893903
(*originalImplementation.GeneratePasswordResetTokenPOST) = func(formFields []epmodels.TypeFormField, tenantId string, options epmodels.APIOptions, userContext supertokens.UserContext) (epmodels.GeneratePasswordResetTokenPOSTResponse, error) {
894904
// Generate request ID for bot and suspicious IP detection
@@ -949,6 +959,46 @@ func main() {
949959
return resp, nil
950960
}
951961

962+
// rewrite the original implementation of PasswordResetPOST
963+
originalPasswordResetPOST := *originalImplementation.PasswordResetPOST
964+
(*originalImplementation.PasswordResetPOST) = func(formFields []epmodels.TypeFormField, token string, tenantId string, options epmodels.APIOptions, userContext supertokens.UserContext) (epmodels.ResetPasswordPOSTResponse, error) {
965+
password := ""
966+
for _, field := range formFields {
967+
if field.ID == "password" {
968+
valueAsString, asStrOk := field.Value.(string)
969+
if !asStrOk {
970+
return epmodels.ResetPasswordPOSTResponse{}, errors.New("Should never come here as we check the type during validation")
971+
}
972+
password = valueAsString
973+
}
974+
}
975+
976+
// Check anomaly detection service before proceeding
977+
checkErr, err := handleSecurityChecks(
978+
SecurityCheckInput{
979+
Password: password,
980+
},
981+
)
982+
if err != nil {
983+
return epmodels.ResetPasswordPOSTResponse{}, err
984+
}
985+
986+
if checkErr != nil {
987+
return epmodels.ResetPasswordPOSTResponse{
988+
GeneralError: checkErr,
989+
}, nil
990+
}
991+
992+
// First we call the original implementation
993+
resp, err := originalPasswordResetPOST(formFields, token, tenantId, options, userContext)
994+
995+
if err != nil {
996+
return epmodels.ResetPasswordPOSTResponse{}, err
997+
}
998+
999+
return resp, nil
1000+
}
1001+
9521002
return originalImplementation
9531003
},
9541004
Functions: func(originalImplementation epmodels.RecipeInterface) epmodels.RecipeInterface {
@@ -993,7 +1043,7 @@ SECRET_API_KEY = "<secret-api-key>"; # Your secret API key that you received fro
9931043
# The full URL with the correct region will be provided by the SuperTokens team
9941044
ANOMALY_DETECTION_API_URL = "https://security-<region>.aws.supertokens.io/v1/security"
9951045

996-
async def handle_security_checks(request_id: Union[str, None], password: Union[str, None], brute_force_config: List[Dict[str, Any]], email: Union[str, None], phone_number: Union[str, None], action_type: str) -> Union[GeneralErrorResponse, None]:
1046+
async def handle_security_checks(request_id: Union[str, None], password: Union[str, None], brute_force_config: Union[List[Dict[str, Any]], None], email: Union[str, None], phone_number: Union[str, None], action_type: Union[str, None]) -> Union[GeneralErrorResponse, None]:
9971047
request_body = {}
9981048

9991049
if request_id is not None:
@@ -1123,7 +1173,7 @@ def override_email_password_apis(original_implementation: APIInterface):
11231173
email = field.value
11241174
brute_force_config = get_brute_force_config(email, ip, action_type)
11251175

1126-
# we check the anomaly detection service before calling the original implementation of signUp
1176+
# we check the anomaly detection service before calling the original implementation of sign_in_post
11271177
security_check_response = await handle_security_checks(
11281178
request_id=request_id,
11291179
password=None,
@@ -1135,7 +1185,7 @@ def override_email_password_apis(original_implementation: APIInterface):
11351185
if security_check_response is not None:
11361186
return security_check_response
11371187

1138-
# We need to call the original implementation of sign_up_post.
1188+
# We need to call the original implementation of sign_in_post.
11391189
response = await original_sign_in_post(form_fields, tenant_id, api_options, user_context)
11401190

11411191
return response
@@ -1159,7 +1209,7 @@ def override_email_password_apis(original_implementation: APIInterface):
11591209
email = field.value
11601210
brute_force_config = get_brute_force_config(email, ip, action_type)
11611211

1162-
# we check the anomaly detection service before calling the original implementation of signUp
1212+
# we check the anomaly detection service before calling the original implementation of generate_password_reset_token_post
11631213
security_check_response = await handle_security_checks(
11641214
request_id=request_id,
11651215
password=None,
@@ -1171,12 +1221,45 @@ def override_email_password_apis(original_implementation: APIInterface):
11711221
if security_check_response is not None:
11721222
return security_check_response
11731223

1174-
# We need to call the original implementation of sign_up_post.
1224+
# We need to call the original implementation of generate_password_reset_token_post.
11751225
response = await original_generate_password_reset_token_post(form_fields, tenant_id, api_options, user_context)
11761226

11771227
return response
11781228
original_implementation.generate_password_reset_token_post = generate_password_reset_token_post
11791229

1230+
1231+
original_password_reset_post = original_implementation.password_reset_post
1232+
async def password_reset_post(
1233+
form_fields: List[FormField],
1234+
token: str,
1235+
tenant_id: str,
1236+
api_options: APIOptions,
1237+
user_context: Dict[str, Any],
1238+
):
1239+
password = None
1240+
for field in form_fields:
1241+
if field.id == "password":
1242+
password = field.value
1243+
1244+
# we check the anomaly detection service before calling the original implementation of password_reset_post
1245+
security_check_response = await handle_security_checks(
1246+
request_id=None,
1247+
password=password,
1248+
brute_force_config=None,
1249+
email=None,
1250+
phone_number=None,
1251+
action_type=None
1252+
)
1253+
if security_check_response is not None:
1254+
return security_check_response
1255+
1256+
response = await original_password_reset_post(
1257+
form_fields, token, tenant_id, api_options, user_context
1258+
)
1259+
1260+
return response
1261+
original_implementation.password_reset_post = password_reset_post
1262+
11801263
return original_implementation
11811264
# highlight-end
11821265

@@ -1276,6 +1359,7 @@ async function handleSecurityChecks(input: {
12761359
});
12771360
} catch (err) {
12781361
// silently fail in order to not break the auth flow
1362+
console.error(err);
12791363
return;
12801364
}
12811365
let responseData = response.data;
@@ -1336,8 +1420,8 @@ SuperTokens.init({
13361420
const emailOrPhoneNumber = "email" in input ? input.email : input.phoneNumber;
13371421
const bruteForceConfig = getBruteForceConfig(emailOrPhoneNumber, ip, actionType);
13381422

1339-
// we check the anomaly detection service before calling the original implementation of signUp
1340-
let securityCheckResponse = await handleSecurityChecks({ ...input, bruteForceConfig, actionType });
1423+
// we check the anomaly detection service before calling the original implementation of createCodePOST
1424+
let securityCheckResponse = await handleSecurityChecks({ bruteForceConfig, actionType });
13411425
if(securityCheckResponse !== undefined) {
13421426
return securityCheckResponse;
13431427
}
@@ -1357,8 +1441,8 @@ SuperTokens.init({
13571441

13581442
const bruteForceConfig = getBruteForceConfig(userIdentifier, ip, actionType);
13591443

1360-
// we check the anomaly detection service before calling the original implementation of signUp
1361-
let securityCheckResponse = await handleSecurityChecks({ ...input, phoneNumber, email, bruteForceConfig, actionType });
1444+
// we check the anomaly detection service before calling the original implementation of resendCodePOST
1445+
let securityCheckResponse = await handleSecurityChecks({ phoneNumber, email, bruteForceConfig, actionType });
13621446
if(securityCheckResponse !== undefined) {
13631447
return securityCheckResponse;
13641448
}
@@ -1631,7 +1715,7 @@ SECRET_API_KEY = "<secret-api-key>" # Your secret API key that you received from
16311715
# The full URL with the correct region will be provided by the SuperTokens team
16321716
ANOMALY_DETECTION_API_URL = "https://security-<region>.aws.supertokens.io/v1/security"
16331717

1634-
async def handle_security_checks(request_id: Union[str, None], password: Union[str, None], brute_force_config: List[Dict[str, Any]], email: Union[str, None], phone_number: Union[str, None], action_type: str) -> Union[GeneralErrorResponse, None]:
1718+
async def handle_security_checks(request_id: Union[str, None], password: Union[str, None], brute_force_config: Union[List[Dict[str, Any]], None], email: Union[str, None], phone_number: Union[str, None], action_type: Union[str, None]) -> Union[GeneralErrorResponse, None]:
16351719
request_body = {}
16361720

16371721
request_body['bruteForce'] = brute_force_config
@@ -1693,7 +1777,7 @@ def override_passwordless_apis(original_implementation: APIInterface):
16931777
identifier = phone_number
16941778
brute_force_config = get_brute_force_config(identifier, ip, action_type)
16951779

1696-
# we check the anomaly detection service before calling the original implementation of signUp
1780+
# we check the anomaly detection service before calling the original implementation of create_code_post
16971781
security_check_response = await handle_security_checks(
16981782
request_id=None,
16991783
password=None,
@@ -1705,7 +1789,7 @@ def override_passwordless_apis(original_implementation: APIInterface):
17051789
if security_check_response is not None:
17061790
return security_check_response
17071791

1708-
# We need to call the original implementation of sign_up_post.
1792+
# We need to call the original implementation of create_code_post.
17091793
response = await original_create_code_post(email, phone_number, tenant_id, api_options, user_context)
17101794

17111795
return response
@@ -1728,7 +1812,7 @@ def override_passwordless_apis(original_implementation: APIInterface):
17281812
identifier = phone_number
17291813
brute_force_config = get_brute_force_config(identifier, ip, action_type)
17301814

1731-
# we check the anomaly detection service before calling the original implementation of signUp
1815+
# we check the anomaly detection service before calling the original implementation of resend_code_post
17321816
security_check_response = await handle_security_checks(
17331817
request_id=None,
17341818
password=None,
@@ -1740,7 +1824,7 @@ def override_passwordless_apis(original_implementation: APIInterface):
17401824
if security_check_response is not None:
17411825
return security_check_response
17421826

1743-
# We need to call the original implementation of sign_up_post.
1827+
# We need to call the original implementation of resend_code_post.
17441828
response = await original_resend_code_post(device_id, pre_auth_session_id, tenant_id, api_options, user_context)
17451829

17461830
return response

v2/attackprotectionsuite/frontend-setup.mdx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,15 @@ Below is an example of how to implement request ID generation on your frontend:
2020

2121

2222
```tsx
23-
const PUBLIC_API_KEY = "<public-api-key>"; // Your public API key that you received from the SuperTokens team
24-
const SDK_URL = "https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/k9bwGCuvuA83Ad6s";
25-
const PROXY_ENDPOINT_URL = "https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/CnsdzKsyFKU8Q3h2"
2623
const ENVIRONMENT_ID = "<environment-id>"; // Your environment ID that you received from the SuperTokens team
27-
// Initialize the agent on page load.
28-
const supertokensRequestIdPromise = import(SDK_URL + "?apiKey=" + PUBLIC_API_KEY).then((RequestId: any) => RequestId.load({
29-
endpoint: [
30-
PROXY_ENDPOINT_URL,
31-
RequestId.defaultEndpoint
32-
]
33-
}));
24+
// Initialize the agent on page load using your public API key that you received from the SuperTokens team.
25+
const supertokensRequestIdPromise = require("https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/k9bwGCuvuA83Ad6s?apiKey=<PUBLIC_API_KEY>")
26+
.then((RequestId: any) => RequestId.load({
27+
endpoint: [
28+
'https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/CnsdzKsyFKU8Q3h2',
29+
RequestId.defaultEndpoint
30+
]
31+
}));
3432

3533
async function getRequestId() {
3634
const sdk = await supertokensRequestIdPromise;
@@ -43,6 +41,10 @@ async function getRequestId() {
4341
}
4442
```
4543

44+
:::note
45+
Make sure to replace the `<PUBLIC_API_KEY>` in the above string with the provided public API key.
46+
:::
47+
4648
### Passing the Request ID to the Backend
4749

4850
Once you have generated the request ID on the frontend, you need to pass it to the backend. This is done by including the `requestId` property along with the value as part of the preAPIHook body from the initialisation of the recipes.
@@ -55,17 +57,15 @@ Below is a full example of how to configure the SDK and pass the request ID to t
5557
```tsx
5658
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
5759

58-
const PUBLIC_API_KEY = "<public-api-key>"; // Your public API key that you received from the SuperTokens team
59-
const SDK_URL = "https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/k9bwGCuvuA83Ad6s";
60-
const PROXY_ENDPOINT_URL = "https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/CnsdzKsyFKU8Q3h2"
6160
const ENVIRONMENT_ID = "<environment-id>"; // Your environment ID that you received from the SuperTokens team
62-
// Initialize the agent on page load.
63-
const supertokensRequestIdPromise = import(SDK_URL + "?apiKey=" + PUBLIC_API_KEY).then((RequestId: any) => RequestId.load({
64-
endpoint: [
65-
PROXY_ENDPOINT_URL,
66-
RequestId.defaultEndpoint
67-
]
68-
}));
61+
// Initialize the agent on page load using your public API key that you received from the SuperTokens team.
62+
const supertokensRequestIdPromise = require("https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/k9bwGCuvuA83Ad6s?apiKey=<PUBLIC_API_KEY>")
63+
.then((RequestId: any) => RequestId.load({
64+
endpoint: [
65+
'https://deviceid.supertokens.io/PqWNQ35Ydhm6WDUK/CnsdzKsyFKU8Q3h2',
66+
RequestId.defaultEndpoint
67+
]
68+
}));
6969

7070
async function getRequestId() {
7171
const sdk = await supertokensRequestIdPromise;

v2/src/plugins/codeTypeChecking/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,11 @@ Enabled: true,
369369

370370
if (language === "typescript") {
371371
if (codeSnippet.includes("require(")) {
372-
throw new Error("Do not use 'require' in TS code. Error in " + mdFile);
372+
// except for the attack protection suite , where we need to allow require for compatibility reasons,
373+
// as the SDK URL is dynamic and import might break some builds
374+
if (!codeSnippet.includes('require("https://deviceid.supertokens.io')) {
375+
throw new Error("Do not use 'require' in TS code. Error in " + mdFile);
376+
}
373377
}
374378
codeSnippet = `export { }\n// Original: ${mdFile}\n${codeSnippet}`; // see https://www.aritsltd.com/blog/frontend-development/cannot-redeclare-block-scoped-variable-the-reason-behind-the-error-and-the-way-to-resolve-it/
375379

0 commit comments

Comments
 (0)