Create an Authentication Application within Ceptor Gateway
- Kim Rasmussen
- Kim Rasmussen (Unlicensed)
There are many different ways of authenticating users using Ceptor, here is an example on how to do it by creating a small html/javascript application and hosting it in the Ceptor Gateway
This javascript application will be calling a REST API exposed in Ceptor, called CeptorAuthenticate - this API is provided with Ceptor in samples/ceptor_apis.zip.
You can find an example application in the /auth folder of your Ceptor distribution. This is very easy to modify and style according to your needs. It uses the modern Materialize Front-End javascript framework, available at https://materializecss.com/ - this is very lightweight and allows for easy customization.
This application supports signing up new users, authenticating them using OTP sent via SMS, TOTP Authenticator, or OTP sent via email.
Password reset using one of these MFA methods are also supported.
Sample screenshots
Below are sample screenshots:
Authentication page
2nd step, asking for TOTP (Google Authenticator, Microsoft Authenticator or equivalent) code.
Password reset.
Enter new password, after OTP has been verified.
Auth application
You can find a javascript application in the /auth folder of your Ceptor installation, it contains the following files:
index.html
css/checkforce-stylke.css
css/materialize.min.css
icon/favicon.png
js/ceptor.js
js/axios.min.js
js/checkforce.min.js
js/jquery-3.4.1.min.js
js/materialize.min.jss
<!DOCTYPE html> <html> <head> <!--Let browser know website is optimized for mobile--> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-title" content="Ceptor"> <title>Welcome</title> <link rel="icon" href="icon/favicon.png" type="image/png" /> <!--Import Google Icon Font--> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <!--Import materialize.css--> <link type="text/css" rel="stylesheet" href="css/materialize.min.css" media="screen,projection" /> <link rel="stylesheet" href="css/checkforce-style.css"> </head> <body> <div id="main" class="container"> </div> <!--JavaScript at end of body for optimized loading--> <script type="text/javascript" src="./js/materialize.min.js"></script> <script type="text/javascript" src="./js/axios.min.js"></script> <script type="text/javascript" src="./js/jquery-3.4.1.min.js"></script> <script type="text/javascript" src="./js/checkforce.min.js"></script> <script type="text/javascript" src="./js/ceptor.js"></script> <script> $(document).ready(function(){ Ceptor.init('#main', 'Ceptor'); if (location.hash === "#reset") Ceptor.resetPasswordPage(); else if (location.hash === "#register") Ceptor.registerPage(); else Ceptor.loginPage(); }); </script> </body> </html>
Simply change the name in the call to "Ceptor.init('#main', 'Ceptor')" replacing it with your preferences to control the name on login screen.
All of the front-end logic is contained in the javascript file ceptor.js
From the html page, Ceptor.ini() is called first, then Ceptor.loginPage() which displays the input promting for userid and password.
You can add your own customizations here.
/** * Ceptor login / reset password user interface * * Uses CeptorAuthenticate API in the Gateway - check /ceptorauthenticate/1/?openapi.json for details * */ var locationName; var productName; var pathPrefix = '/ceptorauthenticate/1'; var availableOTPMethods = ['totp','sms','email']; var selectedOTP; var Ceptor = {}; /** * Initialize Ceptor * * @param loc Location to draw content in * @param prod Product name to display on login page */ Ceptor.init = function(loc, prod) { locationName = loc; productName = prod; document.Ceptor = this; } /** * Show login page, but first check status - if already authenticated, instead jump to afterLoginPage() */ Ceptor.loginPage = function() { axios.get(pathPrefix+'/info') .then(function (response) { console.log(response); // handle success if (response.data.logged_on) { document.Ceptor.afterLoginPage(response.data); } else { Ceptor.showActualLoginPage(); } }) .catch(function (error) { console.log(error); document.Ceptor.handleError("Unable to check Login status", error); document.Ceptor.showActualLoginPage(); }); } /** * Show login page prompting for userid and password */ Ceptor.showActualLoginPage = function() { $(locationName).html( '<div id="login-page" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Please Authenticate</h3>'+ ' <div class="black-text center">'+ ' <h5>Thank you for choosing '+$('<div>').text(productName).html()+'</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ ' <div class="card-content">'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="userid" type="text">'+ ' <label for="userid">User ID</label>'+ ' </div>'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">lock_outline</i>'+ ' <input id="password" type="password">'+ ' <label for="password">Password</label>'+ ' </div><br>'+ ' <div class="form-field">'+ ' <button id="loginbutton" class="btn-large waves-effect waves-light blue" style="width:100%" onclick="document.Ceptor.loginClicked()">Login</button>'+ ' </div>'+ ' <div class="input-field col s6 m6 l6">'+ ' <p class="margin medium-small"><a href="#register" onclick="document.Ceptor.registerPage()">Register Now!</a></p>'+ ' </div>'+ ' <div class="input-field col s6 m6 l6">'+ ' <p class="margin right-align medium-small"><a href="#reset" onclick="document.Ceptor.resetPasswordPage()">Forgot password?</a></p>'+ ' </div>'+ ' <br>'+ ' <br>'+ ' </div>'+ '</div>' ); $('#userid').focus(); $('#userid').keyup(function() { if (event.keyCode === 13) { $('#password').focus(); } }); $('#password').keyup(function() { if (event.keyCode === 13) { $('#loginbutton').click(); } }); } /** * Handles logon part 1, of userid and password fields. */ Ceptor.loginClicked = function() { var userid = $('#userid').val(); var password = $('#password').val(); $('#loginbutton').prop( "disabled", true ); axios.post(pathPrefix+'/auth/uidpw', { userid: userid, credentials: password }) .then(function (response) { // handle success Ceptor.afterLoginPage(response.data); }) .catch(function (error) { console.log(error.response.data); if (error.response.data.code == 15) { // ERROR_NEED_OTP availableOTPMethods = error.response.data.otpmethods; // Prompt for OTP Ceptor.loginPageOTP(); } else { Ceptor.handleError("Unable to login", error); } }) .finally(function () { $('#loginbutton').prop( "disabled", false ); }); } /** * Shows 2nd part of login page, prompting for OTP code */ Ceptor.loginPageOTP = function() { var str= '<div id="login-page" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Please Authenticate</h3>'+ ' <div class="black-text center">'+ ' <h5>Multifactor Login required</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ ' <div class="card-content">'+ ' <h5>Select method for One-Time-Pin verification</h5><br>'+ ' <div class="col s12">'+ ' <ul class="tabs">'; if (availableOTPMethods.includes("totp")) str +=' <li class="tab col s3"><a href="#page1" onclick="selectedOTP=\'totp\';">Authenticator</a></li>'; if (availableOTPMethods.includes("sms")) str +=' <li class="tab col s3"><a href="#page2" onclick="selectedOTP=\'sms\';">SMS / Text Message</a></li>'; if (availableOTPMethods.includes("email")) str +=' <li class="tab col s3"><a href="#page3" onclick="selectedOTP=\'email\';">Email</a></li>'; str+=' </ul>'+ ' </div>'; if (availableOTPMethods.includes("totp")) str+=' <div id="page1" class="col s12">'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="totp" type="text">'+ ' <label for="totp">Enter TOTP code from Authenticator App</label>'+ ' </div>'+ ' </div>'; if (availableOTPMethods.includes("sms")) str+=' <div id="page2" class="col s12">'+ ' <div class="input-field" id="sms_otp_container">'+ ' <button id="sendsmsbutton" class="btn-small green" onclick="Ceptor.sendSMS()">Send SMS</button>'+ ' </div>'+ ' <div class="input-field" id="sms_input_container" hidden>'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="sms" type="text">'+ ' <label for="totp">Enter code sent to your mobile phone</label>'+ ' </div>'+ ' </div>'; if (availableOTPMethods.includes("email")) str+=' <div id="page3" class="col s12">'+ ' <div class="input-field" id="email_otp_container">'+ ' <button id="sendemailbutton" class="btn-small green" onclick="Ceptor.sendEmailOTP()">Send email with code</button>'+ ' </div>'+ ' <div class="input-field" id="email_input_container" hidden>'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="emailotp" type="text">'+ ' <label for="totp">Enter code sent to your email</label>'+ ' </div>'+ ' </div>'; str+='</div>'+ '<div class="form-field">'+ ' <button id="loginbutton" class="btn-large waves-effect waves-light blue" onclick="document.Ceptor.loginOTPClicked()" style="width:100%">Verify</button>'+ '</div>'+ '<div class="input-field col s6 m6 l6">'+ ' <p class="margin medium-small"><a href="#" onclick="document.Ceptor.loginPage()">Back</a></p>'+ '</div>'+ '</div>'; $(locationName).html(str); M.Tabs.init($('.tabs'), { duration: 300, swipeable: false }); selectedOTP = availableOTPMethods[0]; $('#'+selectedOTP).focus(); } /** * Sends an SMS with an OTP code to the client */ Ceptor.sendSMS = function() { $('#sendsmsbutton').prop( "disabled", true ); $('#sms_otp_container').fadeOut({duration:100}); axios.put(pathPrefix+'/auth/sendsms') .then(function (response) { // handle success // Show prompt $('#sms_input_container').show({ duration:400, complete: function() {$('#sms').focus();} }); }) .catch(function (error) { $('#sms_otp_container').fadeIn({duration:200}); Ceptor.handleError("Unable to send SMS/Text message", error); }) .finally(function () { $('#sendsmsbutton').prop( "disabled", false ); }); } /** * Sends an email with an OTP code to the client */ Ceptor.sendEmailOTP = function() { $('#login-page').prop( "disabled", true ); $('#email_otp_container').fadeOut({duration:100}); axios.put(pathPrefix+'/auth/sendemail') .then(function (response) { // handle success // Show prompt $('#email_input_container').show({ duration:400, complete: function() {$('#emailotp').focus();} }); }) .catch(function (error) { $('#email_otp_container').fadeIn({duration:200}); Ceptor.handleError("Unable to send SMS/Text message", error); }) .finally(function () { $('#login-page').prop( "disabled", false ); }); } /** * Handles logon part 2, authenticating user using the selected OTP method */ Ceptor.loginOTPClicked = function() { if (selectedOTP === 'sms') { var otp = $('#sms').val(); if (!otp) { $('#sms').focus(); return; } $('#loginbutton').prop( "disabled", true ); axios.post(pathPrefix+'/auth/verifysmsotp?code='+otp) .then(function (response) { // handle success - the login page will fetch /info and go to the confirm page Ceptor.loginPage(); }) .catch(function (error) { Ceptor.handleError("Unable to verify entered code", error); }) .finally(function () { $('#loginbutton').prop( "disabled", false ); }); } else if (selectedOTP === 'totp') { var otp = $('#totp').val(); if (!otp) { $('#totp').focus(); return; } $('#loginbutton').prop( "disabled", true ); axios.post(pathPrefix+'/auth/verifytotp?code='+otp) .then(function (response) { // handle success - the login page will fetch /info and go to the confirm page Ceptor.loginPage(); }) .catch(function (error) { Ceptor.handleError("Unable to verify entered code", error); }) .finally(function () { $('#loginbutton').prop( "disabled", false ); }); } else if (selectedOTP === 'email') { var otp = $('#emailotp').val(); if (!otp) { $('#emailotp').focus(); return; } $('#loginbutton').prop( "disabled", true ); axios.post(pathPrefix+'/auth/verifyemailotp?code='+otp) .then(function (response) { // handle success - the login page will fetch /info and go to the confirm page Ceptor.loginPage(); }) .catch(function (error) { Ceptor.handleError("Unable to verify entered code", error); }) .finally(function () { $('#loginbutton').prop( "disabled", false ); }); } } /** * Page shown after login * * @param info Session info from /info API call */ Ceptor.afterLoginPage = function(info) { $(locationName).html( '<div id="login-result" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Welcome</h3>'+ ' <div class="black-text center">'+ ' <h5>Hello '+$('<div>').text(info.user_name).html()+'</h5>'+ ' </div>'+ ' </div>'+ ' </div>'+ '</div>'+ '<div class="card-panel">'+ ' <span class="blue-text text-darken-2"><h6>Please go to <a href="https://ceptor.io">ceptor.io</a> for more information about Ceptor</h6></span>'+ '</div>'+ '<div class="fixed-action-btn">'+ ' <a class="btn-floating btn-large red pulse">'+ ' <i class="large material-icons">menu</i>'+ ' </a>'+ ' <ul>'+ ' <li><a class="btn-floating blue"onclick="document.Ceptor.registerTOTPPage()"><i class="material-icons">person</i></a></li>'+ ' <li><a class="btn-floating yellow darken-1" onclick="document.Ceptor.logoff()"><i class="material-icons">exit_to_app</i></a></li>'+ ' </ul>'+ '</div>' ); var elems = document.querySelectorAll('.fixed-action-btn'); M.FloatingActionButton.init(elems, {}); } /** * Handles an error received by the server, displaying error message * * @param message Prefix message, e.g. "Login error" - error message from server is appended to it * @param error The error returned from axios client */ Ceptor.handleError = function(message, error) { console.log(error.response); if (error.response.status === 401 || error.response.status === 400) { M.toast({html: '<b>'+message+': </b> ' + $("<div>").text(error.response.data.message).html()}); } else { M.toast({html: '<b>'+message+': </b> ' + $("<div>").text(error).html()}); console.log(error.response.data); } } /** * Show the reset password page */ Ceptor.resetPasswordPage = function() { $(locationName).html( '<div id="reset-page" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Forgot Password?</h3>'+ ' <div class="black-text center">'+ ' <h5>Reset your password here</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ ' <div id="resetcontent" class="card-content">'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="user" type="text">'+ ' <label for="user">User ID</label>'+ ' </div>'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">mail_outline</i>'+ ' <input class="validate" id="email" type="email">'+ ' <label for="email">Email</label>'+ ' <span class="helper-text" data-error="Not an email address" data-success="">Please enter email address</span>'+ ' </div>'+ ' <div id="part2"><br>'+ ' <div class="form-field">'+ ' <button class="btn-large waves-effect waves-light blue" onclick="document.Ceptor.resetPasswordPage2()" style="width:100%">Continue</button>'+ ' </div>'+ ' <div class="input-field col s6 m6 l6">'+ ' <p class="margin medium-small"><a href="#" onclick="document.Ceptor.loginPage()">Back</a></p>'+ ' </div>'+ ' </div>'+ ' </div>'+ '</div>'); $('#user').focus(); } /** * Shows reset password page 2 * * Validate userid and email address are entered, if so, prompt for OTP code, allowing choosing which OTP method to use */ Ceptor.resetPasswordPage2 = function() { // Validate fields first var userid = $("#user").val(); var email = $("#email").val(); if (!userid) { $("#user").focus(); M.toast({html: "Please fill in userid"}); return; } if (!email || !validateEmail(email)) { $("#email").focus(); M.toast({html: "Please fill in a valid email address"}); return; } $("#user").prop( "disabled", true ); $("#email").prop( "disabled", true ); var str = '<h5>Select method for One-Time-Pin verification</h5><br>'+ '<div class="row">'+ ' <div class="col s12">'+ ' <ul class="tabs">'; if (availableOTPMethods.includes("totp")) str +=' <li class="tab col s3"><a href="#page1" onclick="selectedOTP=\'totp\';">Authenticator</a></li>'; if (availableOTPMethods.includes("sms")) str +=' <li class="tab col s3"><a href="#page2" onclick="selectedOTP=\'sms\';">SMS / Text Message</a></li>'; if (availableOTPMethods.includes("email")) str += ' <li class="tab col s3"><a href="#page3" onclick="selectedOTP=\'email\';">Email</a></li>'; str += ' </ul>'+ ' </div>'; if (availableOTPMethods.includes("totp")) str+=' <div id="page1" class="col s12">'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="totp" type="text">'+ ' <label for="totp">Enter TOTP code</label>'+ ' </div>'+ ' </div>'; if (availableOTPMethods.includes("sms")) str+=' <div id="page2" class="col s12">'+ ' <div class="input-field" id="sms_otp_container">'+ ' <button id="sendsmsbutton" class="btn-small green" onclick="Ceptor.resetSMSClicked()">Send SMS</button>'+ ' </div>'+ ' <div class="input-field" id="sms_input_container" hidden>'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="sms" type="text">'+ ' <label for="totp">Enter code sent to your mobile phone</label>'+ ' </div>'+ ' </div>'; if (availableOTPMethods.includes("email")) str+=' <div id="page3" class="col s12">'+ ' <div class="input-field" id="email_otp_container">'+ ' <button id="sendemailbutton" class="btn-small green" onclick="Ceptor.resetEmailClicked()">Send email with code</button>'+ ' </div>'+ ' <div class="input-field" id="email_input_container" hidden>'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="emailotp" type="text">'+ ' <label for="totp">Enter code sent to your email</label>'+ ' </div>'+ ' </div>'; str+='</div>'+ '<div class="form-field">'+ ' <button class="btn-large waves-effect waves-light blue" onclick="document.Ceptor.resetPasswordPage3()" style="width:100%">Verify</button>'+ '</div>'+ '<div class="input-field col s6 m6 l6">'+ ' <p class="margin medium-small"><a href="#" onclick="document.Ceptor.loginPage()">Back</a></p>'+ '</div>'+ ''; $("#part2").html(str); $("#part2").prop("class", "card-panel indigo lighten-5"); M.Tabs.init($('.tabs'), { duration: 300, swipeable: false }); selectedOTP = availableOTPMethods[0]; $('#totp').focus(); } /** * Reset password page 3, after entering userid, email and OTP code, asks for new password if codes are verified * * Validate entered OTP code, if correct then ask for new password */ Ceptor.resetPasswordPage3 = function() { var userid = $("#user").val(); var email = $("#email").val(); var enteredOTP; if (selectedOTP === 'sms') { enteredOTP = $('#sms').val(); if (!enteredOTP) { $('#sms').focus(); M.toast({html: "Please enter OTP"}); return; } } else if (selectedOTP === 'email') { enteredOTP = $('#emailotp').val(); if (!enteredOTP) { $('#emailotp').focus(); M.toast({html: "Please enter OTP"}); return; } } else if (selectedOTP === 'totp') { enteredOTP = $('#totp').val(); if (!enteredOTP) { $('#totp').focus(); M.toast({html: "Please enter OTP"}); return; } } axios.post(pathPrefix+'/resetpasswordotp', { userid: userid, email: email, otptype: selectedOTP, code: enteredOTP, }) .then(function (response) { // handle success $("#part2").html( '<h5>Enter new password</h5><br>'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">lock_outline</i>'+ ' <input id="password1" type="password">'+ ' <label for="password1">Password</label>'+ ' </div>'+ ' <div class="pwstrength_viewport_progress"></div>'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">lock_outline</i>'+ ' <input id="password2" type="password">'+ ' <label for="password2">Verify Password</label>'+ ' </div><br>'+ ' <div class="form-field">'+ ' <button id="setpasswordbutton" class="btn-large waves-effect waves-light blue" onclick="document.Ceptor.resetPasswordPage4()" style="width:100%">Set new password</button>'+ ' </div>'+ ' <div class="input-field col s6 m6 l6">'+ ' <p class="margin medium-small"><a href="#" onclick="document.Ceptor.loginPage()">Back</a></p>'+ ' </div>'+ ''); var render = document.querySelector('.pwstrength_viewport_progress'); CheckForce('#password1',{ MaterializeTheme:true }).checkPassword(function(response){ render.innerHTML = response.content; }); }) .catch(function (error) { Ceptor.handleError("Unable to reset password", error); }); } Ceptor.resetSMSClicked = function() { var userid = $("#user").val(); var email = $("#email").val(); $('#sendsmsbutton').prop( "disabled", true ); $('#sms_otp_container').fadeOut({duration:100}); axios.post(pathPrefix+'/initiateresetpassword', { userid: userid, email: email, otptype: 'sms', }) .then(function (response) { // handle success // Show prompt $('#sms_input_container').show({ duration:400, complete: function() {$('#sms').focus();} }); }) .catch(function (error) { $('#sms_otp_container').fadeIn({duration:200}); Ceptor.handleError("Unable to send SMS/Text message", error); }) .finally(function () { $('#sendsmsbutton').prop( "disabled", false ); }); } Ceptor.resetEmailClicked = function() { var userid = $("#user").val(); var email = $("#email").val(); $('#sendemailbutton').prop( "disabled", true ); $('#email_otp_container').fadeOut({duration:100}); axios.post(pathPrefix+'/initiateresetpassword', { userid: userid, email: email, otptype: 'email', }) .then(function (response) { // handle success // Show prompt $('#email_input_container').show({ duration:400, complete: function() {$('#emailotp').focus();} }); }) .catch(function (error) { $('#email_otp_container').fadeIn({duration:200}); Ceptor.handleError("Unable to send SMS/Text message", error); }) .finally(function () { $('#sendemailbutton').prop( "disabled", false ); }); } /** * Last reset password page - validates passwords entered, then submits the password reset request to the server */ Ceptor.resetPasswordPage4 = function() { // Validate both fields first var userid = $("#user").val(); var password1 = $("#password1").val(); var password2 = $("#password2").val(); if (!password1) { $("#password1").focus(); M.toast({html: "Please enter a password"}); return; } if (!password2) { $("#password2").focus(); M.toast({html: "Please verify password"}); return; } if (password1 != password2) { $("#password2").focus(); M.toast({html: "The passwords must match"}); return; } $('#setpasswordbutton').prop( "disabled", true ); axios.post(pathPrefix+'/resetpassword', { userid: userid, newpassword: password1 }) .then(function (response) { // handle success M.toast({html: "Password reset successfully - please login."}); Ceptor.loginPage(); }) .catch(function (error) { Ceptor.handleError("Unable to reset password", error); }) .finally(function () { $('#setpasswordbutton').prop( "disabled", false ); }); } /** * Displays registration page, prompting for user info */ Ceptor.registerPage = function() { $(locationName).html( '<div id="register-page" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Register</h3>'+ ' <div class="black-text center">'+ ' <h5>Sign up here</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ ' <div class="col s12">'+ ' <div class="row">'+ ' <div class="input-field col s6">'+ ' <i class="material-icons prefix">edit</i>'+ ' <input class="validate" id="firstname" type="text">'+ ' <label for="firstname">First Name</label>'+ ' </div>'+ ' <div class="input-field col s6">'+ ' <input class="validate" id="lastname" type="text">'+ ' <label for="lastname">Last Name</label>'+ ' </div>'+ ' </div>'+ ' <div class="row">'+ ' <div class="input-field col s6">'+ ' <i class="material-icons prefix">mail_outline</i>'+ ' <input class="validate" id="email" type="email">'+ ' <label for="email">Email</label>'+ ' <span class="helper-text" data-error="Not a valid email address" data-success=""></span>'+ ' </div>'+ ' <div class="input-field col s6">'+ ' <i class="material-icons prefix">phone</i>'+ ' <input class="validate" id="mobile" type="text">'+ ' <label for="email">Mobile</label>'+ ' </div>'+ ' </div>'+ ' <div class="row">'+ ' <div class="input-field col s12">'+ ' <i class="material-icons prefix">person</i>'+ ' <input class="validate" id="userid" type="text">'+ ' <label for="userid">User ID</label>'+ ' </div>'+ ' </div>'+ ' <div class="row">'+ ' <div class="input-field col s12">'+ ' <i class="material-icons prefix">lock_outline</i>'+ ' <input id="password1" type="password">'+ ' <label for="password1">Password</label>'+ ' <div class="pwstrength_viewport_progress"></div>'+ ' </div>'+ ' </div>'+ ' <div class="row">'+ ' <div class="input-field col s12">'+ ' <i class="material-icons prefix">lock_outline</i>'+ ' <input id="password2" type="password">'+ ' <label for="password2">Verify Password</label>'+ ' </div>'+ ' </div>'+ ' <div class="form-field">'+ ' <button class="btn-large waves-effect waves-light blue" style="width:100%" onclick="Ceptor.registerClicked()">Register</button>'+ ' </div>'+ ' <div class="input-field col s6 m6 l6">'+ ' <p class="margin medium-small"><a href="#" onclick="document.Ceptor.loginPage()">Back</a></p>'+ ' </div>'+ ' </div>'+ '</div>'); var render = document.querySelector('.pwstrength_viewport_progress'); CheckForce('#password1',{ MaterializeTheme:true }).checkPassword(function(response){ render.innerHTML = response.content; }); $('#firstname').focus(); } Ceptor.registerClicked = function() { var firstname = $("#firstname").val(); var lastname = $("#lastname").val(); var email = $("#email").val(); var mobile = $("#mobile").val(); var userid = $("#userid").val(); var password1 = $("#password1").val(); var password2 = $("#password2").val(); if (!firstname) { $('#firstname').focus(); M.toast({html: "Please enter first name"}); return; } if (!lastname) { $('#lastname').focus(); M.toast({html: "Please enter last name"}); return; } if (!email || !validateEmail(email)) { $('#email').focus(); M.toast({html: "Please enter a valid email address"}); return; } if (!mobile) { $('#mobile').focus(); M.toast({html: "Please enter a valid mobile number"}); return; } if (!userid) { $('#userid').focus(); M.toast({html: "Please enter userid"}); return; } if (!password1) { $("#password1").focus(); M.toast({html: "Please enter a password"}); return; } if (!password2) { $("#password2").focus(); M.toast({html: "Please verify password"}); return; } if (password1 != password2) { $("#password2").focus(); M.toast({html: "The passwords must match"}); return; } axios.post(pathPrefix+'/register', { firstname: firstname, lastname: lastname, email: email, mobile: mobile, userid: userid, password: password1 }) .then(function (response) { // handle success M.toast({html: "Registration successful"}); Ceptor.loginPage(); }) .catch(function (error) { Ceptor.handleError("Unable to reset password", error); }); } /** * Validates email address * * @param {email} email */ function validateEmail(email) { var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); } /** * Calls logoff, and goes back to login page */ Ceptor.logoff = function() { axios.put(pathPrefix+'/auth/logoff') .then(function (response) { // handle success Ceptor.showActualLoginPage(); M.toast({html: "Logged off"}); }) .catch(function (error) { console.log(error); Ceptor.handleError("Unable to logoff", error); Ceptor.loginPage(); }); } Ceptor.registerTOTPPage = function() { $(locationName).html( '<div id="login-page" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Register Authenticator App</h3>'+ ' <div class="black-text center">'+ ' <h5>Please re-enter your password</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ ' <div class="card-content">'+ ' <div class="input-field">'+ ' <i class="material-icons prefix">lock_outline</i>'+ ' <input id="password" type="password">'+ ' <label for="password">Password</label>'+ ' </div><br>'+ ' <div class="form-field">'+ ' <button id="loginbutton" class="btn-large waves-effect waves-light blue" style="width:100%" onclick="document.Ceptor.registerTOTPClicked()">Submit</button>'+ ' </div>'+ ' </div>'+ '</div>' ); $('#password').keyup(function() { if (event.keyCode === 13) { $('#loginbutton').click(); } }); $('#password').focus(); } Ceptor.registerTOTPClicked = function() { var password = $('#password').val(); if (!password) { $("#password").focus(); M.toast({html: "Please enter a password"}); return; } $(locationName).html( '<div id="totp-generation" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Register Authenticator App</h3>'+ ' <div class="black-text center">'+ ' <h5>Please hold, generating secret</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ '</div>'); axios.post(pathPrefix+'/totp/qr', { password: password }) .then(function (response) { // handle success $(locationName).html( '<div id="totp-register-page" class="row">'+ ' <div class="card">'+ ' <div class="card-action blue white-text">'+ ' <h3 class="center">Register Authenticator App</h3>'+ ' <div class="black-text center">'+ ' <h5>Please scan QR code or enter secret in authenticator app</h5>'+ ' </div>'+ ' </div>'+ ' </div><br>'+ ' <div class="card-content">'+ ' <div class="form-field">'+ ' Secret Key: ' + response.data.secret + '<br/>'+ ' <img src="data:image/png;base64,' + response.data.qr + '">'+ ' </div>'+ ' <div class="form-field">'+ ' <button id="loginbutton" class="btn-large waves-effect waves-light blue" style="width:100%" onclick="document.Ceptor.loginPage()">Back</button>'+ ' </div>'+ ' </div>'+ '</div>' ); }) .catch(function (error) { console.log(error); Ceptor.handleError("Unable to register TOTP", error); Ceptor.registerTOTPPage(); }); }
Ceptor Gateway Location
To serve the application to the clients, on /auth/* you need to create a new Location in the gateway, from which the static files in the applications are served to the user.
Create a location called "auth", and section the action to "serve".
Set conditions to match /auth/*
and create an URL rewrite rule, which rewrites /auth/(.*) to /$1 - effectively stripping the /auth/ prefix from the request.
Select the WebServer section, and point the webserver at ${ceptor.home}/auth where the html / javascript application is then served from.
Below is a location configuration which can be copied into the gateway configuration, with the configuration described above.
{ "name": "auth", "description": "Authentication application", "conditions": [{ "type": "path", "deny": false, "lowercase": false, "values": ["/auth/*"] }], "conditions.type": "and", "location.enabled": true, "session.needed": true, "session.afterconditionsmatch": false, "session.override": false, "content.preload": false, "action": "serve", "response.compress": true, "cookiesnapper": {}, "plugin": {}, "webserver": { "path": "${ceptor.home}/auth", "case.sensitive": false, "directory.listing.enabled": false, "paths.canonicalize": true, "follow.links": false, "cache.enabled": true, "cache.changelistener": true, "response.notfound": { "response.status": 404, "response.reason": "Not found", "response.contenttype": "text/html; charset=utf8", "response.content": "<!DOCTYPE html>\n<html>\n<head><title>Not found<\/title><\/head>\n<body><h1>Not found :(<\/h1><\/body>\n<\/html>" } }, "urlrewrite": [{ "name": "remote_auth", "pattern": "/auth/(.*)", "newurl": "/$1", "decode.before.match": true, "clear.query.params": false, "redirect": false, "last": true }] },
CeptorAuthenticate API
The CeptorAuthenticate API is called by JavaScript in the authentication application running in the browser.
The API offers these features:
- Authenticate users using Multi Factor Authentication (MFA)
- Create new users using self-service
- Password reset
OpenAPI Specification
Below is the OpenAPI specification for this API:
Implementation
This API is implemented as scripts within the gateway, below is example of the implementation for each operation in the API:
Note that this implementation uses some of the Authentication Plugins included with Ceptor - these plugins use Ceptors own Ceptor User Administration Server and UserAdmin API to store users. You should modify the implementation to suit your requirements and plugins, in case they differ.
You should remove any operations that offers functionality which you do not wish to expose to your clients - if you e.g. do not want to offer self-registration to clients, remove the part for the UI, and remove the relevant API offering this functionality.
GET /info
This operation returns information about the current session, for use by the application.
var pSessionId = context.id; var isAuthenticated = context.agent.isLoggedOn( pSessionId ); var response = { // Is delayed logoff or not "is_delayed_logoff": context.agent.isDelayedLogoff(pSessionId), // User ID "user_id": context.agent.getUser(pSessionId), // Customer ID "customer_id": isAuthenticated ? context.agent.getCustomerID(pSessionId) : null, // Agreement ID "agreement_id": isAuthenticated ? context.agent.getAgreementID(pSessionId) : null, // Is internal user "internal": isAuthenticated ? context.agent.isInternalUser(pSessionId) : null, // Is logged on "logged_on": isAuthenticated, // Users real name "user_name": context.agent.getUserName(pSessionId), // IP address that created the session "ip": context.agent.getUserIP(pSessionId), // Authentication level "authentication_level": context.agent.getAuthenticationLevel(pSessionId), // Authentication method "authentication_method": context.agent.getAuthenticationMethod(pSessionId), // Array of users groups "groups": null, // Name of creating agent "creating_agent": context.agent.getNameOfCreatingAgent(pSessionId), // Ticket or token "ticket": context.agent.getTicketFromSession(pSessionId) }; try { response.groups = Java.from(context.agent.getUserGroups(pSessionId)); } catch(e) {} context.respond(200, 'OK', 'application/json', JSON.stringify(response));
POST /auth/uidpw
This operation supports authenticating using userid and password. By default, it treats this as first step of a multifactor authentication, where the user must complete authentication using either OTP sent as SMS, OTP sent as Email, or a TOTP code using a previously registered Authenticator application.
If you remove this line:
context.agent.setStateVariable(pSessionId, "require_otp", "true", false);
then two-factor authentication will be disabled, and login will complete after the first userid/password login.
function auth() { var req = JSON.parse(context.getRequestBodyAsString()); context.trace.trace("Authenticate called with userid: " + req.userid); try { var pSessionId = context.id; context.agent.setStateVariable(pSessionId, "require_otp", "true", false); context.agent.logon(pSessionId, req.userid, req.credentials); var isAuthenticated = context.agent.isLoggedOn(pSessionId); var response = { // Is delayed logoff or not "is_delayed_logoff": context.agent.isDelayedLogoff(pSessionId), // User ID "user_id": context.agent.getUser(pSessionId), // Customer ID "customer_id": isAuthenticated ? context.agent.getCustomerID(pSessionId) : null, // Agreement ID "agreement_id": isAuthenticated ? context.agent.getAgreementID(pSessionId) : null, // Is internal user "internal": isAuthenticated ? context.agent.isInternalUser(pSessionId) : null, // Is logged on "logged_on": isAuthenticated, // Users real name "user_name": context.agent.getUserName(pSessionId), // IP address that created the session "ip": context.agent.getUserIP(pSessionId), // Authentication level "authentication_level": context.agent.getAuthenticationLevel(pSessionId), // Authentication method "authentication_method": context.agent.getAuthenticationMethod(pSessionId), // Array of users groups "groups": null, // Name of creating agent "creating_agent": context.agent.getNameOfCreatingAgent(pSessionId), // Ticket or token "ticket": context.agent.getTicketFromSession(pSessionId), // Available OTP methods "otpmethods": [] }; try { response.groups = Java.from(context.agent.getUserGroups(pSessionId)); } catch(e) {} var otpMethods = context.agent.getStateVariable(pSessionId, "otpmethods"); if (otpMethods) { response.otpmethods = otpMethods.split(";"); } context.respond(200, 'OK', 'application/json', JSON.stringify(response)); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Authentication failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (response.code == 15) { // Need OTP var otpmethods = context.agent.getStateVariable(pSessionId, "otpmethods"); if (otpmethods) response.otpmethods = otpmethods.split(';'); } if (!response.message) response.message = "Unknown error"; context.respond(401, 'Authentication Failed', 'application/json', JSON.stringify(response)); } else { throw err; } } } auth();
POST /auth/logoff
This operation simply logs off the user.
context.agent.logoff(context.id); context.respond(201, 'OK', 'application/json', '');
POST /auth/sendsms
This operation generates an OTP code and sends it using SMS/Text to the users mobile number.
try { context.agent.newToken(context.id,43,"sendsms"); context.respond(201, 'SMS Sent', 'application/json',''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("SendSMS failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Send SMS Failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /auth/sendemail
This operation generates an OTP and sends it using email to the users email address.
try { context.agent.newToken(context.id,50,"emailotp"); context.respond(201, 'Email Sent', 'application/json',''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Send Email failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Send Email Failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /auth/verifysmsotp
This operation is used for verifying an entered OTP value sent earlier using SMS/Text message.
try { var credentials = [pCode]; context.agent.logon(context.id,43,context.agent.getUser(context.id), Java.to(credentials, "java.lang.String[]")); context.respond(201, 'OK', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("VerifySMS failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Verify SMS OTP Failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /auth/verifytotp
This operation is used for verifying a TOTP code entered using a previously registered TOTP authenticator.
try { var credentials = [pCode]; context.agent.logon(context.id,45,context.agent.getUser(context.id), Java.to(credentials, "java.lang.String[]")); context.respond(201, 'OK', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Verify TOTP failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Verify TOTP Failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /auth/verifyemailotp
This operation is used for verifying an OTP code earlier sent as email.
try { var credentials = [pCode]; context.agent.logon(context.id,50,context.agent.getUser(context.id), Java.to(credentials, "java.lang.String[]")); context.respond(201, 'OK', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Verify email OTP failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Verify email OTP Failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /initiateresetpassword
This operation initiates a reset password flow - this uses an entered userid/email address, as well as an otptype specyfing if sms, totp or email shoudl be used.
It then compares them against known information, and sends out the OTP code if relevant using the chosen mechanism.
var body = context.getRequestBodyAsString(); try { context.agent.executeAuthpluginCommand(context.id, 9 , 'initresetpassword', body); context.respond(201, 'Password Reset', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Password reset failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /resetpasswordotp
This operation is called after /initiateresetpassword with the entered OTP, to validate if it is correct.
var body = context.getRequestBodyAsString(); try { context.agent.executeAuthpluginCommand(context.id, 9 , 'resetpasswordotp', body); context.respond(201, 'Password Reset', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Password reset failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /resetpassword
This operation is the last step of a password reset, after the OTP has been validated. It allows the user to choose a new password.
var body = context.getRequestBodyAsString(); try { context.agent.executeAuthpluginCommand(context.id, 9 , 'resetpassword', body); context.respond(201, 'Password Reset', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Password reset failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
POST /register
This operation is used for registering new users, allowing them to input their information and get an account created.
function register() { var req = JSON.parse(context.getRequestBodyAsString()); context.trace.trace("Register called with userid: " + req.userid); try { var pSessionId = context.id; var CreateUserCredentials = Java.type("dk.itp.security.passticket.CreateUserCredentials"); var cc = new CreateUserCredentials(); cc.userid = req.userid; cc.password = req.password; cc.firstname = req.firstname; cc.lastname = req.lastname; cc.email = req.email; cc.mobile = req.mobile; context.agent.logon(pSessionId, 9, req.userid, cc); context.respond(201, 'Registration successful', 'application/json', ''); } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("Registration failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(401, 'Registration Failed', 'application/json', JSON.stringify(response)); } else { throw err; } } } register();
POST /totp/qr
This operation is used for registering a new Authenticator Application by retrieving the QR code and secret that can be registered in a mobile authenticator such as MS Authenticator or Google Authenticator
try { if (!context.agent.isLoggedOn(context.id)) { var response = { "code": 18, // AuthErrorCodes.ERROR_NOTAUTHENTICATED "message": "Requires authentication" }; context.respond(403, 'Denied not logged on', 'application/json', JSON.stringify(response)); } else { var body = JSON.parse(context.getRequestBodyAsString()); var response = { // QR Code in PNG format // Format: base64 // Secret value to register "secret": context.agent.newToken(context.id,45, "secretpassword;"+body.password), "qr": context.agent.newToken(context.id,45, "PNG") }; context.respond(200, 'OK', 'application/json', JSON.stringify(response)); } } catch(err) { if (! (err instanceof Java.type('java.lang.Throwable'))) throw err; context.trace.trace("QR Code registration failed", err); if (err instanceof Java.type('dk.itp.security.passticket.PTException')) { var response = { "code": err.getErrorCode(), "message": err.getErrorText() }; if (!response.message) response.message = "Unknown error"; context.respond(400, 'QR Code registration failed', 'application/json', JSON.stringify(response)); } else { throw err; } }
Import API directly
If you prefer, you can create a new API from this API Version specification directly.
{ "implementation": {"Sandbox": { "": {"*": { "proxy": {}, "script": "var response = {\n text: 'Sorry, this API operation has not yet been implemented.'\n};\ncontext.respond(200, 'OK', 'application/json', JSON.stringify(response));", "pipeline": {}, "type": "script" }}, "/hello": {"get": { "proxy": {}, "script": "var response = {\n\t\"text\": \"a string\"\n};\ncontext.respond(200, 'OK', 'application/json', JSON.stringify(response));\n", "pipeline": {}, "type": "script" }}, "/auth/uidpw": {"post": { "proxy": {}, "script": "%{script}function auth() {\n var req = JSON.parse(context.getRequestBodyAsString());\n \n context.trace.trace(\"Authenticate called with userid: \" + req.userid);\n\n try {\n var pSessionId = context.id;\n \n context.agent.setStateVariable(pSessionId, \"require_otp\", \"true\", false);\n context.agent.logon(pSessionId, req.userid, req.credentials);\n \n var isAuthenticated = context.agent.isLoggedOn(pSessionId);\n \n var response = {\n // Is delayed logoff or not\n \"is_delayed_logoff\": context.agent.isDelayedLogoff(pSessionId),\n \t// User ID\n \t\"user_id\": context.agent.getUser(pSessionId),\n \t// Customer ID\n \t\"customer_id\": isAuthenticated ? context.agent.getCustomerID(pSessionId) : null,\n \t// Agreement ID\n \t\"agreement_id\": isAuthenticated ? context.agent.getAgreementID(pSessionId) : null,\n \t// Is internal user\n \t\"internal\": isAuthenticated ? context.agent.isInternalUser(pSessionId) : null,\n \t// Is logged on\n \t\"logged_on\": isAuthenticated,\n \t// Users real name\n \t\"user_name\": context.agent.getUserName(pSessionId),\n \t// IP address that created the session\n \t\"ip\": context.agent.getUserIP(pSessionId),\n \t// Authentication level\n \t\"authentication_level\": context.agent.getAuthenticationLevel(pSessionId),\n \t// Authentication method\n \t\"authentication_method\": context.agent.getAuthenticationMethod(pSessionId),\n \t// Array of users groups\n \t\"groups\": null,\n \t// Name of creating agent\n \t\"creating_agent\": context.agent.getNameOfCreatingAgent(pSessionId),\n \t// Ticket or token\n \t\"ticket\": context.agent.getTicketFromSession(pSessionId),\n \t// Available OTP methods\n \t\"otpmethods\": []\n };\n \n try {\n response.groups = Java.from(context.agent.getUserGroups(pSessionId));\n } catch(e) {}\n \n var otpMethods = context.agent.getStateVariable(pSessionId, \"otpmethods\");\n if (otpMethods) {\n response.otpmethods = otpMethods.split(\";\");\n }\n\n context.respond(200, 'OK', 'application/json', JSON.stringify(response));\n } catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Authentication failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (response.code == 15) { // Need OTP\n var otpmethods = context.agent.getStateVariable(pSessionId, \"otpmethods\");\n if (otpmethods)\n response.otpmethods = otpmethods.split(';');\n }\n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(401, 'Authentication Failed', 'application/json', JSON.stringify(response));\n } else {\n throw err;\n }\n }\n}\n\nauth();\n", "pipeline": {}, "type": "script", "override": true }}, "/info": {"get": { "proxy": {}, "script": "%{script}var pSessionId = context.id;\n\nvar isAuthenticated = context.agent.isLoggedOn( pSessionId );\n\nvar response = {\n // Is delayed logoff or not\n \"is_delayed_logoff\": context.agent.isDelayedLogoff(pSessionId),\n\t// User ID\n\t\"user_id\": context.agent.getUser(pSessionId),\n\t// Customer ID\n\t\"customer_id\": isAuthenticated ? context.agent.getCustomerID(pSessionId) : null,\n\t// Agreement ID\n\t\"agreement_id\": isAuthenticated ? context.agent.getAgreementID(pSessionId) : null,\n\t// Is internal user\n\t\"internal\": isAuthenticated ? context.agent.isInternalUser(pSessionId) : null,\n\t// Is logged on\n\t\"logged_on\": isAuthenticated,\n\t// Users real name\n\t\"user_name\": context.agent.getUserName(pSessionId),\n\t// IP address that created the session\n\t\"ip\": context.agent.getUserIP(pSessionId),\n\t// Authentication level\n\t\"authentication_level\": context.agent.getAuthenticationLevel(pSessionId),\n\t// Authentication method\n\t\"authentication_method\": context.agent.getAuthenticationMethod(pSessionId),\n\t// Array of users groups\n\t\"groups\": null,\n\t// Name of creating agent\n\t\"creating_agent\": context.agent.getNameOfCreatingAgent(pSessionId),\n\t// Ticket or token\n\t\"ticket\": context.agent.getTicketFromSession(pSessionId)\n};\n\ntry {\n response.groups = Java.from(context.agent.getUserGroups(pSessionId));\n} catch(e) {}\n\ncontext.respond(200, 'OK', 'application/json', JSON.stringify(response));\n", "pipeline": {}, "type": "script", "override": true }}, "/auth/logoff": {"put": { "proxy": {}, "script": "%{script}context.agent.logoff(context.id);\ncontext.respond(201, 'OK', 'application/json', '');\n", "pipeline": {}, "type": "script", "override": true }}, "/auth/sendsms": {"put": { "proxy": {}, "script": "%{script}try {\n context.agent.newToken(context.id,43,\"sendsms\");\n \n context.respond(201, 'SMS Sent', 'application/json','');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"SendSMS failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Send SMS Failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/auth/verifysmsotp": {"post": { "proxy": {}, "script": "%{script}try {\n var credentials = [pCode];\n \n context.agent.logon(context.id,43,context.agent.getUser(context.id), Java.to(credentials, \"java.lang.String[]\"));\n \n context.respond(201, 'OK', 'application/json', '');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"VerifySMS failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Verify SMS OTP Failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/auth/verifytotp": {"post": { "proxy": {}, "script": "%{script}try {\n var credentials = [pCode];\n \n context.agent.logon(context.id,45,context.agent.getUser(context.id), Java.to(credentials, \"java.lang.String[]\"));\n \n context.respond(201, 'OK', 'application/json', '');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Verify TOTP failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Verify TOTP Failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/auth/verifyemailotp": {"post": { "proxy": {}, "script": "%{script}try {\n var credentials = [pCode];\n \n context.agent.logon(context.id,50,context.agent.getUser(context.id), Java.to(credentials, \"java.lang.String[]\"));\n \n context.respond(201, 'OK', 'application/json', '');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Verify email OTP failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Verify email OTP Failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/auth/sendemail": {"put": { "proxy": {}, "script": "%{script}try {\n context.agent.newToken(context.id,50,\"emailotp\");\n \n context.respond(201, 'Email Sent', 'application/json','');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Send Email failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Send Email Failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/resetpassword": {"post": { "proxy": {}, "script": "%{script}var body = context.getRequestBodyAsString();\n\ntry {\n context.agent.executeAuthpluginCommand(context.id, 9 , 'resetpassword', body);\n \n context.respond(201, 'Password Reset', 'application/json', '');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Password reset failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/initiateresetpassword": {"post": { "proxy": {}, "script": "%{script}var body = context.getRequestBodyAsString();\n\ntry {\n context.agent.executeAuthpluginCommand(context.id, 9 , 'initresetpassword', body);\n \n context.respond(201, 'Password Reset', 'application/json', '');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Password reset failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/resetpasswordotp": {"post": { "proxy": {}, "script": "%{script}var body = context.getRequestBodyAsString();\n\ntry {\n context.agent.executeAuthpluginCommand(context.id, 9 , 'resetpasswordotp', body);\n \n context.respond(201, 'Password Reset', 'application/json', '');\n} catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Password reset failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response)); \n } else {\n throw err;\n }\n}\n", "pipeline": {}, "type": "script", "override": true }}, "/register": {"post": { "proxy": {}, "script": "%{script}function register() {\n var req = JSON.parse(context.getRequestBodyAsString());\n \n context.trace.trace(\"Register called with userid: \" + req.userid);\n\n try {\n var pSessionId = context.id;\n var CreateUserCredentials = Java.type(\"dk.itp.security.passticket.CreateUserCredentials\");\n var cc = new CreateUserCredentials();\n cc.userid = req.userid;\n cc.password = req.password;\n cc.firstname = req.firstname;\n cc.lastname = req.lastname;\n cc.email = req.email;\n cc.mobile = req.mobile;\n \n context.agent.logon(pSessionId, 9, req.userid, cc);\n \n context.respond(201, 'Registration successful', 'application/json', '');\n } catch(err) {\n if (! (err instanceof Java.type('java.lang.Throwable')))\n throw err;\n \n context.trace.trace(\"Registration failed\", err);\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n var response = {\n \"code\": err.getErrorCode(),\n \"message\": err.getErrorText()\n };\n \n if (!response.message)\n response.message = \"Unknown error\";\n \n context.respond(401, 'Registration Failed', 'application/json', JSON.stringify(response));\n } else {\n throw err;\n }\n }\n}\n\nregister();\n", "pipeline": {}, "type": "script", "override": true }}, "/totp/qr": {"post": { "proxy": {}, "script": "%{script}try {\r\n if (!context.agent.isLoggedOn(context.id)) {\r\n var response = {\r\n \"code\": 18, // AuthErrorCodes.ERROR_NOTAUTHENTICATED\r\n \"message\": \"Requires authentication\"\r\n };\r\n \r\n context.respond(403, 'Denied not logged on', 'application/json', JSON.stringify(response));\r\n } else {\r\n var body = JSON.parse(context.getRequestBodyAsString());\r\n\r\n var response = {\r\n \t// QR Code in PNG format\r\n \t// Format: base64\r\n \t// Secret value to register\r\n \t\"secret\": context.agent.newToken(context.id,45, \"secretpassword;\"+body.password),\r\n \t\"qr\": context.agent.newToken(context.id,45, \"PNG\")\r\n };\r\n context.respond(200, 'OK', 'application/json', JSON.stringify(response));\r\n }\r\n} catch(err) {\r\n if (! (err instanceof Java.type('java.lang.Throwable')))\r\n throw err;\r\n \r\n context.trace.trace(\"QR Code registration failed\", err);\r\n if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\r\n var response = {\r\n \"code\": err.getErrorCode(),\r\n \"message\": err.getErrorText()\r\n };\r\n \r\n if (!response.message)\r\n response.message = \"Unknown error\";\r\n \r\n context.respond(400, 'QR Code registration failed', 'application/json', JSON.stringify(response)); \r\n } else {\r\n throw err;\r\n }\r\n}\r\n", "pipeline": {}, "type": "script", "override": true }} }}, "security": { "authorization": { "noauthorization.for.options": false, "roles": [] }, "authentication": { "apikey": false, "apikey.headername": "ceptor-apikey", "basicauth": false, "clientcert": false, "bearer": false, "oauth2": false, "openidconnect": false, "oauth2.scopes": [], "advanced": {} } }, "id": "9fc1faef-4085-47bd-befe-74eeb287ddf4", "apiid": "5be09f26-784a-4b2d-b75e-5bb82c11bc32", "name": "v1", "basepath": "/ceptorauthenticate/1/", "apitype": "openapi", "cors": true, "private": false, "openapispec": { "openapi": "3.0.0", "info": { "version": "1.0.0", "title": "Ceptauthenticate" }, "paths": { "/info": {"get": { "summary": "Gets info about currently authenticated user/session", "operationId": "info", "responses": { "200": { "description": "Session Info", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Session"}}} }, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } } }}, "/auth/uidpw": {"post": { "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AuthenticateRequest"}}}}, "summary": "Authenticate using userid/password", "responses": { "201": { "description": "Authentication successful", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Session"}}} }, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "description": "Authenticate a user using userid and password", "operationId": "authuidpw", "deprecated": false, "tags": [] }}, "/auth/logoff": {"put": { "summary": "Logoff", "description": "Logoff the user", "operationId": "logoff", "responses": { "201": {"description": "Logoff completed"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } } }}, "/auth/sendsms": {"put": { "summary": "Send SMS", "description": "Send SMS to the user", "operationId": "sendsms", "responses": { "201": {"description": "SMS sent"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } } }}, "/auth/sendemail": {"put": { "summary": "Send Email", "description": "Send OTP via email to the user", "operationId": "sendemail", "responses": { "201": {"description": "Email sent"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } } }}, "/auth/verifysmsotp": {"post": { "summary": "Verify SMS OTP", "description": "Verifies OTP code earlier sent as SMS", "operationId": "verifysms", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "parameters": [{ "name": "code", "description": "Entered OTP code", "in": "query", "required": true, "allowEmptyValue": false, "deprecated": false, "schema": {"type": "string"} }] }}, "/auth/verifytotp": {"post": { "summary": "Verify TOTP", "description": "Verifies TOTP code (Google/MS etc. Authenticator App)", "operationId": "verifytotp", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "parameters": [{ "name": "code", "description": "Entered OTP code", "in": "query", "required": true, "allowEmptyValue": false, "deprecated": false, "schema": {"type": "string"} }] }}, "/auth/verifyemailotp": {"post": { "summary": "Verify email OTP", "description": "Verifies OTP code sent via email", "operationId": "verifyemailotp", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "parameters": [{ "name": "code", "description": "Entered OTP code", "in": "query", "required": true, "allowEmptyValue": false, "deprecated": false, "schema": {"type": "string"} }] }}, "/initiateresetpassword": {"post": { "summary": "Initiate Reset password", "description": "Initiate Reset password, prompting for OTP code - possibly sending challenge using e.g. SMS or email", "operationId": "init resetpassword", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InitiateResetPasswordRequest"}}}} }}, "/resetpasswordotp": {"post": { "summary": "Reset password - verify OTP", "description": "Verify OTP code", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResetPasswordOTPRequest"}}}} }}, "/resetpassword": {"post": { "summary": "Reset password", "description": "Resets your password, by answering OTP challenge", "operationId": "resetpassword", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResetPasswordRequest"}}}} }}, "/register": {"post": { "summary": "Register user", "description": "Self registration", "operationId": "register", "responses": { "201": {"description": "OK"}, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RegisterRequest"}}}} }}, "/totp/qr": {"post": { "summary": "Generate TOTP secret and QR code", "operationId": "totpqr", "responses": { "200": { "description": "TOTP QR code", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TOTPQRResponse"}}} }, "default": { "description": "unexpected error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}} } }, "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TOTPQRRequest"}}}} }} }, "components": {"schemas": { "AuthenticateRequest": { "required": ["userid"], "properties": { "userid": {"type": "string"}, "credentials": {"type": "string"} } }, "Session": {"properties": { "is_delayed_logoff": { "type": "boolean", "description": "True, if delayed logoff is initiated for this session" }, "user_id": { "type": "string", "description": "User ID" }, "customer_id": { "type": "string", "description": "Customer ID" }, "agreement_id": { "type": "string", "description": "Agreement ID" }, "logged_on": { "type": "boolean", "description": "Indicates if a user is authenticated in this session" }, "internal": { "type": "boolean", "description": "Is this user an internal user or not" }, "user_name": { "type": "string", "description": "Users real name" }, "ip": { "type": "string", "description": "IP address that created the session" }, "groups": { "type": "array", "description": "Array of users groups", "items": { "type": "string", "description": "Group/Role name" } }, "authentication_method": { "type": "integer", "description": "Authentication method identifier" }, "authentication_level": { "type": "integer", "description": "Authentication level - the highger, the more secure" }, "creating_agent": { "type": "string", "description": "Name of agent that created this session" }, "ticket": { "type": "string", "description": "Optional ticket for this session - can be JWT token, SAML token, SSL clientcert or anything else used as an alternate key to this session instead of the session ID" } }}, "ResetPasswordRequest": { "description": "Reset password input, OTP must be verified first, or this call will fail", "required": [ "userid", "newpassword" ], "properties": { "userid": { "description": "Userid", "type": "string" }, "newpassword": { "description": "New password", "type": "string" } } }, "ResetPasswordOTPRequest": { "description": "Reset password OTP input", "required": [ "userid", "email", "otptype", "code" ], "properties": { "userid": { "description": "Userid", "type": "string" }, "email": { "description": "Email address - must match existing record", "type": "string" }, "otptype": { "description": "Type of otp", "enum": [ "totp", "email", "sms" ], "type": "string" }, "code": { "description": "OTP code", "type": "string" } } }, "InitiateResetPasswordRequest": { "description": "Initiate Reset password input - used when starting a request to ask for password reset, e.g. to OTP code to be submitted", "required": [ "userid", "email", "otptype" ], "properties": { "userid": { "description": "Userid", "type": "string" }, "email": { "description": "Email address - must match existing record", "type": "string" }, "otptype": { "description": "Type of otp", "enum": [ "totp", "email", "sms" ], "type": "string" } } }, "RegisterRequest": { "description": "Registration form", "required": [ "firstname", "lastname", "userid", "password", "email", "mobile" ], "properties": { "firstname": { "description": "First name", "type": "string" }, "lastname": { "description": "Last name", "type": "string" }, "userid": { "description": "Userid", "type": "string" }, "password": { "description": "Password", "type": "string" }, "email": { "description": "Email address", "type": "string" }, "mobile": { "description": "Mobile number", "type": "string" } } }, "TOTPQRRequest": { "required": ["password"], "properties": {"password": { "type": "string", "format": "password", "description": "User password" }} }, "TOTPQRResponse": { "required": [ "qr", "secret" ], "properties": { "qr": { "type": "string", "format": "base64", "description": "QR Code in PNG format" }, "secret": { "type": "string", "description": "Secret value to register" } } }, "Error": { "required": [ "code", "message" ], "properties": { "code": { "type": "integer", "format": "int32" }, "message": {"type": "string"} } } }}, "security": [], "servers": [{ "url": "https://localhost:8443/ceptorauthenticate/1", "description": "Sandbox environment, used for initial testing of APIs, playing around with new versions" }] }, "tags": [], "requestmodification": { "session.needed": false, "cookiesnapper": {}, "plugin": {} }, "deployed": ["Sandbox"], "override.apiprofile.security": true, "remote.openapispec.loadfromdestination": false, "deprecated": false, "documentation": "" }
© Ceptor ApS. All Rights Reserved.