Skip to content

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), or
  • PUT /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 whose field.name property 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 PreSaveServer rule, changes made to ntf.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 any ruleAction.
  • 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 of newPass / confirmPass is supplied.
  • Passwords do not match — when they differ.
  • The password rule breach messages — when the new password fails the passwordCreationRules configured on the server.



End of Document