18 Commits

Author SHA1 Message Date
semantic-release-bot
1b5bd2f754 chore(release): 9.2.0-alpha.1 [skip ci]
# [9.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/9.1.1...9.2.0-alpha.1) (2026-01-24)

### Features

* Add option `databaseOptions.clientMetadata` to send custom metadata to database server for logging and debugging ([#10017](https://github.com/parse-community/parse-server/issues/10017)) ([756c204](756c204220))
2026-01-24 21:45:25 +00:00
Copilot
756c204220 feat: Add option databaseOptions.clientMetadata to send custom metadata to database server for logging and debugging (#10017) 2026-01-24 22:44:38 +01:00
dependabot[bot]
ba3e7602e6 refactor: Bump redis from 4.7.0 to 5.10.0 (#9994) 2026-01-17 22:09:44 +01:00
dependabot[bot]
82e0d3ace1 refactor: Bump commander from 13.1.0 to 14.0.2 (#9992) 2026-01-17 19:52:59 +01:00
Manuel
69da47284c docs: Add frozen LTS branch info to CONTRIBUTING guide (#10008) 2026-01-16 05:05:27 +01:00
dependabot[bot]
774cc54f81 refactor: Bump @semantic-release/github from 11.0.2 to 11.0.3 (#9789) 2025-12-25 14:23:57 +01:00
semantic-release-bot
b3725faee2 chore(release): 9.1.1 [skip ci]
## [9.1.1](https://github.com/parse-community/parse-server/compare/9.1.0...9.1.1) (2025-12-16)

### Bug Fixes

* Server-Side Request Forgery (SSRF) in Instagram auth adapter [GHSA-3f5f-xgrj-97pf](https://github.com/parse-community/parse-server/security/advisories/GHSA-3f5f-xgrj-97pf) ([#9988](https://github.com/parse-community/parse-server/issues/9988)) ([fbcc938](fbcc938b5a))
2025-12-16 01:34:54 +00:00
Manuel
519d798781 build: Release (#9990) 2025-12-16 02:34:01 +01:00
GitHub Actions
9f98d3999c empty commit to trigger CI 2025-12-16 01:32:07 +00:00
semantic-release-bot
3d395b3ce5 chore(release): 9.1.1-alpha.1 [skip ci]
## [9.1.1-alpha.1](https://github.com/parse-community/parse-server/compare/9.1.0...9.1.1-alpha.1) (2025-12-16)

### Bug Fixes

* Server-Side Request Forgery (SSRF) in Instagram auth adapter [GHSA-3f5f-xgrj-97pf](https://github.com/parse-community/parse-server/security/advisories/GHSA-3f5f-xgrj-97pf) ([#9988](https://github.com/parse-community/parse-server/issues/9988)) ([fbcc938](fbcc938b5a))
2025-12-16 01:25:34 +00:00
Manuel
fbcc938b5a fix: Server-Side Request Forgery (SSRF) in Instagram auth adapter [GHSA-3f5f-xgrj-97pf](https://github.com/parse-community/parse-server/security/advisories/GHSA-3f5f-xgrj-97pf) (#9988) 2025-12-16 02:24:37 +01:00
semantic-release-bot
2e06fa1139 chore(release): 9.1.0 [skip ci]
# [9.1.0](https://github.com/parse-community/parse-server/compare/9.0.0...9.1.0) (2025-12-14)

### Bug Fixes

* Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) ([#9985](https://github.com/parse-community/parse-server/issues/9985)) ([3074eb7](3074eb70f5))

### Features

* Add option `logLevels.signupUsernameTaken` to change log level of username already exists sign-up rejection ([#9962](https://github.com/parse-community/parse-server/issues/9962)) ([f18f307](f18f3073d7))
* Add support for custom HTTP status code and headers to Cloud Function response with Express-style syntax ([#9980](https://github.com/parse-community/parse-server/issues/9980)) ([8eeab8d](8eeab8dc57))
* Log more debug info when failing to set duplicate value for field with unique values ([#9919](https://github.com/parse-community/parse-server/issues/9919)) ([a23b192](a23b192466))
2025-12-14 15:49:33 +00:00
Manuel
8c4d67a0fe build: Release (#9987) 2025-12-14 16:48:45 +01:00
GitHub Actions
ae0781d0ac empty commit to trigger CI 2025-12-14 15:41:22 +00:00
semantic-release-bot
0e308feaa7 chore(release): 9.1.0-alpha.4 [skip ci]
# [9.1.0-alpha.4](https://github.com/parse-community/parse-server/compare/9.1.0-alpha.3...9.1.0-alpha.4) (2025-12-14)

### Features

* Log more debug info when failing to set duplicate value for field with unique values ([#9919](https://github.com/parse-community/parse-server/issues/9919)) ([a23b192](a23b192466))
2025-12-14 15:40:04 +00:00
Rahul Lanjewar
a23b192466 feat: Log more debug info when failing to set duplicate value for field with unique values (#9919) 2025-12-14 16:39:17 +01:00
semantic-release-bot
98a42e5277 chore(release): 9.1.0-alpha.3 [skip ci]
# [9.1.0-alpha.3](https://github.com/parse-community/parse-server/compare/9.1.0-alpha.2...9.1.0-alpha.3) (2025-12-14)

### Bug Fixes

* Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) ([#9985](https://github.com/parse-community/parse-server/issues/9985)) ([3074eb7](3074eb70f5))
2025-12-14 14:45:10 +00:00
Manuel
3074eb70f5 fix: Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) (#9985) 2025-12-14 15:44:04 +01:00
28 changed files with 633 additions and 374 deletions

View File

@@ -605,6 +605,8 @@ This creates a risk that a vulnerability is indirectly disclosed by publishing a
While the current major version is published on branch `release`, a Long-Term-Support (LTS) version is published on branch `release-#.x.x`, for example `release-4.x.x` for the Parse Server 4.x LTS branch.
Only the previous major version is under LTS. Older major versions are no longer maintained and their `release-#.x.x` branches are frozen; no further changes will be made. If you need features or fixes on an older branch, fork it and backport changes in your own branch.
### Preparing Release
The following changes are done in the `alpha` branch, before publishing the last `beta` version that will eventually become the major release. This way the changes trickle naturally through all branches and code consistency is ensured among branches.

View File

@@ -1,3 +1,31 @@
# [9.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/9.1.1...9.2.0-alpha.1) (2026-01-24)
### Features
* Add option `databaseOptions.clientMetadata` to send custom metadata to database server for logging and debugging ([#10017](https://github.com/parse-community/parse-server/issues/10017)) ([756c204](https://github.com/parse-community/parse-server/commit/756c204220a2c7be3770b7d4a49f11e8903323db))
## [9.1.1-alpha.1](https://github.com/parse-community/parse-server/compare/9.1.0...9.1.1-alpha.1) (2025-12-16)
### Bug Fixes
* Server-Side Request Forgery (SSRF) in Instagram auth adapter [GHSA-3f5f-xgrj-97pf](https://github.com/parse-community/parse-server/security/advisories/GHSA-3f5f-xgrj-97pf) ([#9988](https://github.com/parse-community/parse-server/issues/9988)) ([fbcc938](https://github.com/parse-community/parse-server/commit/fbcc938b5ade5ff4c30598ac51272ef7ecef0616))
# [9.1.0-alpha.4](https://github.com/parse-community/parse-server/compare/9.1.0-alpha.3...9.1.0-alpha.4) (2025-12-14)
### Features
* Log more debug info when failing to set duplicate value for field with unique values ([#9919](https://github.com/parse-community/parse-server/issues/9919)) ([a23b192](https://github.com/parse-community/parse-server/commit/a23b1924668920f3c92fec0566b57091d0e8aae8))
# [9.1.0-alpha.3](https://github.com/parse-community/parse-server/compare/9.1.0-alpha.2...9.1.0-alpha.3) (2025-12-14)
### Bug Fixes
* Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) ([#9985](https://github.com/parse-community/parse-server/issues/9985)) ([3074eb7](https://github.com/parse-community/parse-server/commit/3074eb70f5b58bf72b528ae7b7804ed2d90455ce))
# [9.1.0-alpha.2](https://github.com/parse-community/parse-server/compare/9.1.0-alpha.1...9.1.0-alpha.2) (2025-12-14)

View File

@@ -1,3 +1,23 @@
## [9.1.1](https://github.com/parse-community/parse-server/compare/9.1.0...9.1.1) (2025-12-16)
### Bug Fixes
* Server-Side Request Forgery (SSRF) in Instagram auth adapter [GHSA-3f5f-xgrj-97pf](https://github.com/parse-community/parse-server/security/advisories/GHSA-3f5f-xgrj-97pf) ([#9988](https://github.com/parse-community/parse-server/issues/9988)) ([fbcc938](https://github.com/parse-community/parse-server/commit/fbcc938b5ade5ff4c30598ac51272ef7ecef0616))
# [9.1.0](https://github.com/parse-community/parse-server/compare/9.0.0...9.1.0) (2025-12-14)
### Bug Fixes
* Cross-Site Scripting (XSS) via HTML pages for password reset and email verification [GHSA-jhgf-2h8h-ggxv](https://github.com/parse-community/parse-server/security/advisories/GHSA-jhgf-2h8h-ggxv) ([#9985](https://github.com/parse-community/parse-server/issues/9985)) ([3074eb7](https://github.com/parse-community/parse-server/commit/3074eb70f5b58bf72b528ae7b7804ed2d90455ce))
### Features
* Add option `logLevels.signupUsernameTaken` to change log level of username already exists sign-up rejection ([#9962](https://github.com/parse-community/parse-server/issues/9962)) ([f18f307](https://github.com/parse-community/parse-server/commit/f18f3073d70a292bc70b5d572ef58e4845de89ca))
* Add support for custom HTTP status code and headers to Cloud Function response with Express-style syntax ([#9980](https://github.com/parse-community/parse-server/issues/9980)) ([8eeab8d](https://github.com/parse-community/parse-server/commit/8eeab8dc57edef3751aa188d8247f296a270b083))
* Log more debug info when failing to set duplicate value for field with unique values ([#9919](https://github.com/parse-community/parse-server/issues/9919)) ([a23b192](https://github.com/parse-community/parse-server/commit/a23b1924668920f3c92fec0566b57091d0e8aae8))
# [9.0.0](https://github.com/parse-community/parse-server/compare/8.6.0...9.0.0) (2025-12-14)

678
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "9.1.0-alpha.2",
"version": "9.2.0-alpha.1",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -28,7 +28,7 @@
"@parse/fs-files-adapter": "3.0.0",
"@parse/push-adapter": "8.1.0",
"bcryptjs": "3.0.2",
"commander": "13.1.0",
"commander": "14.0.2",
"cors": "2.8.5",
"deepcopy": "2.1.0",
"express": "5.2.1",
@@ -55,7 +55,7 @@
"pluralize": "8.0.0",
"punycode": "2.3.1",
"rate-limit-redis": "4.2.0",
"redis": "4.7.0",
"redis": "5.10.0",
"semver": "7.7.2",
"subscriptions-transport-ws": "0.11.0",
"tv4": "1.3.0",
@@ -78,7 +78,7 @@
"@semantic-release/changelog": "6.0.3",
"@semantic-release/commit-analyzer": "13.0.1",
"@semantic-release/git": "10.0.1",
"@semantic-release/github": "11.0.2",
"@semantic-release/github": "11.0.3",
"@semantic-release/npm": "12.0.1",
"@semantic-release/release-notes-generator": "14.0.3",
"all-node-versions": "13.0.1",

View File

@@ -14,9 +14,9 @@
<body>
<h1>{{appName}}</h1>
<h1>Expired verification link!</h1>
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
<input name="token" type="hidden" value="{{{token}}}">
<input name="locale" type="hidden" value="{{{locale}}}">
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
<input name="token" type="hidden" value="{{token}}">
<input name="locale" type="hidden" value="{{locale}}">
<button type="submit">Resend Link</button>
</form>
</body>

View File

@@ -23,11 +23,11 @@
<p>You can set a new Password for your account: {{username}}</p>
<br />
<p>{{error}}</p>
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
<input name='utf-8' type='hidden' value='✓' />
<input name="username" type="hidden" id="username" value="{{{username}}}" />
<input name="token" type="hidden" id="token" value="{{{token}}}" />
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
<input name="username" type="hidden" id="username" value="{{username}}" />
<input name="token" type="hidden" id="token" value="{{token}}" />
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
<p>New Password</p>
<input name="new_password" type="password" id="password" />

View File

@@ -14,9 +14,9 @@
<body>
<h1>{{appName}}</h1>
<h1>Expired verification link!</h1>
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
<input name="token" type="hidden" value="{{{token}}}">
<input name="locale" type="hidden" value="{{{locale}}}">
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
<input name="token" type="hidden" value="{{token}}">
<input name="locale" type="hidden" value="{{locale}}">
<button type="submit">Resend Link</button>
</form>
</body>

View File

@@ -23,11 +23,11 @@
<p>You can set a new Password for your account: {{username}}</p>
<br />
<p>{{error}}</p>
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
<input name='utf-8' type='hidden' value='✓' />
<input name="username" type="hidden" id="username" value="{{{username}}}" />
<input name="token" type="hidden" id="token" value="{{{token}}}" />
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
<input name="username" type="hidden" id="username" value="{{username}}" />
<input name="token" type="hidden" id="token" value="{{token}}" />
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
<p>New Password</p>
<input name="new_password" type="password" id="password" />

View File

@@ -14,9 +14,9 @@
<body>
<h1>{{appName}}</h1>
<h1>Expired verification link!</h1>
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
<input name="token" type="hidden" value="{{{token}}}">
<input name="locale" type="hidden" value="{{{locale}}}">
<form method="POST" action="{{publicServerUrl}}/apps/{{appId}}/resend_verification_email">
<input name="token" type="hidden" value="{{token}}">
<input name="locale" type="hidden" value="{{locale}}">
<button type="submit">Resend Link</button>
</form>
</body>

View File

@@ -23,11 +23,11 @@
<p>You can set a new Password for your account: {{username}}</p>
<br />
<p>{{error}}</p>
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
<form id='form' action='{{publicServerUrl}}/apps/{{appId}}/request_password_reset' method='POST'>
<input name='utf-8' type='hidden' value='✓' />
<input name="username" type="hidden" id="username" value="{{{username}}}" />
<input name="token" type="hidden" id="token" value="{{{token}}}" />
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
<input name="username" type="hidden" id="username" value="{{username}}" />
<input name="token" type="hidden" id="token" value="{{token}}" />
<input name="locale" type="hidden" id="locale" value="{{locale}}" />
<p>New Password</p>
<input name="new_password" type="password" id="password" />

View File

@@ -30,6 +30,7 @@ const nestedOptionTypes = [
/** The prefix of environment variables for nested options. */
const nestedOptionEnvPrefix = {
AccountLockoutOptions: 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
DatabaseOptionsClientMetadata: 'PARSE_SERVER_DATABASE_CLIENT_METADATA_',
CustomPagesOptions: 'PARSE_SERVER_CUSTOM_PAGES_',
DatabaseOptions: 'PARSE_SERVER_DATABASE_',
FileUploadOptions: 'PARSE_SERVER_FILE_UPLOAD_',

View File

@@ -101,6 +101,31 @@ describe('InstagramAdapter', function () {
'Instagram auth is invalid for this user.'
);
});
it('should ignore client-provided apiURL and use hardcoded endpoint', async () => {
const accessToken = 'mockAccessToken';
const authData = {
id: 'mockUserId',
apiURL: 'https://example.com/',
};
mockFetch([
{
url: 'https://graph.instagram.com/me?fields=id&access_token=mockAccessToken',
method: 'GET',
response: {
ok: true,
json: () =>
Promise.resolve({
id: 'mockUserId',
}),
},
},
]);
const user = await adapter.getUserFromAccessToken(accessToken, authData);
expect(user).toEqual({ id: 'mockUserId' });
});
});
describe('InstagramAdapter E2E Test', function () {

View File

@@ -475,4 +475,28 @@ describe_only_db('mongo')('GridFSBucket', () => {
expect(e.message).toEqual('Client must be connected before running operations');
}
});
describe('MongoDB Client Metadata', () => {
it('should not pass metadata to MongoClient by default', async () => {
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
await gfsAdapter._connect();
const driverInfo = gfsAdapter._client.s.options.driverInfo;
// Either driverInfo should be undefined, or it should not contain our custom metadata
if (driverInfo) {
expect(driverInfo.name).toBeUndefined();
}
await gfsAdapter.handleShutdown();
});
it('should pass custom metadata to MongoClient when configured', async () => {
const customMetadata = { name: 'MyParseServer', version: '1.0.0' };
const gfsAdapter = new GridFSBucketAdapter(databaseURI, {
clientMetadata: customMetadata
});
await gfsAdapter._connect();
expect(gfsAdapter._client.s.options.driverInfo.name).toBe(customMetadata.name);
expect(gfsAdapter._client.s.options.driverInfo.version).toBe(customMetadata.version);
await gfsAdapter.handleShutdown();
});
});
});

View File

@@ -1063,4 +1063,29 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
await adapter.handleShutdown();
});
});
describe('MongoDB Client Metadata', () => {
it('should not pass metadata to MongoClient by default', async () => {
const adapter = new MongoStorageAdapter({ uri: databaseURI });
await adapter.connect();
const driverInfo = adapter.client.s.options.driverInfo;
// Either driverInfo should be undefined, or it should not contain our custom metadata
if (driverInfo) {
expect(driverInfo.name).toBeUndefined();
}
await adapter.handleShutdown();
});
it('should pass custom metadata to MongoClient when configured', async () => {
const customMetadata = { name: 'MyParseServer', version: '1.0.0' };
const adapter = new MongoStorageAdapter({
uri: databaseURI,
mongoOptions: { clientMetadata: customMetadata }
});
await adapter.connect();
expect(adapter.client.s.options.driverInfo.name).toBe(customMetadata.name);
expect(adapter.client.s.options.driverInfo.version).toBe(customMetadata.version);
await adapter.handleShutdown();
});
});
});

View File

@@ -1180,4 +1180,72 @@ describe('Pages Router', () => {
});
});
});
describe('XSS Protection', () => {
beforeEach(async () => {
await reconfigureServer({
appId: 'test',
appName: 'exampleAppname',
publicServerURL: 'http://localhost:8378/1',
pages: { enableRouter: true },
});
});
it('should escape XSS payloads in token parameter', async () => {
const xssPayload = '"><script>alert("XSS")</script>';
const response = await request({
url: `http://localhost:8378/1/apps/choose_password?token=${encodeURIComponent(xssPayload)}&username=test&appId=test`,
});
expect(response.status).toBe(200);
expect(response.text).not.toContain('<script>alert("XSS")</script>');
expect(response.text).toContain('&quot;&gt;&lt;script&gt;');
});
it('should escape XSS in username parameter', async () => {
const xssUsername = '<img src=x onerror=alert(1)>';
const response = await request({
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(xssUsername)}&appId=test`,
});
expect(response.status).toBe(200);
expect(response.text).not.toContain('<img src=x onerror=alert(1)>');
expect(response.text).toContain('&lt;img');
});
it('should escape XSS in locale parameter', async () => {
const xssLocale = '"><svg/onload=alert(1)>';
const response = await request({
url: `http://localhost:8378/1/apps/choose_password?locale=${encodeURIComponent(xssLocale)}&appId=test`,
});
expect(response.status).toBe(200);
expect(response.text).not.toContain('<svg/onload=alert(1)>');
expect(response.text).toContain('&quot;&gt;&lt;svg');
});
it('should handle legitimate usernames with quotes correctly', async () => {
const username = "O'Brien";
const response = await request({
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(username)}&appId=test`,
});
expect(response.status).toBe(200);
// Should be properly escaped as HTML entity
expect(response.text).toContain('O&#39;Brien');
// Should NOT contain unescaped quote that breaks HTML
expect(response.text).not.toContain('value="O\'Brien"');
});
it('should handle legitimate usernames with ampersands correctly', async () => {
const username = 'Smith & Co';
const response = await request({
url: `http://localhost:8378/1/apps/choose_password?username=${encodeURIComponent(username)}&appId=test`,
});
expect(response.status).toBe(200);
// Should be properly escaped
expect(response.text).toContain('Smith &amp; Co');
});
});
});

View File

@@ -21,7 +21,6 @@ describe('RedisPubSub', function () {
expect(redis.createClient).toHaveBeenCalledWith({
url: 'redisAddress',
socket_keepalive: true,
no_ready_check: true,
});
});
@@ -35,7 +34,6 @@ describe('RedisPubSub', function () {
expect(redis.createClient).toHaveBeenCalledWith({
url: 'redisAddress',
socket_keepalive: true,
no_ready_check: true,
});
});

View File

@@ -3842,6 +3842,7 @@ describe('schemas', () => {
});
it_id('cbd5d897-b938-43a4-8f5a-5d02dd2be9be')(it_exclude_dbs(['postgres']))('cannot update to duplicate value on unique index', done => {
loggerErrorSpy.calls.reset();
const index = {
code: 1,
};
@@ -3868,6 +3869,12 @@ describe('schemas', () => {
.then(done.fail)
.catch(error => {
expect(error.code).toEqual(Parse.Error.DUPLICATE_VALUE);
// Client should only see generic message (no schema info exposed)
expect(error.message).toEqual('A duplicate value for a field with unique values was provided');
// Server logs should contain full MongoDB error message with detailed information
expect(loggerErrorSpy).toHaveBeenCalledWith('Duplicate key error:', jasmine.stringContaining('E11000 duplicate key error'));
expect(loggerErrorSpy).toHaveBeenCalledWith('Duplicate key error:', jasmine.stringContaining('test_UniqueIndexClass'));
expect(loggerErrorSpy).toHaveBeenCalledWith('Duplicate key error:', jasmine.stringContaining('code_1'));
done();
});
});

View File

@@ -96,8 +96,7 @@ class InstagramAdapter extends BaseAuthCodeAdapter {
}
async getUserFromAccessToken(accessToken, authData) {
const defaultURL = 'https://graph.instagram.com/';
const apiURL = authData.apiURL || defaultURL;
const apiURL = 'https://graph.instagram.com/';
const path = `${apiURL}me?fields=id&access_token=${accessToken}`;
const response = await fetch(path);

View File

@@ -35,7 +35,7 @@ export class RedisCacheAdapter {
return;
}
try {
await this.client.quit();
await this.client.close();
} catch (err) {
logger.error('RedisCacheAdapter error on shutdown', { error: err });
}

View File

@@ -17,6 +17,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
_connectionPromise: Promise<Db>;
_mongoOptions: Object;
_algorithm: string;
_clientMetadata: ?{ name: string, version: string };
constructor(
mongoDatabaseURI = defaults.DefaultMongoURI,
@@ -36,6 +37,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
: null;
const defaultMongoOptions = {};
const _mongoOptions = Object.assign(defaultMongoOptions, mongoOptions);
this._clientMetadata = mongoOptions.clientMetadata;
// Remove Parse Server-specific options that should not be passed to MongoDB client
for (const key of ParseServerDatabaseOptions) {
delete _mongoOptions[key];
@@ -45,7 +47,16 @@ export class GridFSBucketAdapter extends FilesAdapter {
_connect() {
if (!this._connectionPromise) {
this._connectionPromise = MongoClient.connect(this._databaseURI, this._mongoOptions).then(
// Only use driverInfo if clientMetadata option is set
const options = { ...this._mongoOptions };
if (this._clientMetadata) {
options.driverInfo = {
name: this._clientMetadata.name,
version: this._clientMetadata.version
};
}
this._connectionPromise = MongoClient.connect(this._databaseURI, options).then(
client => {
this._client = client;
return client.db(client.s.options.dbName);

View File

@@ -2,7 +2,6 @@ import { createClient } from 'redis';
import { logger } from '../../logger';
function createPublisher({ redisURL, redisOptions = {} }): any {
redisOptions.no_ready_check = true;
const client = createClient({ url: redisURL, ...redisOptions });
client.on('error', err => { logger.error('RedisPubSub Publisher client error', { error: err }) });
client.on('connect', () => {});
@@ -12,7 +11,6 @@ function createPublisher({ redisURL, redisOptions = {} }): any {
}
function createSubscriber({ redisURL, redisOptions = {} }): any {
redisOptions.no_ready_check = true;
const client = createClient({ url: redisURL, ...redisOptions });
client.on('error', err => { logger.error('RedisPubSub Subscriber client error', { error: err }) });
client.on('connect', () => {});

View File

@@ -134,6 +134,7 @@ export class MongoStorageAdapter implements StorageAdapter {
_onchange: any;
_stream: any;
_logClientEvents: ?Array<any>;
_clientMetadata: ?{ name: string, version: string };
// Public
connectionPromise: ?Promise<any>;
database: any;
@@ -156,6 +157,7 @@ export class MongoStorageAdapter implements StorageAdapter {
this.schemaCacheTtl = mongoOptions.schemaCacheTtl;
this.disableIndexFieldValidation = !!mongoOptions.disableIndexFieldValidation;
this._logClientEvents = mongoOptions.logClientEvents;
this._clientMetadata = mongoOptions.clientMetadata;
// Create a copy of mongoOptions and remove Parse Server-specific options that should not
// be passed to MongoDB client. Note: We only delete from this._mongoOptions, not from the
@@ -179,7 +181,17 @@ export class MongoStorageAdapter implements StorageAdapter {
// parsing and re-formatting causes the auth value (if there) to get URI
// encoded
const encodedUri = formatUrl(parseUrl(this._uri));
this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions)
// Only use driverInfo if clientMetadata option is set
const options = { ...this._mongoOptions };
if (this._clientMetadata) {
options.driverInfo = {
name: this._clientMetadata.name,
version: this._clientMetadata.version
};
}
this.connectionPromise = MongoClient.connect(encodedUri, options)
.then(client => {
// Starting mongoDB 3.0, the MongoClient.connect don't return a DB anymore but a client
// Fortunately, we can get back the options and use them to select the proper DB.
@@ -519,7 +531,7 @@ export class MongoStorageAdapter implements StorageAdapter {
.then(() => ({ ops: [mongoObject] }))
.catch(error => {
if (error.code === 11000) {
// Duplicate value
logger.error('Duplicate key error:', error.message);
const err = new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
@@ -605,6 +617,7 @@ export class MongoStorageAdapter implements StorageAdapter {
.then(result => mongoObjectToParseObject(className, result, schema))
.catch(error => {
if (error.code === 11000) {
logger.error('Duplicate key error:', error.message);
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'

View File

@@ -109,9 +109,9 @@ class ParseLiveQueryServer {
this.subscriber.close?.(),
]);
}
if (typeof this.subscriber.quit === 'function') {
if (typeof this.subscriber.close === 'function') {
try {
await this.subscriber.quit();
await this.subscriber.close();
} catch (err) {
logger.error('PubSubAdapter error on shutdown', { error: err });
}

View File

@@ -1179,6 +1179,13 @@ module.exports.DatabaseOptions = {
'The MongoDB driver option to specify the amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the autoSelectFamily option. If set to a positive integer less than 10, the value 10 is used instead.',
action: parsers.numberParser('autoSelectFamilyAttemptTimeout'),
},
clientMetadata: {
env: 'PARSE_SERVER_DATABASE_CLIENT_METADATA',
help:
"Custom metadata to append to database client connections for identifying Parse Server instances in database logs. If set, this metadata will be visible in database logs during connection handshakes. This can help with debugging and monitoring in deployments with multiple database clients. Set `name` to identify your application (e.g., 'MyApp') and `version` to your application's version. Leave undefined (default) to disable this feature and avoid the additional data transfer overhead.",
action: parsers.objectParser,
type: 'DatabaseOptionsClientMetadata',
},
compressors: {
env: 'PARSE_SERVER_DATABASE_COMPRESSORS',
help:
@@ -1461,6 +1468,18 @@ module.exports.DatabaseOptions = {
action: parsers.numberParser('zlibCompressionLevel'),
},
};
module.exports.DatabaseOptionsClientMetadata = {
name: {
env: 'PARSE_SERVER_DATABASE_CLIENT_METADATA_NAME',
help: "The name to identify your application in database logs (e.g., 'MyApp').",
required: true,
},
version: {
env: 'PARSE_SERVER_DATABASE_CLIENT_METADATA_VERSION',
help: "The version of your application (e.g., '1.0.0').",
required: true,
},
};
module.exports.AuthAdapter = {
enabled: {
help: 'Is `true` if the auth adapter is enabled, `false` otherwise.',

View File

@@ -264,6 +264,7 @@
* @property {String} authSource The MongoDB driver option to specify the database name associated with the user's credentials.
* @property {Boolean} autoSelectFamily The MongoDB driver option to set whether the socket attempts to connect to IPv6 and IPv4 addresses until a connection is established. If available, the driver will select the first IPv6 address.
* @property {Number} autoSelectFamilyAttemptTimeout The MongoDB driver option to specify the amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the autoSelectFamily option. If set to a positive integer less than 10, the value 10 is used instead.
* @property {DatabaseOptionsClientMetadata} clientMetadata Custom metadata to append to database client connections for identifying Parse Server instances in database logs. If set, this metadata will be visible in database logs during connection handshakes. This can help with debugging and monitoring in deployments with multiple database clients. Set `name` to identify your application (e.g., 'MyApp') and `version` to your application's version. Leave undefined (default) to disable this feature and avoid the additional data transfer overhead.
* @property {Union} compressors The MongoDB driver option to specify an array or comma-delimited string of compressors to enable network compression for communication between this client and a mongod/mongos instance.
* @property {Number} connectTimeoutMS The MongoDB driver option to specify the amount of time, in milliseconds, to wait to establish a single TCP socket connection to the server before raising an error. Specifying 0 disables the connection timeout.
* @property {Boolean} createIndexRoleName Set to `true` to automatically create a unique index on the name field of the _Role collection on server start. Set to `false` to skip index creation. Default is `true`.<br><br>⚠️ When setting this option to `false` to manually create the index, keep in mind that the otherwise automatically created index may change in the future to be optimized for the internal usage by Parse Server.
@@ -315,6 +316,12 @@
* @property {Number} zlibCompressionLevel The MongoDB driver option to specify the compression level if using zlib for network compression (0-9).
*/
/**
* @interface DatabaseOptionsClientMetadata
* @property {String} name The name to identify your application in database logs (e.g., 'MyApp').
* @property {String} version The version of your application (e.g., '1.0.0').
*/
/**
* @interface AuthAdapter
* @property {Boolean} enabled Is `true` if the auth adapter is enabled, `false` otherwise.

View File

@@ -755,6 +755,15 @@ export interface DatabaseOptions {
allowPublicExplain: ?boolean;
/* An array of MongoDB client event configurations to enable logging of specific events. */
logClientEvents: ?(LogClientEvent[]);
/* Custom metadata to append to database client connections for identifying Parse Server instances in database logs. If set, this metadata will be visible in database logs during connection handshakes. This can help with debugging and monitoring in deployments with multiple database clients. Set `name` to identify your application (e.g., 'MyApp') and `version` to your application's version. Leave undefined (default) to disable this feature and avoid the additional data transfer overhead. */
clientMetadata: ?DatabaseOptionsClientMetadata;
}
export interface DatabaseOptionsClientMetadata {
/* The name to identify your application in database logs (e.g., 'MyApp'). */
name: string;
/* The version of your application (e.g., '1.0.0'). */
version: string;
}
export interface AuthAdapter {

View File

@@ -38,6 +38,7 @@ export const DefaultMongoURI = DefinitionDefaults.databaseURI;
// before passing to MongoDB client
export const ParseServerDatabaseOptions = [
'allowPublicExplain',
'clientMetadata',
'createIndexRoleName',
'createIndexUserEmail',
'createIndexUserEmailCaseInsensitive',