- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-28-2022 12:43 PM
Hi, i am working on configuring a ui page as a custom login into our application and am struggling for results. My ui page loads ,accepts data, and then only refreshes. I grab the input data and store it with my client script, then call a script include in order to decrypt the password and then id like to just check that decrypted pass is the same as input pass and finally redirect to our page if it is confirmed, however i seem to be having trouble with the script include. I cannot even verify that the script include is being called or run as no matter what i try i do not have access to anything within the class at any point it seems. html is onclick=validate()
most of the elements work, im just running into problems with the script include and I have tried a wide variety of random solutions to try to get it to work and none of them have even been consistently failing right
not yet concerned about the redirect, just struggling mostly on getting pass decrypted through include
script include
client script
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-29-2022 05:26 AM
I am not 100% sure what you are trying to achieve, but if you want to make this page public (accessible by not-logged-in users) you have to make the following adjustments:
- Add the UI Page to the public pages (sys_public.list)
- Add the following to your script include
isPublic: function (} { return true; },​
- The GlideRecord calls in the Client Script will afaik not work if you try this for an unauthenticated (not logged in) user.
Instead, consider doing the whole authentication check inside your script include (adjust credentialTable/userNameField/passwordField in the initialize according to your needs):
var passwordDecryptor = Class.create();
passwordDecryptor.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
initialize: function(request, responseXML, gc) {
global.AbstractAjaxProcessor.prototype.initialize.apply(this, arguments);
this.credentialTable = 'x_772220_mcp_porta_sla_user';
this.userNameField = 'username';
this.passwordField = 'password';
},
validate: function () {
var credGr = new GlideRecord(this.credentialTable);
credGr.addQuery(this.userNameField, this.getParameter('user_name'));
credGr.setLimit(1);
credGr.query();
if (credGr.next()) {
var password = this.getParameter('password') || '';
if (credGr[this.passwordField].getDecryptedValue() == password) {
return JSON.stringify({ success: true });
}
return JSON.stringify({
success: false,
message: 'Invalid password'
});
}
return JSON.stringify({
success: false,
message: 'Unknown user'
});
},
isPublic: function () {
return true;
},
type: 'passwordDecryptor'
});
And this is the client script in the UI Page (replace user_name and password with user/pass - just according to your needs):
function validate() {
var ga = new GlideAjax('passwordDecryptor');
ga.addParam('sysparm_name', 'validate');
ga.addParam('user_name', gel('user').value);
ga.addParam('password', gel('pass').value);
ga.getXMLAnswer(function (response) {
response = JSON.parse(response);
if (response.success) {
alert('User name and password are valid!');
} else {
alert(response.message);
}
});
}
Im not a crypto expert, but please consider this Challenge-response-authentication to safely transmit passwords (and this only requires you to store a hash of the password).
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-29-2022 12:32 PM
I really like this overall solution for the problem and will probably implement it. my only real issue is that it does not seem that my script include is being called, and i am unable to verify it. if you have debugging suggestions id be greatly appreciative.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-29-2022 12:59 PM
Your Script Include is not going to be called with the current Client script of your UI Page. You have to completely rebuilt it for the following reasons:
1.) You are using unsupported synchronous GlideAjax:
Official ServiceNow API Documentation: getXMLWait() is not available to scoped applications. (Source)
2.) The GlideAjax call is happening within the while-loop that loops over the GlideRecord results. GlideRecord is not available in scoped applications (your UI Page is in the 'MCP Portal'-Application Scope).
Official ServiceNow API Documentation: The client-side GlideRecord API is not supported in scoped applications. (Source)
General client side debugging tip: When testing things out check the Browser Console (on Chrome you open this by pressing F12). In your case, there should already be errors in the console.
For server side scripting, you have to resort to gs.info when debugging Script Include calls with GlideAjax.
EDIT: Btw, if changed the values of the Client script and the Script Include in my original answer, so you should be able to copy paste this and it should work.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-29-2022 02:49 PM
thank you again for this info. copypasta (plus a little gs.info) does not yield error, but i do have a warning
this missing data.transaction_id is the only clue, all other messages are basically just parsing logs
if this is the problem it is preventing anything from happening aside from a page refresh, googling has not yielded much
could there be an issue with the HTML?
this is the relevant html block, as far as I know.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-29-2022 11:13 PM
1.) The reason why your GlideAjax isn't being executed at all is that you are actually submitting a form (so the Processing script will get executed immediately) and you are redirected (to the UI Page itself). To fix this, either remove type="submit" from the button HTML or remove the <form> tag
<button onclick="validate()" class="btn btn-primary btn-block btn-large">Login</button>
2.) In the Client script, add "debugger;" at the first line of validate:
function validate() {
debugger;
...
Now when you click the Login Button and have the console opened you will be able to debug line by line with this button:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-30-2022 04:27 AM
Actually this reminded me of an old project of mine (GameSpy Login Emulator so we can play some old EA games on LAN-Partys lol) so i've decided to actually implement fully implement this.
This is just a proof-of-concept though and will not pass any serious security review. It only shows the basic steps of the challenge+response building and the processing of the values submitted by the form (please note that we clear the password value before actually transmitting any values to the processing script).
This was tested and is working also in scoped apps (GlideEncrypter is not supported in Scoped Apps).
HTML (external lib for SHA256 used - JavaScript does not have any hashing built in):
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js" integrity="sha512-szJ5FSo9hEmXXe7b5AUVtn/WnL8a5VofnFeYC2i2z03uS2LhAch7ewNLbl5flsEmTTimMN0enBZg/3sQ+YOSzQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<g:evaluate>
var session = gs.getSession();
var errorMessage = session.getClientData('SCOPED_LOGIN_ERROR_MESSAGE');
session.putClientData('SCOPED_LOGIN_ERROR_MESSAGE', '');
var serverChallenge = gs.generateGUID();
session.putClientData('SCOPED_LOGIN_SERVER_CHALLENGE', serverChallenge);
</g:evaluate>
<j:if test="${errorMessage != ''}">
<span style="color: red;">${errorMessage}</span>
</j:if>
<g:ui_form onsubmit="return validateForm(this);">
<!-- note: do not name a input response or request - this will overwrite the global variables in the processing script !-->
<input type="hidden" name="server_challenge" value="${serverChallenge}" />
<input type="hidden" name="client_challenge" />
<input type="hidden" name="login_response" />
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">Login</button>
</g:ui_form>
</j:jelly>
Client script:
function validateForm(form) {
var password = form.elements['password'].value;
if (form.elements['username'].value && password) {
form.elements['password'].value = ''; // we do not want to transmit the password
var clientChallenge = crypto.randomUUID(); // not supported by InternetExplorer!
form.elements['client_challenge'].value = clientChallenge;
form.elements['login_response'].value = sha256(password + clientChallenge + form.elements['server_challenge'].value);
return true;
} else {
alert('Username and Password must not be empty');
return false;
}
}
Processing script:
/* global request, response, GlideRecord, GlideDigest, gs */
/* eslint no-undef: "error" */
(function (request, response) {
// request [GlideServletRequest]
// https://developer.servicenow.com/dev.do#!/reference/api/sandiego/server/no-namespace/c_GlideServletRequestScopedAPI
// response [GlideServletResponse]
// https://developer.servicenow.com/dev.do#!/reference/api/sandiego/server/no-namespace/c_GlideServletResponseScopedAPI
// this
// [represents the values from the <g:ui_form>]
var session = gs.getSession();
var serverChallenge = session.getClientData('SCOPED_LOGIN_SERVER_CHALLENGE');
if (serverChallenge == this.server_challenge) {
var userGr = new GlideRecord('x_376096_scoped_external_app_users');
userGr.addQuery('username', this.username);
userGr.setLimit(1);
userGr.query();
if (userGr.next()) {
var pwdResponse = new GlideDigest()
.getSHA256Hex(userGr.password.getDecryptedValue() + this.client_challenge + session.getClientData('SCOPED_LOGIN_SERVER_CHALLENGE'))
.toLowerCase();
if (pwdResponse == this.login_response) {
response.sendRedirect('/sp');
return;
} else {
session.putClientData('SCOPED_LOGIN_ERROR_MESSAGE', 'Invalid password!');
}
} else {
session.putClientData('SCOPED_LOGIN_ERROR_MESSAGE', 'User not found!');
}
} else {
session.putClientData('SCOPED_LOGIN_ERROR_MESSAGE', 'Invalid server challenge!');
}
response.sendRedirect('/x_376096_scoped_login.do');
}).call(this, request, response);