recaptcha_enterprise/demosite/app/controllers/controller.js (160 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
//
// https://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.
// Sample threshold score for classification of bad / not bad action. The threshold score
// can be used to trigger secondary actions like MFA.
const SAMPLE_THRESHOLD_SCORE = 0.5;
// Error message to be displayed in the client.
const Error = {
INVALID_TOKEN: 'Invalid token',
ACTION_MISMATCH: 'Action mismatch',
SCORE_LESS_THAN_THRESHOLD: 'Returned score less than threshold set',
};
// Label corresponding to assessment analysis.
const Label = {
NOT_BAD: 'Not Bad',
BAD: 'Bad',
};
// 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.
const propertiesReader = require('properties-reader');
const PROPERTIES = propertiesReader('config.properties');
const context = {
project_id: process.env.GOOGLE_CLOUD_PROJECT,
site_key: process.env.SITE_KEY,
};
// Return homepage template.
const home = (req, res) => {
res.render('home', context);
};
// Return signup template.
const signup = (req, res) => {
res.render('signup', context);
};
// Return login template.
const login = (req, res) => {
res.render('login', context);
};
// Return store template.
const store = (req, res) => {
res.render('store', context);
};
// Return comment template.
const comment = (req, res) => {
res.render('comment', context);
};
const {createAssessment} = require('../recaptcha/createAssessment');
// On signup button click, execute reCAPTCHA Enterprise assessment and take action according to the score.
const onSignup = async (req, res) => {
try {
// <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts -->
const recaptchaAction = PROPERTIES.get('recaptcha_action.signup');
const assessmentResponse = await createAssessment(
context.project_id,
context.site_key,
req.body.token,
recaptchaAction
);
// 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 result.label is NOT_BAD:
// Write new username and password to users database.
// let username = req.body.username
// let password = req.body.password
// Business logic.
//
// If result.label is BAD:
// Trigger email/ phone verification flow.
const result = checkForBadAction(assessmentResponse, recaptchaAction);
// <!-- 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.
result.score =
assessmentResponse.riskAnalysis && assessmentResponse.riskAnalysis.score
? assessmentResponse.riskAnalysis.score.toFixed(1)
: (0.0).toFixed(1);
res.json({
data: result,
});
} catch (e) {
res.json({
data: {
error_msg: e,
},
});
}
};
// On login button click, execute reCAPTCHA Enterprise assessment and take action according to the score.
const onLogin = async (req, res) => {
try {
// <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts -->
const recaptchaAction = PROPERTIES.get('recaptcha_action.login');
const assessmentResponse = await createAssessment(
context.project_id,
context.site_key,
req.body.token,
recaptchaAction
);
// 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 result.label is NOT_BAD:
// Check if the login credentials exist and match.
// let username = req.body.username
// let password = req.body.password
// Business logic.
//
// If result.label is BAD:
// Trigger email/ phone verification flow.
const result = checkForBadAction(assessmentResponse, recaptchaAction);
// <!-- 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.
result.score =
assessmentResponse.riskAnalysis && assessmentResponse.riskAnalysis.score
? assessmentResponse.riskAnalysis.score.toFixed(1)
: (0.0).toFixed(1);
res.json({
data: result,
});
} catch (e) {
res.json({
data: {
error_msg: e,
},
});
}
};
// On checkout button click in store page, execute reCAPTCHA Enterprise assessment and take action according to the score.
const onStoreCheckout = async (req, res) => {
try {
// <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts -->
const recaptchaAction = PROPERTIES.get('recaptcha_action.store');
const assessmentResponse = await createAssessment(
context.project_id,
context.site_key,
req.body.token,
recaptchaAction
);
// 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 result.label is NOT_BAD:
// Check if the cart contains items and proceed to checkout and payment.
// let items = req.body.items
// Business logic.
//
// If result.label is BAD:
// Trigger email/ phone verification flow.
const result = checkForBadAction(assessmentResponse, recaptchaAction);
// <!-- 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.
result.score =
assessmentResponse.riskAnalysis && assessmentResponse.riskAnalysis.score
? assessmentResponse.riskAnalysis.score.toFixed(1)
: (0.0).toFixed(1);
res.json({
data: result,
});
} catch (e) {
res.json({
data: {
error_msg: e,
},
});
}
};
// On comment submit, execute reCAPTCHA Enterprise assessment and take action according to the score.
const onCommentSubmit = async (req, res) => {
try {
// <!-- ATTENTION: reCAPTCHA Example (Server Part 1/2) Starts -->
const recaptchaAction = PROPERTIES.get('recaptcha_action.comment');
const assessmentResponse = await createAssessment(
context.project_id,
context.site_key,
req.body.token,
recaptchaAction
);
// 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 result.label is NOT_BAD:
// Check if comment has safe language and proceed to store in database.
// let comment = req.body.comment
// Business logic.
//
// If result.label is BAD:
// Trigger email/ phone verification flow.
const result = checkForBadAction(assessmentResponse, recaptchaAction);
// <!-- 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.
result.score =
assessmentResponse.riskAnalysis && assessmentResponse.riskAnalysis.score
? assessmentResponse.riskAnalysis.score.toFixed(1)
: (0.0).toFixed(1);
res.json({
data: result,
});
} catch (e) {
res.json({
data: {
error_msg: e,
},
});
}
};
// Classify the action as BAD/ NOT_BAD based on conditions specified.
// See https://cloud.google.com/recaptcha/docs/interpret-assessment-website
const checkForBadAction = function (assessmentResponse, recaptchaAction) {
let label = Label.NOT_BAD;
let reason = '';
// Classify the action as BAD if the token obtained from client is not valid.
if (!assessmentResponse.tokenProperties.valid) {
reason = Error.INVALID_TOKEN;
label = Label.BAD;
}
// Classify the action as BAD if the returned recaptcha action doesn't match the expected.
else if (assessmentResponse.tokenProperties.action !== recaptchaAction) {
reason = Error.ACTION_MISMATCH;
label = Label.BAD;
}
// Classify the action as BAD if the returned score is less than or equal to the threshold set.
else if (assessmentResponse.riskAnalysis.score <= SAMPLE_THRESHOLD_SCORE) {
reason = Error.SCORE_LESS_THAN_THRESHOLD;
label = Label.BAD;
}
return {label: label, reason: reason};
};
module.exports = {
home,
signup,
login,
store,
comment,
onSignup,
onLogin,
onStoreCheckout,
onCommentSubmit,
};