From cfe22b3e90d678934cdc822b9fd2c6b8499d39a1 Mon Sep 17 00:00:00 2001
From: Lucas Coratger <73360179+coratgerl@users.noreply.github.com>
Date: Sat, 6 Dec 2025 20:44:49 +0100
Subject: [PATCH 01/10] feat: add event information on `verifyUserEmails`
---
spec/EmailVerificationToken.spec.js | 86 +++++++++++++++++++++++-
spec/ValidationAndPasswordsReset.spec.js | 1 +
src/Options/Definitions.js | 3 +-
src/Options/docs.js | 2 +-
src/Options/index.js | 21 +++++-
src/RestWrite.js | 34 ++++++++--
src/Routers/UsersRouter.js | 9 +++
types/Options/index.d.ts | 16 ++++-
8 files changed, 158 insertions(+), 14 deletions(-)
diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js
index 1a235d8d13..15c317a94c 100644
--- a/spec/EmailVerificationToken.spec.js
+++ b/spec/EmailVerificationToken.spec.js
@@ -298,7 +298,15 @@ describe('Email Verification Token Expiration:', () => {
};
const verifyUserEmails = {
method(req) {
- expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']);
+ expect(Object.keys(req)).toEqual([
+ 'original',
+ 'object',
+ 'master',
+ 'ip',
+ 'installationId',
+ 'createdWith',
+ ]);
+ expect(req.createdWith).toEqual({ action: 'signup', authProvider: 'password' });
return false;
},
};
@@ -359,7 +367,15 @@ describe('Email Verification Token Expiration:', () => {
};
const verifyUserEmails = {
method(req) {
- expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']);
+ expect(Object.keys(req)).toEqual([
+ 'original',
+ 'object',
+ 'master',
+ 'ip',
+ 'installationId',
+ 'createdWith',
+ ]);
+ expect(req.createdWith).toEqual({ action: 'signup', authProvider: 'password' });
if (req.object.get('username') === 'no_email') {
return false;
}
@@ -394,6 +410,71 @@ describe('Email Verification Token Expiration:', () => {
expect(verifySpy).toHaveBeenCalledTimes(5);
});
+ it('provides createdWith on signup when verification blocks session creation', async () => {
+ const verifyUserEmails = {
+ method: params => {
+ expect(params.object).toBeInstanceOf(Parse.User);
+ expect(params.createdWith).toEqual({ action: 'signup', authProvider: 'password' });
+ return true;
+ },
+ };
+ const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough();
+ await reconfigureServer({
+ appName: 'emailVerifyToken',
+ verifyUserEmails: verifyUserEmails.method,
+ preventLoginWithUnverifiedEmail: true,
+ preventSignupWithUnverifiedEmail: true,
+ emailAdapter: MockEmailAdapterWithOptions({
+ fromAddress: 'parse@example.com',
+ apiKey: 'k',
+ domain: 'd',
+ }),
+ publicServerURL: 'http://localhost:8378/1',
+ });
+
+ const user = new Parse.User();
+ user.setUsername('signup_created_with');
+ user.setPassword('pass');
+ user.setEmail('signup@example.com');
+ const res = await user.signUp().catch(e => e);
+ expect(res.message).toBe('User email is not verified.');
+ expect(user.getSessionToken()).toBeUndefined();
+ expect(verifySpy).toHaveBeenCalledTimes(2); // before signup completion and on preventLoginWithUnverifiedEmail
+ });
+
+ it('provides createdWith with auth provider on login verification', async () => {
+ const user = new Parse.User();
+ user.setUsername('user_created_with_login');
+ user.setPassword('pass');
+ user.set('email', 'login@example.com');
+ await user.signUp();
+
+ const verifyUserEmails = {
+ method: async params => {
+ expect(params.object).toBeInstanceOf(Parse.User);
+ expect(params.createdWith).toEqual({ action: 'login', authProvider: 'password' });
+ return true;
+ },
+ };
+ const verifyUserEmailsSpy = spyOn(verifyUserEmails, 'method').and.callThrough();
+ await reconfigureServer({
+ appName: 'emailVerifyToken',
+ publicServerURL: 'http://localhost:8378/1',
+ verifyUserEmails: verifyUserEmails.method,
+ preventLoginWithUnverifiedEmail: verifyUserEmails.method,
+ preventSignupWithUnverifiedEmail: true,
+ emailAdapter: MockEmailAdapterWithOptions({
+ fromAddress: 'parse@example.com',
+ apiKey: 'k',
+ domain: 'd',
+ }),
+ });
+
+ const res = await Parse.User.logIn('user_created_with_login', 'pass').catch(e => e);
+ expect(res.code).toBe(205);
+ expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(2); // before login completion and on preventLoginWithUnverifiedEmail
+ });
+
it_id('d812de87-33d1-495e-a6e8-3485f6dc3589')(it)('can conditionally send user email verification', async () => {
const emailAdapter = {
sendVerificationEmail: () => {},
@@ -797,6 +878,7 @@ describe('Email Verification Token Expiration:', () => {
expect(params.master).toBeDefined();
expect(params.installationId).toBeDefined();
expect(params.resendRequest).toBeTrue();
+ expect(params.createdWith).toBeUndefined();
return true;
},
};
diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js
index 3f6d4048c5..f5bbe2d48d 100644
--- a/spec/ValidationAndPasswordsReset.spec.js
+++ b/spec/ValidationAndPasswordsReset.spec.js
@@ -284,6 +284,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
expect(params.ip).toBeDefined();
expect(params.master).toBeDefined();
expect(params.installationId).toBeDefined();
+ expect(params.createdWith).toEqual({ action: 'login', authProvider: 'password' });
return true;
},
};
diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js
index 66c1d8bcea..4a1264dee4 100644
--- a/src/Options/Definitions.js
+++ b/src/Options/Definitions.js
@@ -481,7 +481,6 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL',
help:
'Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
Default is `false`.
Requires option `verifyUserEmails: true`.',
- action: parsers.booleanParser,
default: false,
},
preventSignupWithUnverifiedEmail: {
@@ -637,7 +636,7 @@ module.exports.ParseServerOptions = {
verifyUserEmails: {
env: 'PARSE_SERVER_VERIFY_USER_EMAILS',
help:
- 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.
Default is `false`.',
+ 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.',
default: false,
},
webhookKey: {
diff --git a/src/Options/docs.js b/src/Options/docs.js
index 9569239ef7..a56cdf2d8f 100644
--- a/src/Options/docs.js
+++ b/src/Options/docs.js
@@ -109,7 +109,7 @@
* @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields
* @property {Boolean} verbose Set the logging to verbose
* @property {Boolean} verifyServerUrl Parse Server makes a HTTP request to the URL set in `serverURL` at the end of its launch routine to verify that the launch succeeded. If this option is set to `false`, the verification will be skipped. This can be useful in environments where the server URL is not accessible from the server itself, such as when running behind a firewall or in certain containerized environments.
⚠️ Server URL verification requires Parse Server to be able to call itself by making requests to the URL set in `serverURL`.
Default is `true`.
- * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.
Default is `false`.
+ * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
* @property {String} webhookKey Key sent with outgoing webhook calls
*/
diff --git a/src/Options/index.js b/src/Options/index.js
index cdeb7cd846..0dedafb3ab 100644
--- a/src/Options/index.js
+++ b/src/Options/index.js
@@ -43,6 +43,18 @@ type RequestKeywordDenylist = {
key: string | any,
value: any,
};
+type EmailVerificationRequest = {
+ original?: any,
+ object: any,
+ master?: boolean,
+ ip?: string,
+ installationId?: string,
+ createdWith?: {
+ action: 'login' | 'signup',
+ authProvider: string,
+ },
+ resendRequest?: boolean,
+};
export interface ParseServerOptions {
/* Your Parse Application ID
@@ -174,18 +186,21 @@ export interface ParseServerOptions {
/* Max file size for uploads, defaults to 20mb
:DEFAULT: 20mb */
maxUploadSize: ?string;
- /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.
+ /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
:DEFAULT: false */
- verifyUserEmails: ?(boolean | void);
+ verifyUserEmails: ?(boolean | (EmailVerificationRequest => boolean | Promise));
/* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
Default is `false`.
Requires option `verifyUserEmails: true`.
:DEFAULT: false */
- preventLoginWithUnverifiedEmail: ?boolean;
+ preventLoginWithUnverifiedEmail: ?(
+ | boolean
+ | (EmailVerificationRequest => boolean | Promise)
+ );
/* If set to `true` it prevents a user from signing up if the email has not yet been verified and email verification is required. In that case the server responds to the sign-up with HTTP status 400 and a Parse Error 205 `EMAIL_NOT_FOUND`. If set to `false` the server responds with HTTP status 200, and client SDKs return an unauthenticated Parse User without session token. In that case subsequent requests fail until the user's email address is verified.
Default is `false`.
diff --git a/src/RestWrite.js b/src/RestWrite.js
index a0de5577a5..9e220fb741 100644
--- a/src/RestWrite.js
+++ b/src/RestWrite.js
@@ -771,6 +771,28 @@ RestWrite.prototype._validateUserName = function () {
});
};
+RestWrite.prototype.getCreatedWith = function () {
+ if (this.storage.createdWith) {
+ return this.storage.createdWith;
+ }
+ const isCreateOperation = !this.query;
+ // Determine authProvider: from stored authProvider or authData keys (e.g., anonymous, facebook).
+ // Default to 'password' on signup with no authData so createdWith aligns with legacy expectations/tests.
+ const authProvider =
+ this.storage.authProvider ||
+ (this.data &&
+ this.data.authData &&
+ Object.keys(this.data.authData).length &&
+ Object.keys(this.data.authData).join(','));
+ const action = authProvider ? 'login' : isCreateOperation ? 'signup' : undefined;
+ if (!action) {
+ return;
+ }
+ const resolvedAuthProvider = authProvider || (action === 'signup' ? 'password' : undefined);
+ this.storage.createdWith = { action, authProvider: resolvedAuthProvider };
+ return this.storage.createdWith;
+};
+
/*
As with usernames, Parse should not allow case insensitive collisions of email.
unlike with usernames (which can have case insensitive collisions in the case of
@@ -826,6 +848,7 @@ RestWrite.prototype._validateEmail = function () {
master: this.auth.isMaster,
ip: this.config.ip,
installationId: this.auth.installationId,
+ createdWith: this.getCreatedWith(),
};
return this.config.userController.setEmailVerifyToken(this.data, request, this.storage);
}
@@ -961,6 +984,7 @@ RestWrite.prototype.createSessionTokenIfNeeded = async function () {
master: this.auth.isMaster,
ip: this.config.ip,
installationId: this.auth.installationId,
+ createdWith: this.getCreatedWith(),
};
// Get verification conditions which can be booleans or functions; the purpose of this async/await
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
@@ -987,12 +1011,14 @@ RestWrite.prototype.createSessionToken = async function () {
this.storage.authProvider = Object.keys(this.data.authData).join(',');
}
- const { sessionData, createSession } = RestWrite.createSession(this.config, {
- userId: this.objectId(),
- createdWith: {
+ const createdWith =
+ this.getCreatedWith() || {
action: this.storage.authProvider ? 'login' : 'signup',
authProvider: this.storage.authProvider || 'password',
- },
+ };
+ const { sessionData, createSession } = RestWrite.createSession(this.config, {
+ userId: this.objectId(),
+ createdWith,
installationId: this.auth.installationId,
});
diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js
index 3828e465e7..d725ef7704 100644
--- a/src/Routers/UsersRouter.js
+++ b/src/Routers/UsersRouter.js
@@ -140,11 +140,20 @@ export class UsersRouter extends ClassesRouter {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
}
// Create request object for verification functions
+ const authProvider =
+ req.body &&
+ req.body.authData &&
+ Object.keys(req.body.authData).length &&
+ Object.keys(req.body.authData).join(',');
const request = {
master: req.auth.isMaster,
ip: req.config.ip,
installationId: req.auth.installationId,
object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
+ createdWith: {
+ action: 'login',
+ authProvider: authProvider || 'password',
+ },
};
// If request doesn't use master or maintenance key with ignoring email verification
diff --git a/types/Options/index.d.ts b/types/Options/index.d.ts
index ad11050648..c4c62bd7e5 100644
--- a/types/Options/index.d.ts
+++ b/types/Options/index.d.ts
@@ -26,6 +26,18 @@ type RequestKeywordDenylist = {
key: string;
value: any;
};
+export interface VerifyUserEmailsRequest {
+ original?: any;
+ object: any;
+ master?: boolean;
+ ip?: string;
+ installationId?: string;
+ createdWith?: {
+ action: 'login' | 'signup';
+ authProvider: string;
+ };
+ resendRequest?: boolean;
+}
export interface ParseServerOptions {
appId: string;
masterKey: (() => void) | string;
@@ -74,8 +86,8 @@ export interface ParseServerOptions {
auth?: Record;
enableInsecureAuthAdapters?: boolean;
maxUploadSize?: string;
- verifyUserEmails?: (boolean | void);
- preventLoginWithUnverifiedEmail?: boolean;
+ verifyUserEmails?: boolean | ((params: VerifyUserEmailsRequest) => boolean | Promise);
+ preventLoginWithUnverifiedEmail?: boolean | ((params: VerifyUserEmailsRequest) => boolean | Promise);
preventSignupWithUnverifiedEmail?: boolean;
emailVerifyTokenValidityDuration?: number;
emailVerifyTokenReuseIfValid?: boolean;
From 9f2ab065ca0f6299c046bb014c8bbcee379e9f4b Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 02:22:47 +0000
Subject: [PATCH 02/10] env var parsing for boolean or function options
---
src/Options/Definitions.js | 2 ++
src/Options/parsers.js | 8 ++++++++
2 files changed, 10 insertions(+)
diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js
index db2d434805..ed67744e5e 100644
--- a/src/Options/Definitions.js
+++ b/src/Options/Definitions.js
@@ -474,6 +474,7 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL',
help:
'Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
Default is `false`.
Requires option `verifyUserEmails: true`.',
+ action: parsers.booleanOrFunctionParser,
default: false,
},
preventSignupWithUnverifiedEmail: {
@@ -630,6 +631,7 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_VERIFY_USER_EMAILS',
help:
'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.',
+ action: parsers.booleanOrFunctionParser,
default: false,
},
webhookKey: {
diff --git a/src/Options/parsers.js b/src/Options/parsers.js
index 3fdad89dc3..d2f0b7b6fb 100644
--- a/src/Options/parsers.js
+++ b/src/Options/parsers.js
@@ -68,6 +68,13 @@ function booleanParser(opt) {
return false;
}
+function booleanOrFunctionParser(opt) {
+ if (typeof opt === 'function') {
+ return opt;
+ }
+ return booleanParser(opt);
+}
+
function nullParser(opt) {
if (opt == 'null') {
return null;
@@ -81,6 +88,7 @@ module.exports = {
numberOrStringParser,
nullParser,
booleanParser,
+ booleanOrFunctionParser,
moduleOrObjectParser,
arrayParser,
objectParser,
From 93d482fc3d5fe715bdd4211c83fd2cabcacd3d5d Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 02:28:06 +0000
Subject: [PATCH 03/10] definitions builder
---
resources/buildConfigDefinitions.js | 5 +++++
src/Options/Definitions.js | 5 +++--
src/Options/docs.js | 4 ++--
src/Options/index.js | 4 ++--
4 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js
index 8979e2658b..5c32bc4693 100644
--- a/resources/buildConfigDefinitions.js
+++ b/resources/buildConfigDefinitions.js
@@ -158,6 +158,11 @@ function mapperFor(elt, t) {
return wrap(t.identifier('booleanParser'));
} else if (t.isObjectTypeAnnotation(elt)) {
return wrap(t.identifier('objectParser'));
+ } else if (t.isUnionTypeAnnotation(elt)) {
+ const unionTypes = elt.typeAnnotation?.types || elt.types;
+ if (unionTypes?.some(type => t.isBooleanTypeAnnotation(type))) {
+ return wrap(t.identifier('booleanOrFunctionParser'));
+ }
} else if (t.isGenericTypeAnnotation(elt)) {
const type = elt.typeAnnotation.id.name;
if (type == 'Adapter') {
diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js
index ed67744e5e..11bddc1a33 100644
--- a/src/Options/Definitions.js
+++ b/src/Options/Definitions.js
@@ -473,7 +473,7 @@ module.exports.ParseServerOptions = {
preventLoginWithUnverifiedEmail: {
env: 'PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL',
help:
- 'Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
Default is `false`.
Requires option `verifyUserEmails: true`.',
+ 'Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
Requires option `verifyUserEmails: true`.',
action: parsers.booleanOrFunctionParser,
default: false,
},
@@ -574,6 +574,7 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_SEND_USER_EMAIL_VERIFICATION',
help:
'Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending.
Default is `true`.
',
+ action: parsers.booleanOrFunctionParser,
default: true,
},
serverCloseComplete: {
@@ -630,7 +631,7 @@ module.exports.ParseServerOptions = {
verifyUserEmails: {
env: 'PARSE_SERVER_VERIFY_USER_EMAILS',
help:
- 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.',
+ 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider. The `createdWith` property is `undefined` for resend verification email requests; use the `resendRequest` property to identify those.
Default is `false`.',
action: parsers.booleanOrFunctionParser,
default: false,
},
diff --git a/src/Options/docs.js b/src/Options/docs.js
index c47c914ee2..6c92b42088 100644
--- a/src/Options/docs.js
+++ b/src/Options/docs.js
@@ -84,7 +84,7 @@
* @property {String} playgroundPath Mount path for the GraphQL Playground, defaults to /playground
* @property {Number} port The port to run the ParseServer, defaults to 1337.
* @property {Boolean} preserveFileName Enable (or disable) the addition of a unique hash to the file names
- * @property {Boolean} preventLoginWithUnverifiedEmail Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
Default is `false`.
Requires option `verifyUserEmails: true`.
+ * @property {Boolean} preventLoginWithUnverifiedEmail Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
Requires option `verifyUserEmails: true`.
* @property {Boolean} preventSignupWithUnverifiedEmail If set to `true` it prevents a user from signing up if the email has not yet been verified and email verification is required. In that case the server responds to the sign-up with HTTP status 400 and a Parse Error 205 `EMAIL_NOT_FOUND`. If set to `false` the server responds with HTTP status 200, and client SDKs return an unauthenticated Parse User without session token. In that case subsequent requests fail until the user's email address is verified.
Default is `false`.
Requires option `verifyUserEmails: true`.
* @property {ProtectedFields} protectedFields Protected fields that should be treated with extra security when fetching details.
* @property {Union} publicServerURL Optional. The public URL to Parse Server. This URL will be used to reach Parse Server publicly for features like password reset and email verification links. The option can be set to a string or a function that can be asynchronously resolved. The returned URL string must start with `http://` or `https://`.
@@ -108,7 +108,7 @@
* @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields
* @property {Boolean} verbose Set the logging to verbose
* @property {Boolean} verifyServerUrl Parse Server makes a HTTP request to the URL set in `serverURL` at the end of its launch routine to verify that the launch succeeded. If this option is set to `false`, the verification will be skipped. This can be useful in environments where the server URL is not accessible from the server itself, such as when running behind a firewall or in certain containerized environments.
⚠️ Server URL verification requires Parse Server to be able to call itself by making requests to the URL set in `serverURL`.
Default is `true`.
- * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
+ * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider. The `createdWith` property is `undefined` for resend verification email requests; use the `resendRequest` property to identify those.
Default is `false`.
* @property {String} webhookKey Key sent with outgoing webhook calls
*/
diff --git a/src/Options/index.js b/src/Options/index.js
index d64413ffd8..ab1c49e6dd 100644
--- a/src/Options/index.js
+++ b/src/Options/index.js
@@ -186,12 +186,12 @@ export interface ParseServerOptions {
/* Max file size for uploads, defaults to 20mb
:DEFAULT: 20mb */
maxUploadSize: ?string;
- /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
+ /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider. The `createdWith` property is `undefined` for resend verification email requests; use the `resendRequest` property to identify those.
Default is `false`.
:DEFAULT: false */
verifyUserEmails: ?(boolean | (EmailVerificationRequest => boolean | Promise));
- /* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
+ /* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
From 11e8e398bc9619f052a7e495b9659892efdce668 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 02:38:00 +0000
Subject: [PATCH 04/10] centralize getCreatedWith
---
src/RestWrite.js | 30 +++++++++++++++---------------
src/Routers/UsersRouter.js | 15 +++------------
2 files changed, 18 insertions(+), 27 deletions(-)
diff --git a/src/RestWrite.js b/src/RestWrite.js
index 9e220fb741..6630a81e85 100644
--- a/src/RestWrite.js
+++ b/src/RestWrite.js
@@ -771,25 +771,27 @@ RestWrite.prototype._validateUserName = function () {
});
};
+RestWrite.buildCreatedWith = function (action, authProvider) {
+ return { action, authProvider: authProvider || 'password' };
+};
+
RestWrite.prototype.getCreatedWith = function () {
if (this.storage.createdWith) {
return this.storage.createdWith;
}
const isCreateOperation = !this.query;
- // Determine authProvider: from stored authProvider or authData keys (e.g., anonymous, facebook).
- // Default to 'password' on signup with no authData so createdWith aligns with legacy expectations/tests.
- const authProvider =
- this.storage.authProvider ||
- (this.data &&
- this.data.authData &&
- Object.keys(this.data.authData).length &&
- Object.keys(this.data.authData).join(','));
- const action = authProvider ? 'login' : isCreateOperation ? 'signup' : undefined;
+ const authDataProvider =
+ this.data?.authData &&
+ Object.keys(this.data.authData).length &&
+ Object.keys(this.data.authData).join(',');
+ const authProvider = this.storage.authProvider || authDataProvider;
+ // storage.authProvider is only set for login (existing user found in handleAuthData)
+ const action = this.storage.authProvider ? 'login' : isCreateOperation ? 'signup' : undefined;
if (!action) {
return;
}
const resolvedAuthProvider = authProvider || (action === 'signup' ? 'password' : undefined);
- this.storage.createdWith = { action, authProvider: resolvedAuthProvider };
+ this.storage.createdWith = RestWrite.buildCreatedWith(action, resolvedAuthProvider);
return this.storage.createdWith;
};
@@ -1009,13 +1011,11 @@ RestWrite.prototype.createSessionToken = async function () {
if (this.storage.authProvider == null && this.data.authData) {
this.storage.authProvider = Object.keys(this.data.authData).join(',');
+ // Invalidate cached createdWith since authProvider was just resolved
+ delete this.storage.createdWith;
}
- const createdWith =
- this.getCreatedWith() || {
- action: this.storage.authProvider ? 'login' : 'signup',
- authProvider: this.storage.authProvider || 'password',
- };
+ const createdWith = this.getCreatedWith();
const { sessionData, createSession } = RestWrite.createSession(this.config, {
userId: this.objectId(),
createdWith,
diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js
index d725ef7704..6421d9abe1 100644
--- a/src/Routers/UsersRouter.js
+++ b/src/Routers/UsersRouter.js
@@ -150,10 +150,7 @@ export class UsersRouter extends ClassesRouter {
ip: req.config.ip,
installationId: req.auth.installationId,
object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
- createdWith: {
- action: 'login',
- authProvider: authProvider || 'password',
- },
+ createdWith: RestWrite.buildCreatedWith('login', authProvider),
};
// If request doesn't use master or maintenance key with ignoring email verification
@@ -299,10 +296,7 @@ export class UsersRouter extends ClassesRouter {
const { sessionData, createSession } = RestWrite.createSession(req.config, {
userId: user.objectId,
- createdWith: {
- action: 'login',
- authProvider: 'password',
- },
+ createdWith: RestWrite.buildCreatedWith('login'),
installationId: req.info.installationId,
});
@@ -369,10 +363,7 @@ export class UsersRouter extends ClassesRouter {
const { sessionData, createSession } = RestWrite.createSession(req.config, {
userId,
- createdWith: {
- action: 'login',
- authProvider: 'masterkey',
- },
+ createdWith: RestWrite.buildCreatedWith('login', 'masterkey'),
installationId: req.info.installationId,
});
From cdeb7d7c189afdb5fd6aeb1627b3e729ca989308 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 02:41:38 +0000
Subject: [PATCH 05/10] parser tests
---
spec/buildConfigDefinitions.spec.js | 50 +++++++++++++++++++++++++++++
spec/parsers.spec.js | 18 +++++++++++
2 files changed, 68 insertions(+)
diff --git a/spec/buildConfigDefinitions.spec.js b/spec/buildConfigDefinitions.spec.js
index f0a8055860..44c2fd716c 100644
--- a/spec/buildConfigDefinitions.spec.js
+++ b/spec/buildConfigDefinitions.spec.js
@@ -133,6 +133,56 @@ describe('buildConfigDefinitions', () => {
expect(result.property.name).toBe('arrayParser');
});
+ it('should return booleanOrFunctionParser for UnionTypeAnnotation containing boolean (nullable)', () => {
+ const mockElement = {
+ type: 'UnionTypeAnnotation',
+ typeAnnotation: {
+ types: [
+ { type: 'BooleanTypeAnnotation' },
+ { type: 'FunctionTypeAnnotation' },
+ ],
+ },
+ };
+
+ const result = mapperFor(mockElement, t);
+
+ expect(t.isMemberExpression(result)).toBe(true);
+ expect(result.object.name).toBe('parsers');
+ expect(result.property.name).toBe('booleanOrFunctionParser');
+ });
+
+ it('should return booleanOrFunctionParser for UnionTypeAnnotation containing boolean (non-nullable)', () => {
+ const mockElement = {
+ type: 'UnionTypeAnnotation',
+ types: [
+ { type: 'BooleanTypeAnnotation' },
+ { type: 'FunctionTypeAnnotation' },
+ ],
+ };
+
+ const result = mapperFor(mockElement, t);
+
+ expect(t.isMemberExpression(result)).toBe(true);
+ expect(result.object.name).toBe('parsers');
+ expect(result.property.name).toBe('booleanOrFunctionParser');
+ });
+
+ it('should return undefined for UnionTypeAnnotation without boolean', () => {
+ const mockElement = {
+ type: 'UnionTypeAnnotation',
+ typeAnnotation: {
+ types: [
+ { type: 'StringTypeAnnotation' },
+ { type: 'NumberTypeAnnotation' },
+ ],
+ },
+ };
+
+ const result = mapperFor(mockElement, t);
+
+ expect(result).toBeUndefined();
+ });
+
it('should return objectParser for unknown GenericTypeAnnotation', () => {
const mockElement = {
type: 'GenericTypeAnnotation',
diff --git a/spec/parsers.spec.js b/spec/parsers.spec.js
index 413bdb5156..a844016ba7 100644
--- a/spec/parsers.spec.js
+++ b/spec/parsers.spec.js
@@ -3,6 +3,7 @@ const {
numberOrBoolParser,
numberOrStringParser,
booleanParser,
+ booleanOrFunctionParser,
objectParser,
arrayParser,
moduleOrObjectParser,
@@ -48,6 +49,23 @@ describe('parsers', () => {
expect(parser(2)).toEqual(false);
});
+ it('parses correctly with booleanOrFunctionParser', () => {
+ const parser = booleanOrFunctionParser;
+ // Preserves functions
+ const fn = () => true;
+ expect(parser(fn)).toBe(fn);
+ const asyncFn = async () => false;
+ expect(parser(asyncFn)).toBe(asyncFn);
+ // Parses booleans and string booleans like booleanParser
+ expect(parser(true)).toEqual(true);
+ expect(parser(false)).toEqual(false);
+ expect(parser('true')).toEqual(true);
+ expect(parser('false')).toEqual(false);
+ expect(parser('1')).toEqual(true);
+ expect(parser(1)).toEqual(true);
+ expect(parser(0)).toEqual(false);
+ });
+
it('parses correctly with objectParser', () => {
const parser = objectParser;
expect(parser({ hello: 'world' })).toEqual({ hello: 'world' });
From 83daaeea345f4324c7b6f2346d3aae8bbbf591e5 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 02:44:15 +0000
Subject: [PATCH 06/10] tests
---
spec/EmailVerificationToken.spec.js | 73 +++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js
index 2b818cf52d..82114760af 100644
--- a/spec/EmailVerificationToken.spec.js
+++ b/spec/EmailVerificationToken.spec.js
@@ -465,6 +465,79 @@ describe('Email Verification Token Expiration:', () => {
expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(2); // before login completion and on preventLoginWithUnverifiedEmail
});
+ it('provides createdWith with auth provider on signup verification', async () => {
+ const createdWithValues = [];
+ const verifyUserEmails = {
+ method: params => {
+ createdWithValues.push(params.createdWith);
+ return true;
+ },
+ };
+ const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough();
+ await reconfigureServer({
+ appName: 'emailVerifyToken',
+ verifyUserEmails: verifyUserEmails.method,
+ preventLoginWithUnverifiedEmail: true,
+ preventSignupWithUnverifiedEmail: true,
+ emailAdapter: MockEmailAdapterWithOptions({
+ fromAddress: 'parse@example.com',
+ apiKey: 'k',
+ domain: 'd',
+ }),
+ publicServerURL: 'http://localhost:8378/1',
+ });
+
+ const provider = {
+ authData: { id: '8675309', access_token: 'jenny' },
+ shouldError: false,
+ authenticate(options) {
+ options.success(this, this.authData);
+ },
+ restoreAuthentication() {
+ return true;
+ },
+ getAuthType() {
+ return 'facebook';
+ },
+ deauthenticate() {},
+ };
+ Parse.User._registerAuthenticationProvider(provider);
+ const res = await Parse.User._logInWith('facebook').catch(e => e);
+ expect(res.message).toBe('User email is not verified.');
+ // Called once in createSessionTokenIfNeeded (no email set, so _validateEmail skips)
+ expect(verifySpy).toHaveBeenCalledTimes(1);
+ expect(createdWithValues[0]).toEqual({ action: 'signup', authProvider: 'facebook' });
+ });
+
+ it('provides createdWith for preventLoginWithUnverifiedEmail function', async () => {
+ const user = new Parse.User();
+ user.setUsername('user_prevent_login_fn');
+ user.setPassword('pass');
+ user.set('email', 'preventlogin@example.com');
+ await user.signUp();
+
+ const preventLoginCreatedWith = [];
+ await reconfigureServer({
+ appName: 'emailVerifyToken',
+ publicServerURL: 'http://localhost:8378/1',
+ verifyUserEmails: true,
+ preventLoginWithUnverifiedEmail: params => {
+ preventLoginCreatedWith.push(params.createdWith);
+ return true;
+ },
+ emailAdapter: MockEmailAdapterWithOptions({
+ fromAddress: 'parse@example.com',
+ apiKey: 'k',
+ domain: 'd',
+ }),
+ });
+
+ const res = await Parse.User.logIn('user_prevent_login_fn', 'pass').catch(e => e);
+ expect(res.code).toBe(205);
+ expect(preventLoginCreatedWith.length).toBe(1);
+ expect(preventLoginCreatedWith[0]).toEqual({ action: 'login', authProvider: 'password' });
+ });
+
it_id('d812de87-33d1-495e-a6e8-3485f6dc3589')(it)('can conditionally send user email verification', async () => {
const emailAdapter = {
sendVerificationEmail: () => {},
From d16fe23ccbb1aac664dc85a88734041899109f00 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 03:07:03 +0000
Subject: [PATCH 07/10] docs
---
src/Options/Definitions.js | 4 ++--
src/Options/docs.js | 4 ++--
src/Options/index.js | 6 +++++-
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js
index 11bddc1a33..86104e5e15 100644
--- a/src/Options/Definitions.js
+++ b/src/Options/Definitions.js
@@ -473,7 +473,7 @@ module.exports.ParseServerOptions = {
preventLoginWithUnverifiedEmail: {
env: 'PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL',
help:
- 'Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
Requires option `verifyUserEmails: true`.',
+ "Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
The `createdWith` values per scenario:- Password signup: `{ action: 'signup', authProvider: 'password' }`
- Auth provider signup: `{ action: 'signup', authProvider: '' }`
- Password login: `{ action: 'login', authProvider: 'password' }`
- Auth provider login: function not invoked; auth provider login bypasses email verification
Default is `false`.
Requires option `verifyUserEmails: true`.",
action: parsers.booleanOrFunctionParser,
default: false,
},
@@ -631,7 +631,7 @@ module.exports.ParseServerOptions = {
verifyUserEmails: {
env: 'PARSE_SERVER_VERIFY_USER_EMAILS',
help:
- 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider. The `createdWith` property is `undefined` for resend verification email requests; use the `resendRequest` property to identify those.
Default is `false`.',
+ "Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
The `createdWith` values per scenario:- Password signup: `{ action: 'signup', authProvider: 'password' }`
- Auth provider signup: `{ action: 'signup', authProvider: '' }`
- Password login: `{ action: 'login', authProvider: 'password' }`
- Auth provider login: function not invoked; auth provider login bypasses email verification
- Resend verification email: `createdWith` is `undefined`; use the `resendRequest` property to identify those
Default is `false`.",
action: parsers.booleanOrFunctionParser,
default: false,
},
diff --git a/src/Options/docs.js b/src/Options/docs.js
index 6c92b42088..0240d14b16 100644
--- a/src/Options/docs.js
+++ b/src/Options/docs.js
@@ -84,7 +84,7 @@
* @property {String} playgroundPath Mount path for the GraphQL Playground, defaults to /playground
* @property {Number} port The port to run the ParseServer, defaults to 1337.
* @property {Boolean} preserveFileName Enable (or disable) the addition of a unique hash to the file names
- * @property {Boolean} preventLoginWithUnverifiedEmail Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
Default is `false`.
Requires option `verifyUserEmails: true`.
+ * @property {Boolean} preventLoginWithUnverifiedEmail Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
The `createdWith` values per scenario:- Password signup: `{ action: 'signup', authProvider: 'password' }`
- Auth provider signup: `{ action: 'signup', authProvider: '' }`
- Password login: `{ action: 'login', authProvider: 'password' }`
- Auth provider login: function not invoked; auth provider login bypasses email verification
Default is `false`.
Requires option `verifyUserEmails: true`.
* @property {Boolean} preventSignupWithUnverifiedEmail If set to `true` it prevents a user from signing up if the email has not yet been verified and email verification is required. In that case the server responds to the sign-up with HTTP status 400 and a Parse Error 205 `EMAIL_NOT_FOUND`. If set to `false` the server responds with HTTP status 200, and client SDKs return an unauthenticated Parse User without session token. In that case subsequent requests fail until the user's email address is verified.
Default is `false`.
Requires option `verifyUserEmails: true`.
* @property {ProtectedFields} protectedFields Protected fields that should be treated with extra security when fetching details.
* @property {Union} publicServerURL Optional. The public URL to Parse Server. This URL will be used to reach Parse Server publicly for features like password reset and email verification links. The option can be set to a string or a function that can be asynchronously resolved. The returned URL string must start with `http://` or `https://`.
@@ -108,7 +108,7 @@
* @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields
* @property {Boolean} verbose Set the logging to verbose
* @property {Boolean} verifyServerUrl Parse Server makes a HTTP request to the URL set in `serverURL` at the end of its launch routine to verify that the launch succeeded. If this option is set to `false`, the verification will be skipped. This can be useful in environments where the server URL is not accessible from the server itself, such as when running behind a firewall or in certain containerized environments.
⚠️ Server URL verification requires Parse Server to be able to call itself by making requests to the URL set in `serverURL`.
Default is `true`.
- * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider. The `createdWith` property is `undefined` for resend verification email requests; use the `resendRequest` property to identify those.
Default is `false`.
+ * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
The `createdWith` values per scenario:- Password signup: `{ action: 'signup', authProvider: 'password' }`
- Auth provider signup: `{ action: 'signup', authProvider: '' }`
- Password login: `{ action: 'login', authProvider: 'password' }`
- Auth provider login: function not invoked; auth provider login bypasses email verification
- Resend verification email: `createdWith` is `undefined`; use the `resendRequest` property to identify those
Default is `false`.
* @property {String} webhookKey Key sent with outgoing webhook calls
*/
diff --git a/src/Options/index.js b/src/Options/index.js
index ab1c49e6dd..97144294fa 100644
--- a/src/Options/index.js
+++ b/src/Options/index.js
@@ -186,13 +186,17 @@ export interface ParseServerOptions {
/* Max file size for uploads, defaults to 20mb
:DEFAULT: 20mb */
maxUploadSize: ?string;
- /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider. The `createdWith` property is `undefined` for resend verification email requests; use the `resendRequest` property to identify those.
+ /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
+ The `createdWith` values per scenario:
+ - Password signup: `{ action: 'signup', authProvider: 'password' }`
- Auth provider signup: `{ action: 'signup', authProvider: '' }`
- Password login: `{ action: 'login', authProvider: 'password' }`
- Auth provider login: function not invoked; auth provider login bypasses email verification
- Resend verification email: `createdWith` is `undefined`; use the `resendRequest` property to identify those
Default is `false`.
:DEFAULT: false */
verifyUserEmails: ?(boolean | (EmailVerificationRequest => boolean | Promise));
/* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. Supports a function with a return value of `true` or `false` for conditional prevention. The function receives a request object that includes `createdWith` to indicate whether the invocation is for `signup` or `login` and the used auth provider.
+ The `createdWith` values per scenario:
+ - Password signup: `{ action: 'signup', authProvider: 'password' }`
- Auth provider signup: `{ action: 'signup', authProvider: '' }`
- Password login: `{ action: 'login', authProvider: 'password' }`
- Auth provider login: function not invoked; auth provider login bypasses email verification
Default is `false`.
Requires option `verifyUserEmails: true`.
From c7f05f0314e12ce0dd5a84f4a013db002588a211 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 03:24:40 +0000
Subject: [PATCH 08/10] flow type
---
src/Options/index.js | 9 ++++++++-
types/Options/index.d.ts | 6 +++++-
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/src/Options/index.js b/src/Options/index.js
index 97144294fa..e190d376da 100644
--- a/src/Options/index.js
+++ b/src/Options/index.js
@@ -55,6 +55,10 @@ type EmailVerificationRequest = {
},
resendRequest?: boolean,
};
+type SendEmailVerificationRequest = {
+ user: any,
+ master?: boolean,
+};
export interface ParseServerOptions {
/* Your Parse Application ID
@@ -233,7 +237,10 @@ export interface ParseServerOptions {
Default is `true`.
:DEFAULT: true */
- sendUserEmailVerification: ?(boolean | void);
+ sendUserEmailVerification: ?(
+ | boolean
+ | (SendEmailVerificationRequest => boolean | Promise)
+ );
/* The account lockout policy for failed login attempts. */
accountLockout: ?AccountLockoutOptions;
/* The password policy for enforcing password related rules. */
diff --git a/types/Options/index.d.ts b/types/Options/index.d.ts
index c5d9b2c107..c03fabe8ae 100644
--- a/types/Options/index.d.ts
+++ b/types/Options/index.d.ts
@@ -38,6 +38,10 @@ export interface VerifyUserEmailsRequest {
};
resendRequest?: boolean;
}
+export interface SendEmailVerificationRequest {
+ user: any;
+ master?: boolean;
+}
export interface ParseServerOptions {
appId: string;
masterKey: (() => void) | string;
@@ -91,7 +95,7 @@ export interface ParseServerOptions {
preventSignupWithUnverifiedEmail?: boolean;
emailVerifyTokenValidityDuration?: number;
emailVerifyTokenReuseIfValid?: boolean;
- sendUserEmailVerification?: (boolean | void);
+ sendUserEmailVerification?: boolean | ((params: SendEmailVerificationRequest) => boolean | Promise);
accountLockout?: AccountLockoutOptions;
passwordPolicy?: PasswordPolicyOptions;
cacheAdapter?: Adapter;
From 91a66c863c51588be996ceac481377bc1aa7da29 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 03:25:33 +0000
Subject: [PATCH 09/10] tighten parser union type
---
resources/buildConfigDefinitions.js | 2 +-
spec/buildConfigDefinitions.spec.js | 16 ++++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js
index 5c32bc4693..ae7246fd9b 100644
--- a/resources/buildConfigDefinitions.js
+++ b/resources/buildConfigDefinitions.js
@@ -160,7 +160,7 @@ function mapperFor(elt, t) {
return wrap(t.identifier('objectParser'));
} else if (t.isUnionTypeAnnotation(elt)) {
const unionTypes = elt.typeAnnotation?.types || elt.types;
- if (unionTypes?.some(type => t.isBooleanTypeAnnotation(type))) {
+ if (unionTypes?.some(type => t.isBooleanTypeAnnotation(type)) && unionTypes?.some(type => t.isFunctionTypeAnnotation(type))) {
return wrap(t.identifier('booleanOrFunctionParser'));
}
} else if (t.isGenericTypeAnnotation(elt)) {
diff --git a/spec/buildConfigDefinitions.spec.js b/spec/buildConfigDefinitions.spec.js
index 44c2fd716c..bc15793a04 100644
--- a/spec/buildConfigDefinitions.spec.js
+++ b/spec/buildConfigDefinitions.spec.js
@@ -183,6 +183,22 @@ describe('buildConfigDefinitions', () => {
expect(result).toBeUndefined();
});
+ it('should return undefined for UnionTypeAnnotation with boolean but without function', () => {
+ const mockElement = {
+ type: 'UnionTypeAnnotation',
+ typeAnnotation: {
+ types: [
+ { type: 'BooleanTypeAnnotation' },
+ { type: 'VoidTypeAnnotation' },
+ ],
+ },
+ };
+
+ const result = mapperFor(mockElement, t);
+
+ expect(result).toBeUndefined();
+ });
+
it('should return objectParser for unknown GenericTypeAnnotation', () => {
const mockElement = {
type: 'GenericTypeAnnotation',
From c03761193cfdccebe203e7626b710d60a0d493dd Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 6 Feb 2026 03:38:12 +0000
Subject: [PATCH 10/10] inconsistent type naming convention
---
types/Options/index.d.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/types/Options/index.d.ts b/types/Options/index.d.ts
index c03fabe8ae..e0c1bbc1ec 100644
--- a/types/Options/index.d.ts
+++ b/types/Options/index.d.ts
@@ -26,7 +26,7 @@ type RequestKeywordDenylist = {
key: string;
value: any;
};
-export interface VerifyUserEmailsRequest {
+export interface EmailVerificationRequest {
original?: any;
object: any;
master?: boolean;
@@ -90,8 +90,8 @@ export interface ParseServerOptions {
auth?: Record;
enableInsecureAuthAdapters?: boolean;
maxUploadSize?: string;
- verifyUserEmails?: boolean | ((params: VerifyUserEmailsRequest) => boolean | Promise);
- preventLoginWithUnverifiedEmail?: boolean | ((params: VerifyUserEmailsRequest) => boolean | Promise);
+ verifyUserEmails?: boolean | ((params: EmailVerificationRequest) => boolean | Promise);
+ preventLoginWithUnverifiedEmail?: boolean | ((params: EmailVerificationRequest) => boolean | Promise);
preventSignupWithUnverifiedEmail?: boolean;
emailVerifyTokenValidityDuration?: number;
emailVerifyTokenReuseIfValid?: boolean;