nextjs-cms

passwordField

A masked input whose value is hashed with bcrypt before it is stored.

import { passwordField } from 'nextjs-cms/core/fields'

Basic usage

passwordField({ name: 'password' })

The value is never stored in plain text: it's hashed with bcrypt (cost 10) on save.

With every option

passwordField({
    name: 'password',
    label: 'Portal password',
    required: true,
    order: 5,

    placeholder: 'At least 12 characters',
    minLength: 12,
    maxLength: 72,
    validation: {
        regex: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/,
        message: 'Use upper- and lowercase letters and at least one number.',
    },
})

Options

OptionTypeDefaultDescription
placeholderstringnoneHint in the empty input.
minLengthnumbernoneMinimum length of the plain-text password.
maxLengthnumbernoneMaximum length of the plain-text password.
validation{ regex: RegExp; message: string }nonePattern the password must match. message is shown when it doesn't.

Plus the common options. defaultValue and localized make no sense for passwords.

Options in detail

validation

Checked on the plain-text value, before hashing. The error reads <label>: <message>.

// At least one digit and one symbol
validation: {
    regex: /^(?=.*\d)(?=.*[^A-Za-z0-9]).+$/,
    message: 'Include at least one number and one symbol.',
}

maxLength and bcrypt

bcrypt only uses the first 72 bytes of a password. Setting maxLength: 72 avoids passwords whose tail is silently ignored.

Use case: credentials for your website's users

A common use is a section of accounts that log in to your website (a members area, a partner portal):

sections/partners.section.ts
const email = textField({ name: 'email', required: true })

export default hasItemsSection({
    name: 'partners',
    order: 8,
    title: { section: 'Partners', singular: 'Partner', plural: 'Partners' },
    headingField: email,
    db: { table: 'partners', unique: [{ columns: [email] }] },
    fields: [email, passwordField({ name: 'password', required: true, minLength: 12 })],
})
website/lib/auth.ts
import bcrypt from 'bcrypt'
import { eq } from 'drizzle-orm'

export async function verifyPartner(email: string, password: string) {
    const [partner] = await db.select().from(PartnersTable).where(eq(PartnersTable.email, email)).limit(1)
    if (!partner) return null
    return (await bcrypt.compare(password, partner.password)) ? partner : null
}
Good to know:

Admin accounts (the people who log in to the CMS) are not a section. They're managed on the built-in Admins page. See Admins & Permissions.

Storage

ColumnVARCHAR(255) CHARACTER SET utf8mb4
Valuebcrypt hash, like $2b$10$N9qo8uLOickgx2ZMRZoMye...