@molecule/api-resource-user
API resource · auth · API (Node) · v1.4.11 · Apache-2.0
User registration, login, JWT sessions
npm install @molecule/api-resource-userHow it works
@molecule/api-resource-user is an API resource: the routes, validation and storage for auth, built on the database and auth cores so it runs on whichever providers your app has bonded.
// Extend safely: a display field in Props, a secret in SecretProps.
// propsSchema: { …, timezone: z.string().optional() } // safe → client
// secretPropsSchema: { …, passwordResetToken: z.string().optional() } // server-only, secrets table
// A custom handler returns SAFE props — never the secrets row.
router.get('/me/timezone', async (req, res) => {
const userId = getUserId(res)
if (!userId) return res.status(401).json({ error: 'Authentication required.' })
const user = await findById('users', userId) // the users table holds Props only
res.json({ timezone: user?.timezone }) // never spread a secrets-table row here
})Works with: @molecule/api-bond, @molecule/api-config, @molecule/api-database, @molecule/api-i18n, @molecule/api-jwt, @molecule/api-locales-user, @molecule/api-locales-user-payments, @molecule/api-password, @molecule/api-rate-limit, @molecule/api-resource, @molecule/api-resource-device, @molecule/api-secrets
Secrets: JWT_PRIVATE_KEY, JWT_PUBLIC_KEY
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
The user resource types, schema, and definition.
Quick Start
// Extend safely: a display field in Props, a secret in SecretProps.
// propsSchema: { …, timezone: z.string().optional() } // safe → client
// secretPropsSchema: { …, passwordResetToken: z.string().optional() } // server-only, secrets table
// A custom handler returns SAFE props — never the secrets row.
router.get('/me/timezone', async (req, res) => {
const userId = getUserId(res)
if (!userId) return res.status(401).json({ error: 'Authentication required.' })
const user = await findById('users', userId) // the users table holds Props only
res.json({ timezone: user?.timezone }) // never spread a secrets-table row here
})
Type
resource
Installation
npm install @molecule/api-resource-user @molecule/api-bond @molecule/api-config @molecule/api-database @molecule/api-entitlements @molecule/api-i18n @molecule/api-jwt @molecule/api-locales-user @molecule/api-locales-user-payments @molecule/api-password @molecule/api-payments @molecule/api-push-notifications @molecule/api-rate-limit @molecule/api-resource @molecule/api-resource-device @molecule/api-secrets @molecule/api-two-factor uuid zod
API
Interfaces
UserRequestHandlerMap
Shape of the user request-handler map produced by createRequestHandlerMap.
Names match the route definitions in routes.ts. Exported so helpers that
accept the map (e.g. mountDefaultUserAuthRoutes, mountDefaultUserCrudRoutes)
can type their parameter precisely instead of widening to
Record<string, MoleculeRequestHandler>.
interface UserRequestHandlerMap {
auth: MoleculeRequestHandler
authSelf: MoleculeRequestHandler
rateLimitAuth: MoleculeRequestHandler
rateLimitTwoFactor: MoleculeRequestHandler
create: MoleculeRequestHandler
logIn: MoleculeRequestHandler
oauthAuthorize: MoleculeRequestHandler
logInOAuth: MoleculeRequestHandler
logout: MoleculeRequestHandler
read: MoleculeRequestHandler
readSelf: MoleculeRequestHandler
update: MoleculeRequestHandler
del: MoleculeRequestHandler
updatePassword: MoleculeRequestHandler
forgotPassword: MoleculeRequestHandler
resetPassword: MoleculeRequestHandler
verifyTwoFactor: MoleculeRequestHandler
updatePlan: MoleculeRequestHandler
verifyPayment: MoleculeRequestHandler
billingPortal: MoleculeRequestHandler
handlePaymentNotification: MoleculeRequestHandler
requireWebhookAuthenticity: MoleculeRequestHandler
}
Types
CreateOAuthProps
Create O Auth Props type.
type CreateOAuthProps = z.infer<typeof createOAuthPropsSchema>
CreateProps
Create Props type.
type CreateProps = z.infer<typeof createPropsSchema>
CreateSecretProps
Create Secret Props type.
type CreateSecretProps = z.infer<typeof createSecretPropsSchema>
Props
User props type inferred from schema.
type Props = z.infer<typeof propsSchema>
SecretProps
Secret Props type.
type SecretProps = z.infer<typeof secretPropsSchema>
Session
User session data (userId, email, role, permissions, metadata) inferred from sessionSchema.
type Session = z.infer<typeof sessionSchema>
UpdatePasswordSecretProps
Update Password Secret Props type.
type UpdatePasswordSecretProps = z.infer<typeof updatePasswordSecretPropsSchema>
UpdatePlanProps
Update Plan Props type.
type UpdatePlanProps = z.infer<typeof updatePlanPropsSchema>
UpdateProps
Update Props type.
type UpdateProps = z.infer<typeof updatePropsSchema>
VerifyTwoFactorProps
Verify Two Factor Props type.
type VerifyTwoFactorProps = z.infer<typeof verifyTwoFactorPropsSchema>
VerifyTwoFactorSecretProps
Verify Two Factor Secret Props type.
type VerifyTwoFactorSecretProps = z.infer<typeof verifyTwoFactorSecretPropsSchema>
Functions
createRequestHandlerMap(createRequestHandler)
Creates the full request handler map for the User resource. Optional features (OAuth, payments) are conditionally included based on bonded providers.
Handler names match the route definitions in routes.ts.
function createRequestHandlerMap(
createRequestHandler: (
handler: Handler,
) => (req: MoleculeRequest, res: MoleculeResponse, next: MoleculeNextFunction) => Promise<void>,
): UserRequestHandlerMap
createRequestHandler— Factory from@molecule/api-resourcethat wraps handler configs into Express middleware.
Returns: A UserRequestHandlerMap of handler names to Express middleware.
createResource(options)
Creates a user resource definition with optional OAuth servers and plan keys.
function createResource(options?: {
oauthServers?: OAuthServers
planKeys?: PlanKeys
}): types.Resource<unknown>
options— Optional configuration.options.oauthServers— Tuple of allowed OAuth server names (e.g.['google', 'github']). Constrains theoauthServerschema field.options.planKeys— Tuple of allowed plan key strings (e.g.['free', 'pro']). Constrains theplanKeyschema field.
Returns: A Resource with name 'User', table 'users', and a Zod schema reflecting the options.
createSchema(options)
Creates a full schema for user props.
OAuth servers and plan keys can be constrained by passing them as options.
function createSchema(options?: { oauthServers?: OAuthServers; planKeys?: PlanKeys }): z.ZodObject<
{
id: z.ZodString
createdAt: z.ZodString
updatedAt: z.ZodString
username: z.ZodOptional<z.ZodString>
name: z.ZodOptional<z.ZodString>
email: z.ZodOptional<z.ZodNullable<z.ZodString>>
emailVerified: z.ZodOptional<z.ZodBoolean>
avatar: z.ZodOptional<z.ZodNullable<z.ZodString>>
bio: z.ZodOptional<z.ZodNullable<z.ZodString>>
twoFactorEnabled: z.ZodOptional<z.ZodBoolean>
oauthServer:
| z.ZodOptional<z.ZodString>
| z.ZodOptional<
z.ZodEnum<{
[k in keyof { [k in NonNullable<OAuthServers>[number]]: k }]: {
[k in NonNullable<OAuthServers>[number]]: k
}[k]
}>
>
oauthId: z.ZodOptional<z.ZodString>
oauthData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>
planKey:
| z.ZodOptional<z.ZodString>
| z.ZodOptional<
z.ZodEnum<{
[k in keyof { [k in NonNullable<PlanKeys>[number]]: k }]: {
[k in NonNullable<PlanKeys>[number]]: k
}[k]
}>
>
planExpiresAt: z.ZodOptional<z.ZodString>
planAutoRenews: z.ZodOptional<z.ZodBoolean>
},
z.core.$strip
>
options— Optional configuration.options.oauthServers— Tuple of allowed OAuth server names. ConstrainsoauthServerto a Zod enum.options.planKeys— Tuple of allowed plan key strings. ConstrainsplanKeyto a Zod enum.
Returns: A Zod object schema extending basePropsSchema with user-specific fields (username, email, OAuth, plan).
Constants
createOAuthPropsSchema
Schema for creating a user via OAuth.
const createOAuthPropsSchema: z.ZodObject<
{
username: z.ZodOptional<z.ZodString>
name: z.ZodOptional<z.ZodString>
email: z.ZodOptional<z.ZodNullable<z.ZodString>>
emailVerified: z.ZodOptional<z.ZodBoolean>
avatar: z.ZodOptional<z.ZodNullable<z.ZodString>>
bio: z.ZodOptional<z.ZodNullable<z.ZodString>>
oauthServer: z.ZodOptional<z.ZodString> | z.ZodOptional<z.ZodEnum<{}>>
oauthId: z.ZodOptional<z.ZodString>
oauthData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>
},
z.core.$strip
>
createPropsSchema
Schema for creating a user via password.
const createPropsSchema: z.ZodObject<
{
username: z.ZodOptional<z.ZodString>
name: z.ZodOptional<z.ZodString>
email: z.ZodOptional<z.ZodNullable<z.ZodString>>
},
z.core.$strip
>
createSecretPropsSchema
Schema for creating secret props (password hash only).
const createSecretPropsSchema: z.ZodObject<
{ passwordHash: z.ZodOptional<z.ZodString> },
z.core.$strip
>
i18nRegistered
The i18n registered.
const i18nRegistered: true
MAX_AVATAR_LENGTH
Maximum length (in characters) of a user's avatar. Sized to permit a small
inline data-URI (~256KB) without requiring an external upload/storage bond.
Larger avatars must be hosted elsewhere and referenced by URL.
const MAX_AVATAR_LENGTH: number
MAX_BIO_LENGTH
Maximum length (in characters) of a user's bio.
const MAX_BIO_LENGTH: 1000
propsSchema
Default schema for user props.
const propsSchema: z.ZodObject<
{
id: z.ZodString
createdAt: z.ZodString
updatedAt: z.ZodString
username: z.ZodOptional<z.ZodString>
name: z.ZodOptional<z.ZodString>
email: z.ZodOptional<z.ZodNullable<z.ZodString>>
emailVerified: z.ZodOptional<z.ZodBoolean>
avatar: z.ZodOptional<z.ZodNullable<z.ZodString>>
bio: z.ZodOptional<z.ZodNullable<z.ZodString>>
twoFactorEnabled: z.ZodOptional<z.ZodBoolean>
oauthServer: z.ZodOptional<z.ZodString> | z.ZodOptional<z.ZodEnum<{}>>
oauthId: z.ZodOptional<z.ZodString>
oauthData: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>
planKey: z.ZodOptional<z.ZodString> | z.ZodOptional<z.ZodEnum<{}>>
planExpiresAt: z.ZodOptional<z.ZodString>
planAutoRenews: z.ZodOptional<z.ZodBoolean>
},
z.core.$strip
>
resource
Default user resource definition.
const resource: types.Resource<unknown>
resourceUserSecretDefinitions
Secret definitions required by the user resource.
const resourceUserSecretDefinitions: SecretDefinition[]
routes
Route definitions for the User resource. Routes marked optional require additional packages to be installed.
Declarative route definitions used by the injection engine.
const routes: (
| { method: 'post'; path: string; middlewares: string[]; handler: string; optional?: undefined }
| { method: 'get'; path: string; middlewares: string[]; handler: string; optional: string }
| { method: 'post'; path: string; middlewares: string[]; handler: string; optional: string }
| { method: 'get'; path: string; middlewares: string[]; handler: string; optional?: undefined }
| { method: 'patch'; path: string; middlewares: string[]; handler: string; optional?: undefined }
| { method: 'delete'; path: string; middlewares: string[]; handler: string; optional?: undefined }
)[]
secretPropsSchema
Secret properties stored in a separate table.
const secretPropsSchema: z.ZodObject<
{
id: z.ZodString
passwordHash: z.ZodOptional<z.ZodString>
passwordResetToken: z.ZodOptional<z.ZodString>
passwordResetTokenAt: z.ZodOptional<z.ZodString>
pendingTwoFactorSecret: z.ZodOptional<z.ZodString>
twoFactorSecret: z.ZodOptional<z.ZodString>
lastTwoFactorTimeStep: z.ZodOptional<z.ZodNumber>
},
z.core.$strip
>
sessionSchema
Zod schema for JWT session payloads (userId, deviceId, optional OAuth fields).
const sessionSchema: z.ZodObject<
{
id: z.ZodOptional<z.ZodString>
userId: z.ZodString
deviceId: z.ZodString
oauthServer: z.ZodOptional<z.ZodString>
oauthId: z.ZodOptional<z.ZodString>
},
z.core.$strip
>
updatePasswordSecretPropsSchema
Schema for updating password secret props (partial password hash).
const updatePasswordSecretPropsSchema: z.ZodObject<
{ passwordHash: z.ZodOptional<z.ZodOptional<z.ZodString>> },
z.core.$strip
>
updatePlanPropsSchema
Schema for updating a user's plan (partial planKey, planExpiresAt, planAutoRenews).
const updatePlanPropsSchema: z.ZodObject<
{
planKey: z.ZodOptional<z.ZodOptional<z.ZodString> | z.ZodOptional<z.ZodEnum<{}>>>
planExpiresAt: z.ZodOptional<z.ZodOptional<z.ZodString>>
planAutoRenews: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>
},
z.core.$strip
>
updatePropsSchema
Schema for updating a user (partial username, name, email, avatar, bio).
const updatePropsSchema: z.ZodObject<
{
username: z.ZodOptional<z.ZodOptional<z.ZodString>>
name: z.ZodOptional<z.ZodOptional<z.ZodString>>
email: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
avatar: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
bio: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
},
z.core.$strip
>
verifyTwoFactorPropsSchema
Schema for verifying two-factor authentication.
const verifyTwoFactorPropsSchema: z.ZodObject<
{ twoFactorEnabled: z.ZodOptional<z.ZodOptional<z.ZodBoolean>> },
z.core.$strip
>
verifyTwoFactorSecretPropsSchema
Schema for two-factor secret props.
const verifyTwoFactorSecretPropsSchema: z.ZodObject<
{
pendingTwoFactorSecret: z.ZodOptional<z.ZodOptional<z.ZodString>>
twoFactorSecret: z.ZodOptional<z.ZodOptional<z.ZodString>>
},
z.core.$strip
>
Namespaces
authorization
Members:
authorization.getAuthCookieName— function: Resolve the actual cookie name for an auth cookie.authorization.getAuthCookieOptions— function: Base cookie attributes shared by EVERY auth cookie this resource sets andauthorization.invalidateDeviceExistsCache— function: Evict a single device's positive entry from the device-exists cache so theauthorization.invalidateAllDeviceExistsCache— function: Evict ALL positive entries from the device-exists cache.authorization.set— function: Set authorization headers and cookie for a session.authorization.VerifyMiddlewareOptions— interface: Options forverifyMiddleware.authorization.verifyMiddleware— function: Middleware that verifies the JWT token from theAuthorizationheader and setsres.locals.session.
authorizers
Members:
authorizers.auth— function: Middleware that checks if the request has an authenticated session (res.locals.session.userId).authorizers.authSelf— function: Middleware that checks if the authenticated user's ID matches the:idroute parameter.authorizers.RateLimitAuthOptions— interface: Configuration forrateLimit.authorizers.rateLimit— function: Creates an authorizer middleware that brute-force-protects an auth endpoint.authorizers.loginAccountKey— function: Account-identifier extractor for the login endpoint: the submitted usernameauthorizers.emailAccountKey— function: Account-identifier extractor for the forgot-password endpoint: the submittedauthorizers.paramIdAccountKey— function: Account-identifier extractor for the verify-two-factor endpoint: the targetauthorizers.requireWebhookAuthenticity— function: Middleware guarding the publicPOST /users/payment-notification/:provider
handlers
Members:
handlers.billingPortal— function: Opens the bonded provider's hosted billing portal for the user, so they canhandlers.handlePaymentNotification— function: Generic payment notification handler that works with any bonded PaymentProvider. Reads thehandlers.verifyPayment— function: Generic payment verification handler that works with any bonded PaymentProvider. Reads thehandlers.CreateRequest— interface: Request body for user creation, including password and optional device name.handlers.create— function: Creates a user with username and password. Validates username uniqueness and email format,handlers.del— function: Deletes a user and their associated data. Deletes the user row FIRST, thenhandlers.ForgotPasswordRequest— interface: Request body for password reset initiation.handlers.forgotPassword— function: Generates a UUID password reset token, stores it in the secrets table, and sends a resethandlers.LogInRequest— interface: Request body for user login, supporting password, reset token, and 2FA flows.handlers.logIn— function: Logs in a user by username or email. Supports password authentication, password reset tokenhandlers.LogInOAuthRequest— interface: Request body for OAuth login, including the OAuth server name, authorization code, and PKCE verifier.handlers.logInOAuth— function: Logs in or creates a user via OAuth. Verifies the authorization code with the bonded OAuthhandlers.logout— function: Logs the current user out. Revokes the session device server-side (so the JWThandlers.oauthAuthorize— function: OAuth initiation —GET /users/oauth/:provider.handlers.read— function: Reads a user by ID from the database. Attaches plan info (with expiration/renewal status) via thehandlers.readSelf— function: Reads the AUTHENTICATED user from the session — no:idparam. Backshandlers.RESET_TOKEN_TTL_MS— const: How long a password-reset token stays valid, in milliseconds. Shared withhandlers.ResetPasswordRequest— interface: Request body for confirming a password reset using a token.handlers.resetPassword— function: Confirms a password reset by validating the one-time token previously generated byhandlers.update— function: Updates a user's profile fields (username, name, email). Validates username formathandlers.UpdatePasswordRequest— interface: Request body for password update, with current password verification and new password.handlers.updatePassword— function: Updates a user's password. If the user already has a password hash, the current passwordhandlers.UpdatePlanRequest— interface: Request body for plan update, containing the target plan key.handlers.updatePlan— function: Updates a user's subscription plan. Uses the bonded PlanService to look up plan metadata andhandlers.VerifyTwoFactorRequest— interface: Request body for two-factor authentication operations (setup, enable, or disable).handlers.verifyTwoFactor— function: Handles two-factor authentication lifecycle via@molecule/api-two-factor:
types
Members:
types.CreateOAuthProps— type: Create O Auth Props type.types.CreateProps— type: Create Props type.types.CreateSecretProps— type: Create Secret Props type.types.Props— type: User props type inferred from schema.types.SecretProps— type: Secret Props type.types.Session— type: User session data (userId, email, role, permissions, metadata) inferred from sessionSchema.types.UpdatePasswordSecretProps— type: Update Password Secret Props type.types.UpdatePlanProps— type: Update Plan Props type.types.UpdateProps— type: Update Props type.types.VerifyTwoFactorProps— type: Verify Two Factor Props type.types.VerifyTwoFactorSecretProps— type: Verify Two Factor Secret Props type.types.Resource— type: An object describing theuserresource.
utilities
Members:
utilities.fetchAvatarDataUri— function: Downloads an OAuth provider's profile image and re-hosts it as an inlineutilities.getPlan— function: Get a user's current plan info.utilities.invalidateEntitlementsCache— function: Invalidates the entitlements plan-key cache for a user after their planutilities.invalidateEntitlementsCacheSafe— function: Fire-and-forget variant ofinvalidateEntitlementsCachefor callutilities.normalizeEmail— function: Normalizes an email address for storage and lookup so case/whitespaceutilities.notify— function: Sends push notifications to all of a user's devices except the current one. Retrieves devices
z
Members:
z.core— namespacez.infer— typez.output— typez.input— typez.JSONType— typez.globalRegistry— constz.GlobalMeta— interfacez.registry— functionz.config— functionz.$output— constz.$input— constz.$brand— constz.clone— functionz.regexes— namespacez.treeifyError— functionz.prettifyError— functionz.formatError— functionz.flattenError— functionz.TimePrecision— constz.util— namespacez.NEVER— const: A special constant with typeneverz.toJSONSchema— functionz.fromJSONSchema— function: Converts a JSON Schema to a Zod schema. This function should be considered semi-experimental. It's behavior is liable to change.z.locales— namespacez.ZodISODateTime— interfacez.ZodISODate— interfacez.ZodISOTime— interfacez.ZodISODuration— interfacez.iso— namespacez.ZodCoercedString— interfacez.ZodCoercedNumber— interfacez.ZodCoercedBigInt— interfacez.ZodCoercedBoolean— interfacez.ZodCoercedDate— interfacez.coerce— namespacez.string— functionz.email— functionz.guid— functionz.uuid— functionz.uuidv4— functionz.uuidv6— functionz.uuidv7— functionz.url— functionz.httpUrl— functionz.emoji— functionz.nanoid— functionz.cuid— function: Validates a CUID v1 string.z.cuid2— functionz.ulid— functionz.xid— functionz.ksuid— functionz.ipv4— functionz.mac— functionz.ipv6— functionz.cidrv4— functionz.cidrv6— functionz.base64— functionz.base64url— functionz.e164— functionz.jwt— functionz.stringFormat— functionz.hostname— functionz.hex— functionz.hash— functionz.number— functionz.int— functionz.float32— functionz.float64— functionz.int32— functionz.uint32— functionz.boolean— functionz.bigint— functionz.int64— functionz.uint64— functionz.symbol— functionz.any— functionz.unknown— functionz.never— functionz.date— functionz.array— functionz.keyof— functionz.object— functionz.strictObject— functionz.looseObject— functionz.union— functionz.xor— function: Creates an exclusive union (XOR) where exactly one option must match.z.discriminatedUnion— functionz.intersection— functionz.tuple— functionz.record— functionz.partialRecord— functionz.looseRecord— functionz.map— functionz.set— functionz.nativeEnum— functionz.literal— functionz.file— functionz.transform— functionz.optional— functionz.exactOptional— functionz.nullable— functionz.nullish— functionz._default— functionz.prefault— functionz.nonoptional— functionz.success— functionz.nan— functionz.pipe— functionz.codec— functionz.invertCodec— functionz.readonly— functionz.templateLiteral— functionz.lazy— functionz.promise— functionz._function— functionz.check— functionz.custom— functionz.refine— functionz.superRefine— functionz.json— functionz.preprocess— functionz.ZodStandardSchemaWithJSON— typez.ZodType— interfacez._ZodType— interfacez._ZodString— interfacez.ZodString— interfacez.ZodStringFormat— interfacez.ZodEmail— interfacez.ZodGUID— interfacez.ZodUUID— interfacez.ZodURL— interfacez.ZodEmoji— interfacez.ZodNanoID— interfacez.ZodCUID— interfacez.ZodCUID2— interfacez.ZodULID— interfacez.ZodXID— interfacez.ZodKSUID— interfacez.ZodIPv4— interfacez.ZodMAC— interfacez.ZodIPv6— interfacez.ZodCIDRv4— interfacez.ZodCIDRv6— interfacez.ZodBase64— interfacez.ZodBase64URL— interfacez.ZodE164— interfacez.ZodJWT— interfacez.ZodCustomStringFormat— interfacez._ZodNumber— interfacez.ZodNumber— interfacez.ZodNumberFormat— interfacez.ZodInt— interfacez.ZodFloat32— interfacez.ZodFloat64— interfacez.ZodInt32— interfacez.ZodUInt32— interfacez._ZodBoolean— interfacez.ZodBoolean— interfacez._ZodBigInt— interfacez.ZodBigInt— interfacez.ZodBigIntFormat— interfacez.ZodSymbol— interfacez.ZodUndefined— interfacez.undefined— functionz.ZodNull— interfacez.null— functionz.ZodAny— interfacez.ZodUnknown— interfacez.ZodNever— interfacez.ZodVoid— interfacez.void— functionz._ZodDate— interfacez.ZodDate— interfacez.ZodArray— interfacez.SafeExtendShape— typez.ZodObject— interfacez.ZodUnion— interfacez.ZodXor— interfacez.ZodDiscriminatedUnion— interfacez.ZodIntersection— interfacez.ZodTuple— interfacez.ZodRecord— interfacez.ZodMap— interfacez.ZodSet— interfacez.ZodEnum— interfacez.enum— functionz.ZodLiteral— interfacez.ZodFile— interfacez.ZodTransform— interfacez.ZodOptional— interfacez.ZodExactOptional— interfacez.ZodNullable— interfacez.ZodDefault— interfacez.ZodPrefault— interfacez.ZodNonOptional— interfacez.ZodSuccess— interfacez.ZodCatch— interfacez.catch— functionz.ZodNaN— interfacez.ZodPipe— interfacez.ZodCodec— interfacez.ZodPreprocess— interfacez.ZodReadonly— interfacez.ZodTemplateLiteral— interfacez.ZodLazy— interfacez.ZodPromise— interfacez.ZodFunction— interfacez.function— functionz.ZodCustom— interfacez.describe— constz.meta— constz.instanceof— functionz.stringbool— constz.ZodJSONSchemaInternals— interfacez.ZodJSONSchema— interfacez.lt— functionz.lte— functionz.gt— functionz.gte— functionz.positive— functionz.negative— functionz.nonpositive— functionz.nonnegative— functionz.multipleOf— functionz.maxSize— functionz.minSize— functionz.size— functionz.maxLength— functionz.minLength— functionz.length— functionz.regex— functionz.lowercase— functionz.uppercase— functionz.includes— functionz.startsWith— functionz.endsWith— functionz.property— functionz.mime— functionz.overwrite— functionz.normalize— functionz.trim— functionz.toLowerCase— functionz.toUpperCase— functionz.slugify— functionz.RefinementCtx— interfacez.ZodIssue— typez.ZodError— interface: An Error-like class used to store Zod validation issues.z.ZodRealError— constz.ZodFlattenedError— typez.ZodFormattedError— typez.ZodErrorMap— interfacez.IssueData— typez.ZodSafeParseResult— typez.ZodSafeParseSuccess— typez.ZodSafeParseError— typez.parse— constz.parseAsync— constz.safeParse— constz.safeParseAsync— constz.encode— constz.decode— constz.encodeAsync— constz.decodeAsync— constz.safeEncode— constz.safeDecode— constz.safeEncodeAsync— constz.safeDecodeAsync— constz.setErrorMap— functionz.getErrorMap— functionz.TypeOf— typez.Infer— typez.ZodFirstPartySchemaTypes— typez.ZodIssueCode— constz.inferFlattenedErrors— typez.inferFormattedError— typez.BRAND— type: Usez.$brandinsteadz.ZodTypeAny— interfacez.ZodSchema— interfacez.Schema— interfacez.ZodRawShape— type: Included for Zod 3 compatibilityz.ZodFirstPartyTypeKind— enum
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-config^1.0.1@molecule/api-database^1.0.1@molecule/api-entitlements^1.0.1@molecule/api-i18n^1.0.1@molecule/api-jwt^1.0.1@molecule/api-locales-user^1.0.1@molecule/api-locales-user-payments^1.0.1@molecule/api-password^1.0.1@molecule/api-payments^1.1.0@molecule/api-push-notifications^1.0.1@molecule/api-rate-limit^1.0.1@molecule/api-resource^1.0.1@molecule/api-resource-device^1.0.1@molecule/api-secrets^1.0.1@molecule/api-two-factor^1.0.1
Environment Variables
JWT_PRIVATE_KEY(required) — JWT signing key (RSA private)- Auto-generated at scaffold — no manual setup.
JWT_PUBLIC_KEY(required) — JWT verification key (RSA public)- Auto-generated at scaffold — no manual setup.
Runtime Dependencies
@molecule/api-bond@molecule/api-config@molecule/api-database@molecule/api-entitlements@molecule/api-i18n@molecule/api-jwt@molecule/api-locales-user@molecule/api-locales-user-payments@molecule/api-password@molecule/api-payments@molecule/api-push-notifications@molecule/api-rate-limit@molecule/api-resource@molecule/api-resource-device@molecule/api-secrets@molecule/api-two-factoruuidzod
The user record is split across TWO schemas — pick the right one or you leak credentials:
Props(propsSchema) — SAFE, client-facing fields (username, name, email,emailVerified,twoFactorEnabled, plan). This is what handlers return and what lives in theuserstable.SecretProps(secretPropsSchema) — SERVER-ONLY secrets:passwordHash, the TOTPtwoFactorSecret(and its pending-setup value). Stored in a SEPARATE secrets table and NEVER serialized to the client. Note the pairtwoFactorEnabled(safe boolean, inProps) vstwoFactorSecret(secret, inSecretProps).
When you extend the user, put a secret (token, hash, key, provider refresh token) in
SecretProps; put a display field in Props. Never add a secret to Props, never
return a secrets-table value in a response or log, and never res.json(userRow) a raw DB
row — return Props.
Auth is ALREADY wired globally (the router's verifyMiddleware → res.locals.session),
so a handler reads the current user with getUserId(res) and does NOT add per-route auth
middleware (see the auth skill). Scope every custom user query by the authenticated id.
On the CLIENT, the bearer token is held IN MEMORY only — a localStorage copy is
XSS-exfiltratable and is forbidden. The session is restored after a reload via the
httpOnly cookie + GET /users/me; don't persist the token yourself.
Client-facing endpoints (mounted under the app's /api prefix → /api/users/...).
The auth CLIENT (useAuth() → login / register / logout / refresh) already wraps
login / signup / logout — do NOT hand-roll those against the raw routes. The rest have NO
client method; call them with raw http.*. Use these EXACT paths — a weak model guesses
/api/auth/* or /api/user (singular), and neither exists:
POST /api/users/forgot-password— request a reset email (body{ email })POST /api/users/reset-password— confirm with the emailed token (body{ token, password })PATCH /api/users/:id— update profile fields (name, username, email, bio); NOTPUT /api/userPATCH /api/users/:id/password— change password ·DELETE /api/users/:id— delete accountPATCH /api/users/:id/plan— update the subscription plan (a paid plan with no existing subscription answers 201{ checkoutUrl }; send the browser there)POST /api/users/:id/verify-payment/:provider— confirm a purchase server-side (body/querysubscriptionId); the provider returns the buyer to the APP's/plan-updated?provider=…&sessionId=…, and THAT page calls this from the app origin so the session cookie applies. A hosted checkout must never redirect straight to this route on a separate API host: the top-level navigation carries no credentials, so it answers 401 and the paid plan is never granted.POST /api/users/:id/billing-portal/:provider— open the provider's hosted billing portal (update card, invoices, cancel); responds{ url }, optional body{ returnPath }to come back to a specific app page. 404user.payment.noBillingAccountwhen the user has no customer record yetGET /api/users/me— the current user (session restore) ·GET /api/users/:id— read one The full, authoritative route list is theroutesexport (seeroutes.ts).
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- A new user can sign up with email + password and lands authenticated (the UI reflects the signed-in user, e.g. their name/menu appears).
- Any flow that emails a link/code (signup verification, password reset)
round-trips: the sandbox CAPTURES the message instead of sending — read it
with the
read_activitytool (filter type 'email') and follow the link/code in its payload; never mock the flow or modify production code to expose it. - Logging out and logging back in with the same credentials reaches the same account and its data.
- The session survives a full page reload (restored via the httpOnly cookie +
GET /users/me— never from a token persisted in localStorage). - A wrong password shows a visible error and does NOT authenticate.
- Authenticated-only screens are unreachable when logged out (redirect to login or an explicit denial — never a blank page).
- A profile/account edit (e.g. display name) persists across a reload.