🪪 fix: Preserve Trusted Registration Provider Overrides (#13307)

This commit is contained in:
Danny Avila 2026-05-30 10:04:50 -04:00 committed by GitHub
parent d75d26164d
commit b01d34abe2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 9 deletions

View file

@ -191,13 +191,13 @@ const verifyEmail = async (req) => {
/**
* Register a new user.
* @param {IUser} user <email, password, name, username>
* @param {Partial<IUser>} [additionalData={}]
* @param {Partial<IUser>} [additionalData={}] Trusted server-provided fields, such as CLI overrides.
* @returns {Promise<{status: number, message: string, user?: IUser}>}
*/
const registerUser = async (user, additionalData = {}) => {
const { error } = registerSchema.safeParse(user);
if (error) {
const errorMessage = errorsToString(error.errors);
const result = registerSchema.safeParse(user);
if (!result.success) {
const errorMessage = errorsToString(result.error.errors);
logger.info(
'Route: register - Validation Error',
{ name: 'Request params:', value: user },
@ -207,7 +207,8 @@ const registerUser = async (user, additionalData = {}) => {
return { status: 404, message: errorMessage };
}
const { email, password, name, username, provider } = user;
const { email, password, name, username } = result.data;
const { provider, ...trustedAdditionalData } = additionalData ?? {};
let newUserId;
try {
@ -245,7 +246,7 @@ const registerUser = async (user, additionalData = {}) => {
avatar: null,
role: isFirstRegisteredUser ? SystemRoles.ADMIN : SystemRoles.USER,
password: bcrypt.hashSync(password, salt),
...additionalData,
...trustedAdditionalData,
};
const emailEnabled = checkEmailConfig();

View file

@ -42,11 +42,25 @@ jest.mock('~/models', () => ({
deleteUserById: jest.fn(),
generateRefreshToken: jest.fn(),
}));
jest.mock('~/strategies/validators', () => ({ registerSchema: { parse: jest.fn() } }));
jest.mock('~/strategies/validators', () => ({
registerSchema: {
safeParse: jest.fn((user) => ({
success: true,
data: {
name: user.name,
username: user.username,
email: user.email,
password: user.password,
confirm_password: user.confirm_password,
},
})),
},
}));
jest.mock('~/server/services/Config', () => ({ getAppConfig: jest.fn() }));
jest.mock('~/server/utils', () => ({ sendEmail: jest.fn() }));
const {
checkEmailConfig,
shouldUseSecureCookie,
isEmailDomainAllowed,
resolveAppConfigForUser,
@ -58,6 +72,9 @@ const jwt = require('jsonwebtoken');
const { logger } = require('@librechat/data-schemas');
const {
findUser,
createUser,
updateUser,
countUsers,
getUserById,
generateToken,
generateRefreshToken,
@ -67,6 +84,7 @@ const { getAppConfig } = require('~/server/services/Config');
const {
setOpenIDAuthTokens,
requestPasswordReset,
registerUser,
setAuthTokens,
setCloudFrontAuthCookies,
} = require('./AuthService');
@ -381,6 +399,59 @@ describe('setOpenIDAuthTokens', () => {
});
});
describe('registerUser', () => {
const registrationPayload = {
name: 'Test User',
username: 'testuser',
email: 'test@example.com',
password: 'Password123!',
confirm_password: 'Password123!',
};
beforeEach(() => {
jest.clearAllMocks();
process.env.ALLOW_UNVERIFIED_EMAIL_LOGIN = 'false';
checkEmailConfig.mockReturnValue(false);
isEmailDomainAllowed.mockReturnValue(true);
getAppConfig.mockResolvedValue({
balance: { enabled: false },
registration: { allowedDomains: [] },
});
findUser.mockResolvedValue(null);
countUsers.mockResolvedValue(1);
createUser.mockResolvedValue({ _id: 'new-user-id' });
updateUser.mockResolvedValue({ _id: 'new-user-id' });
});
it('ignores provider values from the public registration payload', async () => {
const result = await registerUser({ ...registrationPayload, provider: 'google' });
expect(result.status).toBe(200);
expect(createUser.mock.calls[0][0]).toEqual(
expect.objectContaining({
email: registrationPayload.email,
provider: 'local',
}),
);
});
it('allows trusted callers to set provider through additional data', async () => {
const result = await registerUser(registrationPayload, {
emailVerified: true,
provider: 'google',
});
expect(result.status).toBe(200);
expect(createUser.mock.calls[0][0]).toEqual(
expect.objectContaining({
email: registrationPayload.email,
emailVerified: true,
provider: 'google',
}),
);
});
});
describe('requestPasswordReset', () => {
beforeEach(() => {
jest.clearAllMocks();

View file

@ -103,10 +103,11 @@ or the user will need to attempt logging in to have a verification link sent to
silentExit(1);
}
const user = { email, password, name, username, confirm_password: password, provider };
const user = { email, password, name, username, confirm_password: password };
const additionalData = { emailVerified, ...(provider !== undefined ? { provider } : {}) };
let result;
try {
result = await registerUser(user, { emailVerified });
result = await registerUser(user, additionalData);
} catch (error) {
console.red('Error: ' + error.message);
silentExit(1);