recaptcha_enterprise/demosite/app/urls.py (136 lines of code) (raw):

# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import configparser import enum import json import os from flask import jsonify, render_template, request, Response from google.cloud.recaptchaenterprise_v1 import Assessment from backend.create_recaptcha_assessment import create_assessment # Sample threshold score for classification of bad / not bad action. The threshold score # can be used to trigger secondary actions like MFA. SAMPLE_THRESHOLD_SCORE = 0.50 context = { "project_id": os.environ["GOOGLE_CLOUD_PROJECT"], "site_key": os.environ["SITE_KEY"], } # Parse config file and read available reCAPTCHA actions. All reCAPTCHA actions registered in the client # should be mapped in the config file. This will be used to verify if the token obtained during assessment # corresponds to the claimed action. config = configparser.ConfigParser() config.read("config.ini") 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) # Return signup template. def signup() -> str: return render_template(template_name_or_list="signup.html", context=context) # On signup button click, execute reCAPTCHA Enterprise assessment and take action according to the score. def on_signup() -> Response: try: recaptcha_action = config["recaptcha_actions"]["signup"] json_data = json.loads(request.data) # <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts --> assessment_response = create_assessment( context.get("project_id"), context.get("site_key"), json_data["token"], recaptcha_action, ) # Check if the token is valid, score is above threshold score and the action equals expected. # 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 --> # 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": f"{assessment_response.risk_analysis.score:.1f}", "label": label, "reason": reason, } } ) except ValueError or Exception as e: return jsonify({"data": {"error_msg": str(e.__dict__)}}) # Return login template. def login() -> str: return render_template(template_name_or_list="login.html", context=context) # On login button click, execute reCAPTCHA Enterprise assessment and take action according to the score. def on_login() -> Response: try: recaptcha_action = config["recaptcha_actions"]["login"] json_data = json.loads(request.data) # <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts --> assessment_response = create_assessment( context.get("project_id"), context.get("site_key"), json_data["token"], recaptcha_action, ) # Check if the token is valid, score is above threshold score and the action equals expected. # 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 --> # 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": f"{assessment_response.risk_analysis.score:.1f}", "label": label, "reason": reason, } } ) except ValueError or Exception as e: return jsonify({"data": {"error_msg": str(e.__dict__)}}) # Return store template. def store() -> str: return render_template(template_name_or_list="store.html", context=context) # On checkout button click in store page, execute reCAPTCHA Enterprise assessment and take action according to the score. def on_store_checkout() -> Response: try: recaptcha_action = config["recaptcha_actions"]["store"] json_data = json.loads(request.data) # <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts --> assessment_response = create_assessment( context.get("project_id"), context.get("site_key"), json_data["token"], recaptcha_action, ) # Check if the token is valid, score is above threshold score and the action equals expected. # 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 --> # 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": f"{assessment_response.risk_analysis.score:.1f}", "label": label, "reason": reason, } } ) except ValueError or Exception as e: return jsonify({"data": {"error_msg": str(e.__dict__)}}) # Return comment template. def comment() -> str: return render_template(template_name_or_list="comment.html", context=context) # On comment submit, execute reCAPTCHA Enterprise assessment and take action according to the score. def on_comment_submit() -> Response: try: recaptcha_action = config["recaptcha_actions"]["comment"] json_data = json.loads(request.data) # <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts --> assessment_response = create_assessment( context.get("project_id"), context.get("site_key"), json_data["token"], recaptcha_action, ) # Check if the token is valid, score is above threshold score and the action equals expected. # 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 --> # 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": f"{assessment_response.risk_analysis.score:.1f}", "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. # See https://cloud.google.com/recaptcha/docs/interpret-assessment-website 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