|
1 | 1 | const request = require('../lib/request'); |
| 2 | +const Config = require('../lib/Config'); |
2 | 3 |
|
3 | 4 | describe('Vulnerabilities', () => { |
4 | 5 | describe('(GHSA-8xq9-g7ch-35hg) Custom object ID allows to acquire role privilege', () => { |
@@ -1704,3 +1705,183 @@ describe('(GHSA-r2m8-pxm9-9c4g) Protected fields WHERE clause bypass via dot-not |
1704 | 1705 | expect(res.status).toBe(400); |
1705 | 1706 | }); |
1706 | 1707 | }); |
| 1708 | + |
| 1709 | +describe('(GHSA-w54v-hf9p-8856) User enumeration via email verification endpoint', () => { |
| 1710 | + let sendVerificationEmail; |
| 1711 | + |
| 1712 | + async function createTestUsers() { |
| 1713 | + const user = new Parse.User(); |
| 1714 | + user.setUsername('testuser'); |
| 1715 | + user.setPassword('password123'); |
| 1716 | + user.set('email', 'unverified@example.com'); |
| 1717 | + await user.signUp(); |
| 1718 | + |
| 1719 | + const user2 = new Parse.User(); |
| 1720 | + user2.setUsername('verifieduser'); |
| 1721 | + user2.setPassword('password123'); |
| 1722 | + user2.set('email', 'verified@example.com'); |
| 1723 | + await user2.signUp(); |
| 1724 | + const config = Config.get(Parse.applicationId); |
| 1725 | + await config.database.update( |
| 1726 | + '_User', |
| 1727 | + { username: 'verifieduser' }, |
| 1728 | + { emailVerified: true } |
| 1729 | + ); |
| 1730 | + } |
| 1731 | + |
| 1732 | + describe('default (emailVerifySuccessOnInvalidEmail: true)', () => { |
| 1733 | + beforeEach(async () => { |
| 1734 | + sendVerificationEmail = jasmine.createSpy('sendVerificationEmail'); |
| 1735 | + await reconfigureServer({ |
| 1736 | + appName: 'test', |
| 1737 | + publicServerURL: 'http://localhost:8378/1', |
| 1738 | + verifyUserEmails: true, |
| 1739 | + emailAdapter: { |
| 1740 | + sendVerificationEmail, |
| 1741 | + sendPasswordResetEmail: () => Promise.resolve(), |
| 1742 | + sendMail: () => {}, |
| 1743 | + }, |
| 1744 | + }); |
| 1745 | + await createTestUsers(); |
| 1746 | + }); |
| 1747 | + it('returns success for non-existent email', async () => { |
| 1748 | + const response = await request({ |
| 1749 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1750 | + method: 'POST', |
| 1751 | + body: { email: 'nonexistent@example.com' }, |
| 1752 | + headers: { |
| 1753 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1754 | + 'X-Parse-REST-API-Key': 'rest', |
| 1755 | + 'Content-Type': 'application/json', |
| 1756 | + }, |
| 1757 | + }); |
| 1758 | + expect(response.status).toBe(200); |
| 1759 | + expect(response.data).toEqual({}); |
| 1760 | + }); |
| 1761 | + |
| 1762 | + it('returns success for already verified email', async () => { |
| 1763 | + const response = await request({ |
| 1764 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1765 | + method: 'POST', |
| 1766 | + body: { email: 'verified@example.com' }, |
| 1767 | + headers: { |
| 1768 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1769 | + 'X-Parse-REST-API-Key': 'rest', |
| 1770 | + 'Content-Type': 'application/json', |
| 1771 | + }, |
| 1772 | + }); |
| 1773 | + expect(response.status).toBe(200); |
| 1774 | + expect(response.data).toEqual({}); |
| 1775 | + }); |
| 1776 | + |
| 1777 | + it('returns success for unverified email', async () => { |
| 1778 | + const response = await request({ |
| 1779 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1780 | + method: 'POST', |
| 1781 | + body: { email: 'unverified@example.com' }, |
| 1782 | + headers: { |
| 1783 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1784 | + 'X-Parse-REST-API-Key': 'rest', |
| 1785 | + 'Content-Type': 'application/json', |
| 1786 | + }, |
| 1787 | + }); |
| 1788 | + expect(response.status).toBe(200); |
| 1789 | + expect(response.data).toEqual({}); |
| 1790 | + }); |
| 1791 | + |
| 1792 | + it('does not send verification email for non-existent email', async () => { |
| 1793 | + sendVerificationEmail.calls.reset(); |
| 1794 | + await request({ |
| 1795 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1796 | + method: 'POST', |
| 1797 | + body: { email: 'nonexistent@example.com' }, |
| 1798 | + headers: { |
| 1799 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1800 | + 'X-Parse-REST-API-Key': 'rest', |
| 1801 | + 'Content-Type': 'application/json', |
| 1802 | + }, |
| 1803 | + }); |
| 1804 | + expect(sendVerificationEmail).not.toHaveBeenCalled(); |
| 1805 | + }); |
| 1806 | + |
| 1807 | + it('does not send verification email for already verified email', async () => { |
| 1808 | + sendVerificationEmail.calls.reset(); |
| 1809 | + await request({ |
| 1810 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1811 | + method: 'POST', |
| 1812 | + body: { email: 'verified@example.com' }, |
| 1813 | + headers: { |
| 1814 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1815 | + 'X-Parse-REST-API-Key': 'rest', |
| 1816 | + 'Content-Type': 'application/json', |
| 1817 | + }, |
| 1818 | + }); |
| 1819 | + expect(sendVerificationEmail).not.toHaveBeenCalled(); |
| 1820 | + }); |
| 1821 | + }); |
| 1822 | + |
| 1823 | + describe('opt-out (emailVerifySuccessOnInvalidEmail: false)', () => { |
| 1824 | + beforeEach(async () => { |
| 1825 | + sendVerificationEmail = jasmine.createSpy('sendVerificationEmail'); |
| 1826 | + await reconfigureServer({ |
| 1827 | + appName: 'test', |
| 1828 | + publicServerURL: 'http://localhost:8378/1', |
| 1829 | + verifyUserEmails: true, |
| 1830 | + emailVerifySuccessOnInvalidEmail: false, |
| 1831 | + emailAdapter: { |
| 1832 | + sendVerificationEmail, |
| 1833 | + sendPasswordResetEmail: () => Promise.resolve(), |
| 1834 | + sendMail: () => {}, |
| 1835 | + }, |
| 1836 | + }); |
| 1837 | + await createTestUsers(); |
| 1838 | + }); |
| 1839 | + |
| 1840 | + it('returns error for non-existent email', async () => { |
| 1841 | + const response = await request({ |
| 1842 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1843 | + method: 'POST', |
| 1844 | + body: { email: 'nonexistent@example.com' }, |
| 1845 | + headers: { |
| 1846 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1847 | + 'X-Parse-REST-API-Key': 'rest', |
| 1848 | + 'Content-Type': 'application/json', |
| 1849 | + }, |
| 1850 | + }).catch(e => e); |
| 1851 | + expect(response.data.code).toBe(Parse.Error.EMAIL_NOT_FOUND); |
| 1852 | + }); |
| 1853 | + |
| 1854 | + it('returns error for already verified email', async () => { |
| 1855 | + const response = await request({ |
| 1856 | + url: 'http://localhost:8378/1/verificationEmailRequest', |
| 1857 | + method: 'POST', |
| 1858 | + body: { email: 'verified@example.com' }, |
| 1859 | + headers: { |
| 1860 | + 'X-Parse-Application-Id': Parse.applicationId, |
| 1861 | + 'X-Parse-REST-API-Key': 'rest', |
| 1862 | + 'Content-Type': 'application/json', |
| 1863 | + }, |
| 1864 | + }).catch(e => e); |
| 1865 | + expect(response.status).not.toBe(200); |
| 1866 | + }); |
| 1867 | + }); |
| 1868 | + |
| 1869 | + it('rejects invalid emailVerifySuccessOnInvalidEmail values', async () => { |
| 1870 | + const invalidValues = [[], {}, 1, 'string']; |
| 1871 | + for (const value of invalidValues) { |
| 1872 | + await expectAsync( |
| 1873 | + reconfigureServer({ |
| 1874 | + appName: 'test', |
| 1875 | + publicServerURL: 'http://localhost:8378/1', |
| 1876 | + verifyUserEmails: true, |
| 1877 | + emailVerifySuccessOnInvalidEmail: value, |
| 1878 | + emailAdapter: { |
| 1879 | + sendVerificationEmail: () => {}, |
| 1880 | + sendPasswordResetEmail: () => Promise.resolve(), |
| 1881 | + sendMail: () => {}, |
| 1882 | + }, |
| 1883 | + }) |
| 1884 | + ).toBeRejectedWith('emailVerifySuccessOnInvalidEmail must be a boolean value'); |
| 1885 | + } |
| 1886 | + }); |
| 1887 | +}); |
0 commit comments