SiteGenesis Passwords
This topic describes how to create secure passwords for SiteGenesis Pipelines (SGPP) and SiteGenesis JavaScript Controllers (SGJC).
Salesforce B2C Commerce has changed the platform requirements for password account creation as part of an ongoing effort to help our customers improve site security.
- at least eight characters
- at least one uppercase character
- at least one lowercase character
- at least one number
- no spaces
- no special characters
You can now set password requirements in Business Manager. This means that you don't have to create a regex in your code to change your password requirements for each password form. Instead, you can set them in Business Manager for each customer list and reference them in your SiteGenesis code.
Migrating to More Secure Passwords
If your site is built on SGPP or SGJC, B2C Commerce recommends that you upgrade the security of your storefront passwords. You can do this by making sure your SiteGenesis password validation code has requirements at least as strong as those set for your customer list or by changing your code to reference the customer list requirements. The recommended password requirements are best practice to protect your customers.
Configure Customer Security Settings
- Password minimum length: 8
- Enforce letters in passwords (including case sensitivity): true
- Enforce numbers in passwords: true
- Enforce special characters in passwords: true
- Maximum age of passwords: (any value).
SiteGenesis does not include the default password requirements in the code for SGPP or SGJC, so you must add it if your storefront site is based on either of these versions of SiteGensis.
Salesforce recommends that you use the configurable, server-side validation available through the B2C Commerce Script API and inform the user about the configured settings. If you do not, your account creation form might error out and not let new accounts be created.
Update Storefront Forms for Pipelines or Controllers
In the /Templates/Default/Account/User/Registration.isml File
<isinputfield formfield="${pdict.CurrentForms.profile.login.password}" type="password" dynamicname="true" attributes="${autocomplete_attributes}"/>
Add the following code for the field, so that the customer gets feedback on how they need to change their password so that it can pass the validation:
<div class="form-row label-inline form-indent">
<isinclude template="account/passwordhint"/>
</div>
<isif condition="${!(customer.authenticated && customer.registered)}">
<isinputfield formfield="${pdict.CurrentForms.profile.login.passwordconfirm}" type="password" dynamicname="true" attributes="${autocomplete_attributes}"/>
<isif condition="${pdict.passwordnomatch && pdict.passwordnomatch == true}">
<div class="error">
<div class="form-caption error-message">${Resource.msg('profile.passwordnomatch','forms',null)}</div>
</div>
</isif>
Find the field for the profile login password and add
validation.
<isinputfield formfield="${pdict.CurrentForms.profile.login.currentpassword}" type="password" dynamicname="true" attributes="${autocomplete_attributes}"/>
<isif condition="${pdict.passwordisbad && pdict.passwordisbad == 'true'}">
<div class="error">
<div class="form-caption error-message">${Resource.msg('profile.currentpasswordnomatch','forms',null)}</div>
</div>
</isif>
<isinputfield formfield="${pdict.CurrentForms.profile.login.newpassword}" type="password" dynamicname="true" attributes="${autocomplete_attributes}"/>
<div class="form-row label-inline form-indent">
<isinclude template="account/passwordhint"/>
</div>
<isinputfield formfield="${pdict.CurrentForms.profile.login.newpasswordconfirm}" type="password" dynamicname="true" attributes="${autocomplete_attributes}"/>
<isif condition="${pdict.passwordnomatch && pdict.passwordnomatch == 'true'}">
<div class="error">
<div class="form-caption error-message">${Resource.msg('profile.passwordnomatch','forms',null)}</div>
</div>
</isif>
<iselse/>
Add a new script to validate the password form field.
/**
* CustomerPasswordValidator.ds
*
* This script gets a submitted password
*
* @input customterPasswordFormField : dw.web.FormField the password field being tested
*/
importPackage( dw.web );
exports.validateCustomerPassword = function( customterPasswordFormField : FormField )
{
var customerMgr = require('dw/customer/CustomerMgr');
var isAcceptable = customerMgr.isAcceptablePassword(customterPasswordFormField.value);
return isAcceptable;
}
Updating Pipelines
To update your forms to accommodate the new password requirements, you must edit the following SiteGenesis files in the app_storefront_core cartridge, or the equivalent functionality in your custom code:
If you are using pipelines, in the /pipelines/Account.xml file:
- Find the Assign node that is directly before the decision node for logging in a new customer.
-
Add a
passwordisbad
property and assign a default value offalse
to the property. -
Find the transition out of the InvalidateFormElement and add an Assign element that includes the
passwordisbad
property and assignstrue
as the value.
Updating Controllers
If you are using controllers, in the controllers/Account.js file:
Edit the EditProfile Function
editProfile()
function, add
passwordnomatch
and passwordisbad
keys
with values from the HTTPParameterMap
to the data
model.
app.getView({
passwordnomatch: request.httpParameterMap.passwordnomatch,
passwordisbad: request.httpParameterMap.passwordisbad,
bctext2: Resource.msg('account.user.registration.editaccount', 'account', null),
Action: 'edit',
ContinueURL: URLUtils.https('Account-EditForm')
}).render('account/user/registration');
}
Edit the EditForm Changepassword Action
In the editForm()
function, for the
handleForm
function changepassword
action, add a currentPasswordIsValid
variable and a
newPasswordsMatch
variable with a default value of
true
.
changepassword: function () {
var isProfileUpdateValid = true;
var hasEditSucceeded = false;
var currentPasswordIsValid = true;
var newPasswordsMatch = true;
var Customer = app.getModel('Customer');
Set the
currentPasswordIsValid
variable or
newPasswordsMatch
variable to false
if
the customer profile is invalid and true
if it's valid
and the profile edit succeeds:
if (!Customer.checkUserName()) {
app.getForm('profile.customer.email').invalidate();
isProfileUpdateValid = false;
currentPasswordIsValid = false;
}
if (app.getForm('profile.login.newpassword').value() !== app.getForm('profile.login.newpasswordconfirm').value()) {
isProfileUpdateValid = false;
newPasswordsMatch = false;
}
if (isProfileUpdateValid) {
hasEditSucceeded = Customer.editAccount(app.getForm('profile.customer.email').value(), app.getForm('profile.login.newpassword').value(), app.getForm('profile.login.currentpassword').value(), app.getForm('profile'));
if (!hasEditSucceeded) {
currentPasswordIsValid = false;
}
}
if (isProfileUpdateValid && hasEditSucceeded) {
response.redirect(URLUtils.https('Account-Show'));
} else {
if (!currentPasswordIsValid) {
response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true', 'passwordisbad', 'true'));
} else if (!newPasswordsMatch) {
response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true', 'passwordnomatch', 'true'));
}
}
Edit the EditForm Error Action
In the editForm()
function, for the
handleForm
function error
action, add
additional error handling if the password doesn't meet the
requirements.
error: function () {
// first, make sure current password is good
if (app.getForm('profile.login.currentpassword').value()) {
var Customer = app.getModel('Customer');
var validPassword = Customer.checkPassword(app.getForm('profile.login.currentpassword').value());
if (!validPassword) {
response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true', 'passwordisbad', 'true'));
}
}
var complianceErrors = false;
if (app.getForm('profile.login.password').value() && !app.getForm('profile.login.password').object.valid) {
app.getForm('profile.login.password').invalidate();
complianceErrors = true;
}
if (app.getForm('profile.login.newpassword').value() && !app.getForm('profile.login.newpassword').object.valid) {
app.getForm('profile.login.newpassword').invalidate();
complianceErrors = true;
}
if (app.getForm('profile.login.newpasswordconfirm').value() && !app.getForm('profile.login.newpasswordconfirm').object.valid) {
app.getForm('profile.login.newpasswordconfirm').invalidate();
complianceErrors = true;
}
if (complianceErrors) {
response.redirect(URLUtils.https('Account-EditProfile', 'invalid', 'true'));
}
}
Edit the SetNewPasswordForm Error Action
In the setNewPasswordForm()
function,
for the handleForm
function error
action, render the setnewpassword
page for the
form.
error: function () {
app.getView({
ContinueURL: URLUtils.https('Account-SetNewPasswordForm')
}).render('account/password/setnewpassword');
},
In the startRegister()
function, add
passwordnomatch
with a value from the
HTTPParameterMap
to the data model.
app.getView({
passwordnomatch: request.httpParameterMap.passwordnomatch,
ContinueURL: URLUtils.https('Account-RegistrationForm')
}).render('account/user/registration');
Edit the RegistrationForm Error Action
In the registrationForm()
function, for the
handleForm
function error
action,
validate the profile login password and render the
setnewpassword
page for the form.
app.getForm('profile').handleAction({
error: function () {
// Profile Password field validation
if (app.getForm('profile.login.password').value() && !app.getForm('profile.login.password').object.valid) {
app.getForm('profile.login.password').invalidate();
}
if (app.getForm('profile.login.passwordconfirm').value() && !app.getForm('profile.login.passwordconfirm').object.valid) {
app.getForm('profile.login.passwordconfirm').invalidate();
}
app.getView({
ContinueURL: URLUtils.https('Account-RegistrationForm')
}).render('account/user/registration');
},
Edit the RegistrationForm Confirm Action
In the registrationForm()
function,
for the handleForm
function confirm
action, if the profile login isn't valid, set
passwordsmatch
to false
, set
passwordnomatch
to true
, and render the
registration page.
confirm: function () {
var email, emailConfirmation, orderNo, profileValidation, password, passwordConfirmation, existingCustomer, Customer, target;
var passwordsmatch = true;
...
if (password !== passwordConfirmation) {
app.getForm('profile.login.passwordconfirm').invalidate();
profileValidation = false;
passwordsmatch = false;
}
...
if (!profileValidation) {
if (!passwordsmatch) {
app.getView({
passwordnomatch: true,
ContinueURL: URLUtils.https('Account-RegistrationForm')
}).render('account/user/registration');