Step 2: app.js
In the app.js, we need to define the loginOtp component, it will let the component is available for users.
// Define loginOtp component
controllerMappings.addComponent("sampleapp/components", "loginOtp", "html", "Use this for login OTP", "sampleapp");
Because, we are creating an OTP login component, so we need to have 2 steps:
1. User enter the phone number, system generate OTP and send SMS to user. We need a controller call getOtp to handle this.
2. User received OTP and enter it into OTP verification form to login into Kademi system. We need another controller call loginOtp to handle this.
Ok, now we will define the controller which has url /samples/getOtp and handle creating and sending OTP sms:
controllerMappings
.websiteController()
.enabled(true)
.isPublic(true)
.path('/samples/getOtp')
.addMethod('POST', 'getOtp')
.postPriviledge("READ_CONTENT")
.build();
Of course, we need a function name getOtp as defined to process creating and sending OTP sms.
Note that, we have 3 main APIs are used in getOtp controller:
- findMatchingProfiles: This API is under UserManager, help us find user profiles which are match with our provided request. To use this, you need combine with newProfileMatchRequest API of UserManager. See code bellow for example.
- generatePasswordReset: This API is under UserManager, we can use it to generate the reset password token, the inputs are Profile and Website object. In this case, we use reset password token as One Time Password (OTP).
- smsManager: SmsManager provides all APIs which handle SMS issues. In this case, we use it to send SMS. Remember to install KSms and Clicksend App and config Clicksend app in Settings section to send real SMS. Check Clicksend integration documentation here: Clicksend integration
function getOtp(page, params, files, form) {
// Get phone from the login form
var phone = form.cleanedParam('phone');
// Get current website
var wrf = page.find("/");
var website = wrf.website;
// Start userManager service and get profiles which is match with the phone
var um = services.userManager;
var pmr = um.newProfileMatchRequest().phone(phone);
var profs = um.findMatchingProfiles(pmr);
profile = profs.get(0);
transactionManager.runInTransaction(function () {
// Generate password otp using generatePasswordReset
var passwordOtp = um.generatePasswordReset(profile, website);
var message = "Your login OTP is: " + passwordOtp.token;
var smsItem = services.smsManager.send(profile, message);
});
jsonResult = views.jsonResult(true);
return jsonResult;
}
Next, we need to build the second controller loginOtp, which has url samples/loginOtp and handle verifying and logging user into Kademi system.
controllerMappings
.websiteController()
.enabled(true)
.isPublic(true)
.path('/samples/loginOtp')
.addMethod('POST', 'loginOtp')
.postPriviledge("READ_CONTENT")
.build();
Like the first controller, we need a function to handle loginOtp:
Inside loginOtp controller, we used 2 main APIs:
- findPasswordReset: This API is under UserManager and will return PasswordReset object which included user profile. This API need 2 params: reset pasword token - in this case we called it OTP and Website object
- securityManager.authenticate: This API is under SecurityManager and we use it to login an user to Kademi system with Profile's name.
function loginOtp(page, params, files, form) {
// Get otp from user form.
var otp = form.cleanedParam('otp');
// Get current website
var wrf = page.find("/");
var website = wrf.website;
// Get do reset password page via otp
var passwordReset = services.userManager.findPasswordReset(otp, website);
// Do Logging passwordReset
log.info('PASSWORDRESET: {}', passwordReset);
// Check if token is not available
if(formatter.isNull(passwordReset)){
log.info()
return views.jsonResult(false, 'OTP is not found');
}
// Check if token is used
if(!formatter.isNull(passwordReset.usedDate)) {
return views.jsonResult(false, 'OTP is used');
}
// Check if token is expired
var expiredDate = formatter.addDays(passwordReset.createdDate, 30);
if(formatter.between(expiredDate, null, formatter.now)) {
return views.jsonResult(false, 'OTP is expired');
}
// Get user profile which is match with otp
var profile = passwordReset.profile;
// Do login
transactionManager.runInTransaction(function () {
services.securityManager.authenticate(profile.name);
});
jsonResult = views.jsonResult(true, 'Login success!');
return jsonResult;
}
Let's combine all these code blocks above and we will have final app.js as bellow:
// Define loginOtp component
controllerMappings.addComponent("sampleapp/components", "loginOtp", "html", "Use this for login OTP", "sampleapp");
// Define getOtp controller, which will send OTP SMS
controllerMappings
.websiteController()
.enabled(true)
.isPublic(true)
.path('/samples/getOtp')
.addMethod('POST', 'getOtp')
.postPriviledge("READ_CONTENT")
.build();
// Define loginOtp controller, which handle verify OTP and do user login
controllerMappings
.websiteController()
.enabled(true)
.isPublic(true)
.path('/samples/loginOtp')
.addMethod('POST', 'loginOtp')
.postPriviledge("READ_CONTENT")
.build();
function getOtp(page, params, files, form) {
// Get phone from the login form
var phone = form.cleanedParam('phone');
// Get current website
var wrf = page.find("/");
var website = wrf.website;
// Start userManager service and get profiles which is match with the phone
var um = services.userManager;
var pmr = um.newProfileMatchRequest().phone(phone);
var profs = um.findMatchingProfiles(pmr);
profile = profs.get(0);
transactionManager.runInTransaction(function () {
// Generate password otp using generatePasswordReset
var passwordOtp = um.generatePasswordReset(profile, website);
var message = "Your login OTP is: " + passwordOtp.token;
var smsItem = services.smsManager.send(profile, message);
});
jsonResult = views.jsonResult(true);
return jsonResult;
}
function loginOtp(page, params, files, form) {
// Get otp from user form.
var otp = form.cleanedParam('otp');
// Get current website
var wrf = page.find("/");
var website = wrf.website;
// Get do reset password page via otp
var passwordReset = services.userManager.findPasswordReset(otp, website);
// Do Logging passwordReset
log.info('PASSWORDRESET: {}', passwordReset);
// Check if token is not available
if(formatter.isNull(passwordReset)){
log.info()
return views.jsonResult(false, 'OTP is not found');
}
// Check if token is used
if(!formatter.isNull(passwordReset.usedDate)) {
return views.jsonResult(false, 'OTP is used');
}
// Check if token is expired
var expiredDate = formatter.addDays(passwordReset.createdDate, 30);
if(formatter.between(expiredDate, null, formatter.now)) {
return views.jsonResult(false, 'OTP is expired');
}
// Get user profile which is match with otp
var profile = passwordReset.profile;
// Do login
transactionManager.runInTransaction(function () {
services.securityManager.authenticate(profile.name);
});
jsonResult = views.jsonResult(true, 'Login success!');
return jsonResult;
}