Skip to content

fix: update demosite to reflect error messages in client #9867

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions recaptcha_enterprise/demosite/app/static/demo-6df0841a.js
Original file line number Diff line number Diff line change
Expand Up @@ -2247,7 +2247,7 @@ var demoCSS = i$3`
margin-bottom: var(--size-huge);
}
#guide .text,
#guide #verdict + .scoreExample {
#guide #label + .scoreExample {
margin-bottom: var(--size-xhuge);
}
#guide p,
Expand Down Expand Up @@ -2372,7 +2372,7 @@ var demoCSS = i$3`
animation: .5s ease-out 0s 2 alternate both paused drawerBump;
}
#guide .response,
#verdict p,
#label p,
.scoreExample {
transition: max-height var(--full-lapse) ease-out var(--half-lapse),
opacity var(--full-lapse) ease-out var(--half-lapse);
Expand All @@ -2383,7 +2383,7 @@ var demoCSS = i$3`
opacity: 0;
}
.scored #guide .response,
.scored #verdict p,
.scored #label p,
.scored .scoreExample {
opacity: 1;
}
Expand Down Expand Up @@ -3700,7 +3700,8 @@ class RecaptchaDemo extends s {
step: { type: String },
/* Result */
score: { type: String },
verdict: { type: String },
label: { type: String },
reason: { type: String },
};

constructor() {
Expand All @@ -3714,7 +3715,8 @@ class RecaptchaDemo extends s {
/* Result */
this._score = undefined;
this.score = this._score;
this.verdict = undefined;
this.label = undefined;
this.reason = undefined;
/* Other */
this.cleanupGame = () => {};
/* In the year of our lord 2023 */
Expand Down Expand Up @@ -3850,7 +3852,7 @@ class RecaptchaDemo extends s {
}

handleSubmit() {
if (this.score && this.verdict) {
if (this.score && this.label) {
this.goToNextStep();
return;
}
Expand Down Expand Up @@ -4092,7 +4094,7 @@ class RecaptchaDemo extends s {
"tokenProperties": {
"action": "${ACTIONS[this.step]}",
...
"valid": true
"valid": ${this.reason !== 'Invalid token'}
},
}`
.replace(/^([ ]+)[}](?!,)/m, "}")
Expand Down Expand Up @@ -4177,7 +4179,7 @@ class RecaptchaDemo extends s {
const score = this.score && this.score.slice(0, 3);
const percentage = score && Number(score) * 100;
let card = null;
switch (this.verdict) {
switch (this.label) {
case "Not Bad":
card = x`
<p>reCAPTCHA is ${percentage || "???"}% confident you're not bad.</p>
Expand All @@ -4186,7 +4188,7 @@ class RecaptchaDemo extends s {
break;
case "Bad":
card = x`
<p>reCAPTCHA is ${percentage || "???"}% confident you're not bad.</p>
<p>Suspicious request. Reason: "${this.reason}".</p>
<img alt="Bad" src="${badbad}" />
`;
break;
Expand All @@ -4200,7 +4202,7 @@ class RecaptchaDemo extends s {
`;
}
return x`
<div id="verdict">
<div id="label">
<div id="score">
<div class="score">${score || "–"}</div>
${card}
Expand Down Expand Up @@ -4448,10 +4450,10 @@ class RecaptchaDemo extends s {
animating: this.animating,
drawerOpen: this.step !== "game" && this.drawerOpen,
drawerClosed: this.step === "game" || !this.drawerOpen,
scored: this.score && this.verdict,
scored: this.score && this.label,
sitemapOpen: this.sitemapOpen,
sitemapClosed: !this.sitemapOpen,
unscored: !this.score || !this.verdict,
unscored: !this.score || !this.label,
})}"
id="demo"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ function fetchServerResponse({ body, url }) {
// This code is internal to the demo.
// It passes the score to the demo to display it.
function useAssessmentInClient(score) {
if (score?.data?.score && score?.data?.verdict) {
if (score?.data?.score && score?.data?.label) {
const demoElement = document.querySelector("recaptcha-demo");
demoElement.setAttribute("score", score?.data?.score);
demoElement.setAttribute("verdict", score?.data?.verdict);
demoElement.setAttribute("label", score?.data?.label);
demoElement.setAttribute("reason", score?.data?.reason);
}
}
185 changes: 114 additions & 71 deletions recaptcha_enterprise/demosite/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import configparser
import enum
import json
import os
from typing import Tuple

from flask import jsonify, render_template, request, Response
from google.cloud.recaptchaenterprise_v1 import Assessment

from backend.create_recaptcha_assessment import create_assessment

Expand All @@ -36,6 +39,17 @@
assert "recaptcha_actions" in config


class Error(enum.Enum):
INVALID_TOKEN = "Invalid token"
ACTION_MISMATCH = "Action mismatch"
SCORE_LESS_THAN_THRESHOLD = "Returned score less than threshold set"


class Label(enum.Enum):
NOT_BAD = "Not Bad"
BAD = "Bad"


# Return homepage template.
def home() -> str:
return render_template(template_name_or_list="home.html", context=context)
Expand All @@ -44,6 +58,7 @@ def home() -> str:
# On homepage load, execute reCAPTCHA Enterprise assessment and take action according to the score.
def on_homepage_load() -> Response:
try:
reason = ""
recaptcha_action = config["recaptcha_actions"]["home"]
json_data = json.loads(request.data)

Expand All @@ -55,25 +70,26 @@ def on_homepage_load() -> Response:
)

# Check if the token is valid, score is above threshold score and the action equals expected.
if assessment_response.token_properties.valid and \
assessment_response.risk_analysis.score > SAMPLE_THRESHOLD_SCORE and \
assessment_response.token_properties.action == recaptcha_action:
# Load the home page.
# Business logic.
# Classify the action as not bad.
verdict = "Not Bad"
else:
# If any of the above condition fails, trigger email/ phone verification flow.
# Classify the action as bad.
verdict = "Bad"
# Take action based on the result (BAD/ NOT_BAD).
#
# If 'label' is NOT_BAD:
# Load the home page.
# Business logic.
#
# If 'label' is BAD:
# Trigger email/ phone verification flow.
label, reason = check_for_bad_action(assessment_response, recaptcha_action)
# <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Ends -->

# Return the risk score.
# Below code is only used to send response to the client for demo purposes.
# DO NOT send scores or other assessment response to the client.
# Return the response.
return jsonify(
{
"data": {
"score": "{:.1f}".format(assessment_response.risk_analysis.score),
"verdict": verdict,
"label": label,
"reason": reason,
}
}
)
Expand All @@ -100,27 +116,28 @@ def on_signup() -> Response:
)

# Check if the token is valid, score is above threshold score and the action equals expected.
if assessment_response.token_properties.valid and \
assessment_response.risk_analysis.score > SAMPLE_THRESHOLD_SCORE and \
assessment_response.token_properties.action == recaptcha_action:
# Write new username and password to users database.
# username = json_data["username"]
# password = json_data["password"]
# Business logic.
# Classify the action as not bad.
verdict = "Not Bad"
else:
# If any of the above condition fails, trigger email/ phone verification flow.
# Classify the action as bad.
verdict = "Bad"
# Take action based on the result (BAD/ NOT_BAD).
#
# If 'label' is NOT_BAD:
# Write new username and password to users database.
# username = json_data["username"]
# password = json_data["password"]
# Business logic.
#
# If 'label' is BAD:
# Trigger email/ phone verification flow.
label, reason = check_for_bad_action(assessment_response, recaptcha_action)
# <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Ends -->

# Return the risk score.
# Below code is only used to send response to the client for demo purposes.
# DO NOT send scores or other assessment response to the client.
# Return the response.
return jsonify(
{
"data": {
"score": "{:.1f}".format(assessment_response.risk_analysis.score),
"verdict": verdict,
"label": label,
"reason": reason,
}
}
)
Expand All @@ -147,27 +164,28 @@ def on_login() -> Response:
)

# Check if the token is valid, score is above threshold score and the action equals expected.
if assessment_response.token_properties.valid and \
assessment_response.risk_analysis.score > SAMPLE_THRESHOLD_SCORE and \
assessment_response.token_properties.action == recaptcha_action:
# Check if the login credentials exist and match.
# username = json_data["username"]
# password = json_data["password"]
# Business logic.
# Classify the action as not bad.
verdict = "Not Bad"
else:
# If any of the above condition fails, trigger email/phone verification flow.
# Classify the action as bad.
verdict = "Bad"
# Take action based on the result (BAD/ NOT_BAD).
#
# If 'label' is NOT_BAD:
# Check if the login credentials exist and match.
# username = json_data["username"]
# password = json_data["password"]
# Business logic.
#
# If 'label' is BAD:
# Trigger email/phone verification flow.
label, reason = check_for_bad_action(assessment_response, recaptcha_action)
# <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Ends -->

# Return the risk score.
# Below code is only used to send response to the client for demo purposes.
# DO NOT send scores or other assessment response to the client.
# Return the response.
return jsonify(
{
"data": {
"score": "{:.1f}".format(assessment_response.risk_analysis.score),
"verdict": verdict,
"label": label,
"reason": reason,
}
}
)
Expand All @@ -194,26 +212,27 @@ def on_store_checkout() -> Response:
)

# Check if the token is valid, score is above threshold score and the action equals expected.
if assessment_response.token_properties.valid and \
assessment_response.risk_analysis.score > SAMPLE_THRESHOLD_SCORE and \
assessment_response.token_properties.action == recaptcha_action:
# Check if the cart contains items and proceed to checkout and payment.
# items = json_data["items"]
# Business logic.
# Classify the action as not bad.
verdict = "Not Bad"
else:
# If any of the above condition fails, trigger email/phone verification flow.
# Classify the action as bad.
verdict = "Bad"
# Take action based on the result (BAD/ NOT_BAD).
#
# If 'label' is NOT_BAD:
# Check if the cart contains items and proceed to checkout and payment.
# items = json_data["items"]
# Business logic.
#
# If 'label' is BAD:
# Trigger email/phone verification flow.
label, reason = check_for_bad_action(assessment_response, recaptcha_action)
# <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Ends -->

# Return the risk score.
# Below code is only used to send response to the client for demo purposes.
# DO NOT send scores or other assessment response to the client.
# Return the response.
return jsonify(
{
"data": {
"score": "{:.1f}".format(assessment_response.risk_analysis.score),
"verdict": verdict,
"label": label,
"reason": reason,
}
}
)
Expand All @@ -240,28 +259,52 @@ def on_comment_submit() -> Response:
)

# Check if the token is valid, score is above threshold score and the action equals expected.
if assessment_response.token_properties.valid and \
assessment_response.risk_analysis.score > SAMPLE_THRESHOLD_SCORE and \
assessment_response.token_properties.action == recaptcha_action:
# Check if comment has safe language and proceed to store in database.
# comment = json_data["comment"]
# Business logic.
# Classify the action as not bad.
verdict = "Not Bad"
else:
# If any of the above condition fails, trigger email/phone verification flow.
# Classify the action as bad.
verdict = "Bad"
# Take action based on the result (BAD/ NOT_BAD).
#
# If 'label' is NOT_BAD:
# Check if comment has safe language and proceed to store in database.
# comment = json_data["comment"]
# Business logic.
#
# If 'label' is BAD:
# Trigger email/phone verification flow.
label, reason = check_for_bad_action(assessment_response, recaptcha_action)
# <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Ends -->

# Return the risk score.
# Below code is only used to send response to the client for demo purposes.
# DO NOT send scores or other assessment response to the client.
# Return the response.
return jsonify(
{
"data": {
"score": "{:.1f}".format(assessment_response.risk_analysis.score),
"verdict": verdict,
"label": label,
"reason": reason,
}
}
)
except ValueError or Exception as e:
return jsonify({"data": {"error_msg": str(e.__dict__)}})


# Classify the action as BAD/ NOT_BAD based on conditions specified.
def check_for_bad_action(assessment_response: Assessment, recaptcha_action: str) -> Tuple[str, str]:
reason = ""
label = Label.NOT_BAD.value

# Classify the action as BAD if the token obtained from client is not valid.
if not assessment_response.token_properties.valid:
reason = Error.INVALID_TOKEN.value
label = Label.BAD.value

# Classify the action as BAD if the returned recaptcha action doesn't match the expected.
elif assessment_response.token_properties.action != recaptcha_action:
reason = Error.ACTION_MISMATCH.value
label = Label.BAD.value

# Classify the action as BAD if the returned score is less than or equal to the threshold set.
elif assessment_response.risk_analysis.score <= SAMPLE_THRESHOLD_SCORE:
reason = Error.SCORE_LESS_THAN_THRESHOLD.value
label = Label.BAD.value

return label, reason