# apostrophe-users

# Inherits from: apostrophe-pieces

# apos.users

The apostrophe-users module provides user accounts. It is not intended to be extended with new subclass modules. The apostrophe-login module only looks for instances of apostrophe-user. Of course you may implicitly subclass it at project level (not changing the name) in order to alter its behavior.

A user's permissions are determined by their membership in groups. See the join with apostrophe-group in the schema.

Groups are managed by the apostrophe-groups module.

There is also a simplified permissions model in which you just specify an array of groups as an option to apostrophe-users, and a single-select dropdown menu allows you to pick one and only one of those groups for each user. The recommended properties of each group in the array are title, slug, and permissions, which is an array of permission names such as guest, edit and admin. If you specify the groups option when configuring apostrophe-users, the admin interface for apostrophe-groups will hide itself. Specifying slug is optional, however if your site has many documents there will be a startup time penalty when specifying only title due to the lack of indexing on that property. For most larger sites we recommend not using the groups option at all; just manage groups via the admin bar.

# Public "staff directories" vs. users

In our experience, combining the concept of a "user" who can log in and do things with the concept of a "staff member" who appears in a staff directory is more trouble than it is worth. That's why the published field is not present in apostrophe-users. You can add it back in, but then you have to deal with the confusing concept of "users" who shouldn't actually be allowed to log in.

So for a staff directory, we suggest you create a separate employee module or similar, extending apostrophe-pieces, unless it's true that basically everyone should be allowed to log in.

# secrets option

For security the password property is not stored as plaintext and is not kept in the aposDocs collection. Instead, it is hashed and salted using the credential module and the resulting hash is stored in a separate aposUsersSafe collection.

Additional secrets may be hashed in this way. If you set the secrets option to an array of property names, those properties are never stored directly to the database. Instead, only their hashes are stored, and only in aposUsersSafe.

You may also call apos.users.addSecret('name') to add a new secret property. This is convenient when implementing a module such as apostrophe-signup.

# disableInactiveAccounts option

If set to true, users are disabled after 90 days of inactivity. Users from the "admin" group are ignored. Default values can be changed:

  • neverDisabledGroups must be an array of group names that should NOT be disabled due to not having logged in recently, like [ 'admin' ].
  • inactivityDuration must be an integer number of days. Users who have not logged in in more than inactivityDuration days will not be permitted to log in again until an admin clears the disabled flag via "Manage Users."

# Methods

# addOurTrashPrefixFields()

Add username and email to the list of fields that automatically get uniquely prefixed when a user is in the trash, so that they can be reused by another piece. When the piece is rescued from the trash the prefix is removed again, unless the username or email address has been claimed by another user in the meanwhile.

# enableSecrets()

See options.secrets and also the addSecret method. enableSecrets is part of the implementation and should not be called directly.

# ensureSafe(callback)

Index and obtain access to the aposUsersSafe MongoDB collection as self.safe.

# ensureSafeCollection(callback)

Obtain the aposUsersSafe MongoDB collection as self.safe.

# ensureSafeIndexes(callback)

Index the safe.

# afterConvert(req, piece, callback)

After a user is updated, check to see if the groups option is configured for simplified user management. If it is, convert the single-select choice made via piece.group to an array stored in groupIds, so that all other code can find groups in a consistent way.

# docBeforeInsert(req, doc, options, callback)

For security, on ANY insert of a doc, we check to see if it is an apostrophe-user and, if so, hash the password, remove it from the doc and store the hash in the safe instead.

This method also checks password rules if they are in force and local logins are enabled.

# docBeforeUpdate(req, doc, options, callback)

For security, on ANY update of a doc, we check to see if it is an apostrophe-user and, if so, hash the password, remove it from the doc and store the hash in the safe instead.

# insertOrUpdateSafe(req, doc, action, callback)

Insert or update a user's record in the safe, which stores the password hash completely outside of the aposDocs collection. First checks to be sure this is an apostrophe-user and invokes its callback immediately if not. Invoked by docBeforeInsert and docBeforeUpdate.

# hashPassword(doc, safeUser, callback)

Hash the password property of doc, then delete that property and update the passwordHash property of safeUser. This method is called by the docBeforeInsert and docBeforeSave handlers of this module. Ifpasswordis falsy (i.e. the user left it blank, requesting no change), it is left alone andsafeUser` is not updated.

# hashSecrets(doc, safeUser, callback)

Similar to hashPassword, this method hashes all of the properties enumerated in options.secrets and via addSecrets, then deletes them and updates the corresponding properties of safeUser. If a secret is named signup, the corresponding property in safeUser will be named signupHash.

This method is called by the docBeforeInsert and docBeforeSave handlers of this module.

# addSecret(name)

Add the property specified by name to a list of secret properties. These are never stored directly to the user's doc in mongodb. Instead, if any of them have non-falsy values at the time a user is saved, those values are hashed and the hash is recorded in a separate mongodb collection used only for this purpose. You may then call verifySecret later to verify that a newly entered value matches the previously hashed value. This is useful to verify password reset codes, signup verification codes and the like with security just as good as that used for the password.

# hashSecret(doc, safeUser, secret, callback)

Hashes a secret property of doc, deletes the property, and stores only the hash in safeUser. secret is the name of the property of doc, not the secret itself.

If secret is the string 'password', then the password property will be deleted from doc and the passwordHash property of safeUser will be set.

If the secret property is falsy (i.e. the user left the password field blank, requesting no change), it is left alone and safeUser is not updated.

Called automatically by hashSecrets, above.

# verifySecret(user, secret, attempt, callback)

Check whether the provided value attempt matches the hash of the secret property secret. For security the user's password and other property names specified in options.secrets when configuring this module or via addSecrets are not stored as plaintext and are not kept in the aposDocs collection. Instead, they are hashed and salted using the credential module and the resulting hash is stored in a separate aposUsersSafe collection. This method can be used to verify that attempt matches the previously hashed value for the property named secret, without ever storing the actual value of the secret.

If no callback is passed, a promise is returned. If the verification fails the promise is rejected.

# forgetSecret(user, secret, callback)

Forget the secret associated with the property name passed in secret. If secret is 'passwordReset', then the passwordResetHash property is deleted from the appropriate record in the aposUsersSafe collection. Note that the plaintext of the secret was never stored in the database in the first place.

If no callback is passed, a promise is returned.

# deduplicateTrash(req, piece, callback)

Reflect email and username changes in the safe after deduplicating in the piece

# deduplicateRescue(req, piece, callback)

Reflect email and username changes in the safe after deduplicating in the piece

# ensureGroups(callback)

Ensure the existence of the groups configured via the groups option, if any, and refresh their permissions.

# ensureGroup(group, callback)

Create and/or refresh a group as specified by the groups option. The group is the second argument to the callback.

# requirePiece(req, res, next)

Extend the standard middleware for the piece-editing routes so that the group single-select property is automatically set to the id of the first group in groupIds. This allows the single-select dropdown element to work with data that actually lives in an array.

# initializeCredential()

Initialize the credential (opens new window) module.

# addFromTask(callback)

Implement the apostrophe-users:add command line task.

# changePasswordFromTask(callback)

Implement the apostrophe-users:change-password task.

# addNonNullJoinMigration()