Database and Workflow Security Guide
The purpose of this guide is to show you how to lock down your records and tighten security. One issue that I have experienced is that users who bookmarked a page they shouldn't be able to go back to, or pressed the Back Button in their browser when they shouldn't have, or who know the URL to a Form and the Record ID of a record they want to modify... can manipulate data. Knack, by default, only has one level of security which is restricting page access with Login roles and that's not enough. This guide provides the following solutions:
- How to prevent specific, or all, actions based on status. (Ex: If "Status" is "Closed" then prevent all edit actions)
- How to assign a record to user(s) and prevent all other users from editing it.
- How to create fully customized security based on literally any criteria.
This guide starts by showing some basic tactics (non-Javascript solutions) we can use from the Builder's user interface and then goes further beyond into our more robust Javascript solution which allows us to lock records based on any criteria.
Basic Workflow Security in Builder (Using Page Rule - Redirect):
Page Rules are great for securing pages based on the status of a record. For this example, I have a workflow which moves records down a pipeline of Phases and Statuses (think of Phase like where we are in a process and Status as the action being taken). The Phases go from "Unassigned" all the way to "Closed". When a record is in the "Unassigned" Phase then a Manager will be presented with an action to assign that record to a Employee. To assign a record, I have a menu button (or action link) appear which redirects the user to another page and is restricted to the Manager user role.
The only time a record should be assigned is when it is in the "Unassigned" status. So, on that page, here's an example Page Rule I could use to prevent Managers from accessing this form when the status is not "Unassigned":
![](upload://wbSKoJ46MbgLI9sAfjb0dHtsGRi.png)
That's the most basic example using only one criteria. When the Status is not Unassigned then the Manager will be redirected to the parent page and prevented from taking action. Often times we will have multiple page rules set up to prevent action. Create an individual page rule per your criteria. For example, I might add something like "when Assigned To is not blank" if I have a seperate form for the Reassign Task process.
Javascript Security (Restricting Records Based on Logged In User and Beyond)
Now let's consider something more complex. We have already assigned a record to an Employee. Let's say my database keeps track of who the record is "Assigned To" and who it was "Assigned By". I only want the "Assigned To" user and the "Assigned By" user to be able to perform a certain action such as: changing the "Due Date" for the record. However we cannot enforce that using Page Rules because Page Rules don't allow us to check if a data-field is the current logged-in-user. We must now resort to Javascript (for more basic checks, use the Page Rule solution above).
Builder Setup for Javascript Solution
To make our code work, you need to create a "Details" view that contains the data you need to work with. So, for my example, I have a "View Details" view for my assigned tasks and this "Details" view would go inside of that page. On that Details view, add all the data fields you want to provide custom validation for.
Javascript Code
Note: In this code, you will need to know the "field_xxx" number for the fields you want to validatie (in this example I use "Assigned To" and "Status"). To find: go to your Builder. Select "Data". Go to your "Object" and click the "Settings" icon next to the Field. Check the URL for your Field ID.
Copy/Paste this code into your Javascript. And fill in the correct view numbers / data fields.
// Hidden View for Ajax Request is Removed from DOM before Rendering
$(document).on('knack-view-render.view_2740', function(event, view, record) {
$("#" + view.key).remove();
}):
// Ajax GET requests gets the data we need to validate our custom security
// Success function passes data to custom_Validation
// Error function tries to get the data again otherwise kicks user back to previous page
function myAjaxTest(recordID, formView, boolFormSubmitted) {
$.ajax({
url: "https://api.knack.com/v1/pages/scene_895/views/view_2740/records/" + recordID,
type: "GET",
headers: {
"X-Knack-Application-Id": Knack.application_id,
"X-Knack-REST-API-Key": `knack`,
"Authorization": Knack.getUserToken()
},
tryCount: 0,
retryLimit: 3,
async: false,
success: function(data) {
custom_Validation(data, recordID, formView, boolFormSubmitted);
},
error : function(XMLHttpRequest, textStatus, errorThrown ) {
this.tryCount++;
let tryCount = this.tryCount, retryLimit = this.retryLimit, seconds;
if (tryCount <= retryLimit) { //try again
switch(tryCount) {
case 1:
case 2: seconds = 5; break;
case 3: seconds = 10; break; }
let timeout = seconds * 1000;
console.log("Error: " + XMLHttpRequest.status + " " + XMLHttpRequest.statusText + "\nRetry Count: " + tryCount + "\nRetrying in " + seconds + " seconds")
let ajaxObject = this;
window.setTimeout(function(){
$.ajax(ajaxObject);
}, timeout);
} else {
const indention = "\n";
alert("Server Error:" + indention + "Refreshing page due to load error." + indention + "Notify your site administrator if issue persists.");
window.location.reload();
}
}
});
}
// This is an example custom validation function
// You can get whatever data you want and run whatever tests you want as well
function custom_Validation(data, recordID, formView, boolFormSubmitted) {
// In this example, I check for two things:
// 1: Is the logged in user who it is assigned to?
// 2: Is the record NOT closed?
// If either are false then the user is redirected to another page
const logged_In_User = Knack.getUserAttributes().name,
// find the data below in your builder
assignedTo = data.field_205_raw,
status = data.field_192;
// This is just an example function if validation fails
function failed_Validation(custom_Text, redirect_URL) {
window.alert("Action Prohibited:\n" + custom_Text);
window.location.replace(redirect_URL); // This is a URL to redirect to. Can be window.history.back()
}
let Employee;
if (assignedTo && assignedTo.length > 0) Employee = assignedTo[0].identifier; // user data fields are objects and this is the user's name
// Here is the actual test, if it fails then the failed_Validation routine fires
if (status == "Closed" || logged_In_User !== Employee) {
failed_Validation("This request is either assigned to another user or closed.", "https://yoursite.knack.com/site#error/")
}
// This checks if a form was being submitted during this routine, if so then the submission continues
if (boolFormSubmitted == false) {
return;
} else {
const $form = $('#' + formView);
$form.find('button[type=submit]').submit();
}
}
// A Form You Want Custom Valdiation On
$(document).on('knack-view-render.view_800', function(event, view, record) {
Knack.showSpinner();
const recordID = record.id,
formView = view.key;
let boolFormSubmitted = false;
// This is an example that runs the validation tests when view renders
myAjaxTest(recordID, formView, boolFormSubmitted);
// This is an example that runs the validation tests when form is submitted
$("#" + formView + " .kn-button").on("click", function(event) {
event.preventDefault();
boolFormSubmitted = true;
myAjaxTest(recordID, formView, boolFormSubmitted);
});
});
Expanding This Code to Meet Your Needs
This code snippet is just a foundation. If you want to perform checks on any other aspect of your data then you can do that. The ".remove()" function is also versatile in that you can delete views from the HTML on page-load that you don't want specific users to see or have access to.