Updating Passwords — via HTTP and in Rules
Updated pdexter - 2026-08-18
(curated from document by Jordan Diamante 2026-07-16)
Overview
Passwords cannot be changed by saving a document through the document update API.
Any attempt to include a password change ({ newPass, confirmPass } in a Password
component field) in a call to:
PUT /api/document/:id(full document update), orPUT /api/document/deepDiffUpdate/:id(deep-diff / JSON patch update)
is rejected by the server with the error:
Direct updating of passwords is not allowed
This also applies to any server-side code that saves documents through DataService.update
/ DataService.jsonPatchUpdate — including updateDocument called from a ruleset — because
they all run the same update pipeline.
Password changes on insert (e.g. account creation) are allowed, and updates that
don't attempt a password change (including sending back the "*" placeholder value)
work as normal.
The supported way to update a password is to call PasswordPreprocessor.preProcess
directly. The preprocessor validates the new password against the configured
passwordCreationRules, hashes it, and (for account documents) stores the hash in the
related account security document. It is exposed to both sandboxes below through the
vm2 vendor root (vm2.root in the server config, server/vendor by default), so it can
be required with a relative path:
var PasswordPreprocessor = require('./PasswordPreprocessor');
preProcess(field, document) takes:
field— the Password component definition, e.g.{ type: 'Password', componentName: 'sc-password-entry', name: 'password' }document— the document whosefield.nameproperty is set to{ newPass: '<new password>', confirmPass: '<new password>' }
and returns a promise. Validation failures (passwords not matching, missing confirmation, password rule breaches) reject with the corresponding error message.
For account documents (systemHeader.systemType === "account"), the hash is written to
the related account security document and the password field on the account document
itself remains the "*" placeholder. For non-account documents, the hashed value is set
on the document field — save the returned document afterwards if it needs to be
persisted.
Method 1 — HTTP call via a component web service
Step 1 — Create the component web service
Create (or reuse) a component document with a restfulWebServiceFunctions field. The
field holds a JavaScript object literal of named functions. Each function receives
(functionParameters, req, context) and must return a promise.
The sandbox the function runs in provides DataService, userId (the calling user's
documentId), logger, config, SharedConstants and q.
Example restfulWebServiceFunctions value for a component named
passwordUpdateService that lets the logged-in user change their own password:
{
updatePassword: function (functionParameters) {
var PasswordPreprocessor = require('./PasswordPreprocessor');
var field = {
type: 'Password',
componentName: 'sc-password-entry',
name: 'password'
};
return DataService.findOne(userId, userId).then(function (account) {
account.password = {
newPass: functionParameters.newPass,
confirmPass: functionParameters.confirmPass
};
// Calling the preprocessor directly (outside the document save pipeline)
// validates, hashes and stores the password in the account security document
return PasswordPreprocessor.preProcess(field, account, userId);
}).then(function () {
return { result: 'Password updated' };
});
}
}
To allow changing another user's password, accept an account documentId in
functionParameters and fetch that account instead of userId. Because the
preprocessor writes the password without further access checks, only do this if the
component is locked down to administrators (see security note below).
Security note: restrict who can execute the component by setting the appropriate
systemHeader keys on the component document. Anyone permitted to execute the
component can change the password targeted by the function.
Step 2 — Call the web service over HTTP
The function is executed with:
POST /api/execute/:componentName/:functionName
The request must be authenticated — either with an existing session cookie or by
passing an apiKey header for an account that has an API key configured.
Example using curl with an API key:
curl -X POST 'https://<host>/api/execute/passwordUpdateService/updatePassword' \
-H 'Content-Type: application/json' \
-H 'apiKey: <api-key>' \
-d '{
"functionParameters": {
"newPass": "NewPassw0rd!238",
"confirmPass": "NewPassw0rd!238"
}
}'
The component and function names can alternatively be passed in the request body to the
base route POST /api/execute:
{
"componentDocumentName": "passwordUpdateService",
"functionName": "updatePassword",
"functionParameters": {
"newPass": "NewPassw0rd!238",
"confirmPass": "NewPassw0rd!238"
}
}
A successful call returns the object resolved by the function (e.g.
{ "result": "Password updated" }). Validation failures are returned as an error
response.
Method 2 — In a server-side ruleset
Server rulesets (e.g. PreSaveServer, PostSave on the server, or system action
rulesets) run in the same vm2 sandbox, so they can require and call
PasswordPreprocessor the same way.
Do not try to change a password from a rule with ft3.updateDocument or
DataService.update — those go through the update pipeline and are rejected with
Direct updating of passwords is not allowed.
Example PreSaveServer ruleset where the saved document carries the requested password
change in a { newPass, confirmPass } value, and the rule applies it to the target
account:
Non Jayrule Ruleset
{
mainRule: async function (ntf, callbackContinue, callbackSuccess, callbackError) {
var ft3 = ntf.scope;
var doc = ntf.document;
// only act when a password change has been requested on the document
if (!doc.requestedPassword || !doc.requestedPassword.newPass) {
callbackSuccess();
return;
}
var PasswordPreprocessor = require('./PasswordPreprocessor');
var field = {
type: 'Password',
componentName: 'sc-password-entry',
name: 'password'
};
try {
// the target account: here the document's related account, but it could
// equally be ntf.user.documentId to change the current user's password
var targetAccountId = doc.accountRel[0].documentId;
var results = await new Promise(function (resolve, reject) {
ft3.findDocuments({ documentId: targetAccountId }, ntf.user.documentId,
function (err, found) {
return err ? reject(err) : resolve(found);
});
});
// findDocuments returns raw search results; processSearchResults
// extracts the plain documents
var account = await new Promise(function (resolve) {
ft3.processSearchResults(results, function (doc) {}, function (docs) {
resolve(docs[0]);
});
});
if (!account) {
throw new Error('Account not found: ' + targetAccountId);
}
account.password = {
newPass: doc.requestedPassword.newPass,
confirmPass: doc.requestedPassword.confirmPass
};
await PasswordPreprocessor.preProcess(field, account, ntf.user.documentId);
// remove the plain text password from the document before it is saved
delete doc.requestedPassword;
callbackSuccess();
} catch (err) {
callbackError(err);
}
}
}
JayRule Ruleset
{
#include "Jayrule Ruleset Overlay JS",
ruleset : {
...
ruleChangePassword: async function (ntf) {
var ft3 = ntf.scope;
var doc = ntf.document;
// only act when a password change has been requested on the document
if (!doc.requestedPassword || !doc.requestedPassword.newPass) {
return;
}
var PasswordPreprocessor = require('./PasswordPreprocessor');
var field = {
type: 'Password',
componentName: 'sc-password-entry',
name: 'password'
};
try {
// the target account: here the document's related account, but it could
// equally be ntf.user.documentId to change the current user's password
var targetAccountId = doc.accountRel[0].documentId;
var results = await new Promise(function (resolve, reject) {
ft3.findDocuments({ documentId: targetAccountId }, ntf.user.documentId,
function (err, found) {
return err ? reject(err) : resolve(found);
});
});
// findDocuments returns raw search results; processSearchResults
// extracts the plain documents
var account = await new Promise(function (resolve) {
ft3.processSearchResults(results, function (doc) {}, function (docs) {
resolve(docs[0]);
});
});
if (!account) {
throw new Error('Account not found: ' + targetAccountId);
}
account.password = {
newPass: doc.requestedPassword.newPass,
confirmPass: doc.requestedPassword.confirmPass
};
await PasswordPreprocessor.preProcess(field, account, ntf.user.documentId);
// remove the plain text password from the document before it is saved
delete doc.requestedPassword;
}
catch (e) {
ntf.errorMessage = e.message;
}
}
}
}
Notes:
- In a
PreSaveServerrule, changes made tontf.document(such as deleting the plain text password field above) are saved with the document — no extra update call is needed for the triggering document itself. - The same
require('./PasswordPreprocessor')call works in JayRule-overlay rulesets (#include "JayRule Ruleset Overlay JS") inside anyruleAction. - Attach the ruleset to a template that only authorised users can save, since anyone who can trigger the rule can change the password it targets.
Validation behaviour (both methods)
PasswordPreprocessor.preProcess rejects with:
Both the new password and a confirmation of the password must be provided to set the password— when only one ofnewPass/confirmPassis supplied.Passwords do not match— when they differ.- The password rule breach messages — when the new password fails the
passwordCreationRulesconfigured on the server.