Compare commits
5 Commits
alpha
...
9.3.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
506449412b | ||
|
|
b6b6327552 | ||
|
|
e64b52f77c | ||
|
|
79f581b97e | ||
|
|
87284a839a |
@@ -8,7 +8,6 @@
|
|||||||
* Run with: npm run benchmark
|
* Run with: npm run benchmark
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const core = require('@actions/core');
|
|
||||||
const Parse = require('parse/node');
|
const Parse = require('parse/node');
|
||||||
const { performance } = require('node:perf_hooks');
|
const { performance } = require('node:perf_hooks');
|
||||||
const { MongoClient } = require('mongodb');
|
const { MongoClient } = require('mongodb');
|
||||||
@@ -25,6 +24,7 @@ const LOG_ITERATIONS = false;
|
|||||||
// Parse Server instance
|
// Parse Server instance
|
||||||
let parseServer;
|
let parseServer;
|
||||||
let mongoClient;
|
let mongoClient;
|
||||||
|
let core;
|
||||||
|
|
||||||
// Logging helpers
|
// Logging helpers
|
||||||
const logInfo = message => core.info(message);
|
const logInfo = message => core.info(message);
|
||||||
@@ -529,6 +529,7 @@ async function benchmarkQueryWithIncludeNested(name) {
|
|||||||
* Run all benchmarks
|
* Run all benchmarks
|
||||||
*/
|
*/
|
||||||
async function runBenchmarks() {
|
async function runBenchmarks() {
|
||||||
|
core = await import('@actions/core');
|
||||||
logInfo('Starting Parse Server Performance Benchmarks...');
|
logInfo('Starting Parse Server Performance Benchmarks...');
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
# [9.3.0-alpha.4](https://github.com/parse-community/parse-server/compare/9.3.0-alpha.3...9.3.0-alpha.4) (2026-02-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Unlinking auth provider triggers auth data validation ([#10045](https://github.com/parse-community/parse-server/issues/10045)) ([b6b6327](https://github.com/parse-community/parse-server/commit/b6b632755263417c2a3c3a31381eedc516723740))
|
||||||
|
|
||||||
# [9.3.0-alpha.3](https://github.com/parse-community/parse-server/compare/9.3.0-alpha.2...9.3.0-alpha.3) (2026-02-07)
|
# [9.3.0-alpha.3](https://github.com/parse-community/parse-server/compare/9.3.0-alpha.2...9.3.0-alpha.3) (2026-02-07)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const core = require('@actions/core');
|
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
@@ -220,6 +219,7 @@ class CiVersionCheck {
|
|||||||
* Runs the check.
|
* Runs the check.
|
||||||
*/
|
*/
|
||||||
async check() {
|
async check() {
|
||||||
|
const core = await import('@actions/core');
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
try {
|
try {
|
||||||
console.log(`\nChecking ${this.packageName} versions in CI environments...`);
|
console.log(`\nChecking ${this.packageName} versions in CI environments...`);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
const core = require('@actions/core');
|
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const core = await import('@actions/core');
|
||||||
const [currentDefinitions, currentDocs] = await Promise.all([
|
const [currentDefinitions, currentDocs] = await Promise.all([
|
||||||
fs.readFile('./src/Options/Definitions.js', 'utf8'),
|
fs.readFile('./src/Options/Definitions.js', 'utf8'),
|
||||||
fs.readFile('./src/Options/docs.js', 'utf8'),
|
fs.readFile('./src/Options/docs.js', 'utf8'),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const core = require('@actions/core');
|
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
let core;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This checks whether any package dependency requires a minimum node engine
|
* This checks whether any package dependency requires a minimum node engine
|
||||||
@@ -137,6 +137,7 @@ class NodeEngineCheck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function check() {
|
async function check() {
|
||||||
|
core = await import('@actions/core');
|
||||||
// Define paths
|
// Define paths
|
||||||
const nodeModulesPath = path.join(__dirname, '../node_modules');
|
const nodeModulesPath = path.join(__dirname, '../node_modules');
|
||||||
const packageJsonPath = path.join(__dirname, '../package.json');
|
const packageJsonPath = path.join(__dirname, '../package.json');
|
||||||
|
|||||||
137
package-lock.json
generated
137
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "9.3.0-alpha.3",
|
"version": "9.3.0-alpha.4",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "9.3.0-alpha.3",
|
"version": "9.3.0-alpha.4",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"cors": "2.8.6",
|
"cors": "2.8.6",
|
||||||
"deepcopy": "2.1.0",
|
"deepcopy": "2.1.0",
|
||||||
"express": "5.2.1",
|
"express": "5.2.1",
|
||||||
"express-rate-limit": "7.5.1",
|
"express-rate-limit": "8.2.1",
|
||||||
"follow-redirects": "1.15.9",
|
"follow-redirects": "1.15.9",
|
||||||
"graphql": "16.11.0",
|
"graphql": "16.11.0",
|
||||||
"graphql-list-fields": "2.0.4",
|
"graphql-list-fields": "2.0.4",
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
"parse-server": "bin/parse-server"
|
"parse-server": "bin/parse-server"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "3.0.0",
|
||||||
"@apollo/client": "3.13.8",
|
"@apollo/client": "3.13.8",
|
||||||
"@babel/cli": "7.27.0",
|
"@babel/cli": "7.27.0",
|
||||||
"@babel/core": "7.29.0",
|
"@babel/core": "7.29.0",
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
"eslint-plugin-expect-type": "0.6.2",
|
"eslint-plugin-expect-type": "0.6.2",
|
||||||
"eslint-plugin-unused-imports": "4.4.1",
|
"eslint-plugin-unused-imports": "4.4.1",
|
||||||
"form-data": "4.0.5",
|
"form-data": "4.0.5",
|
||||||
"globals": "16.2.0",
|
"globals": "17.3.0",
|
||||||
"graphql-tag": "2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
"jasmine": "5.7.1",
|
"jasmine": "5.7.1",
|
||||||
"jasmine-spec-reporter": "7.0.0",
|
"jasmine-spec-reporter": "7.0.0",
|
||||||
@@ -116,37 +116,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.11.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
||||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^3.0.0",
|
||||||
"@actions/http-client": "^2.0.1"
|
"@actions/http-client": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
"version": "1.1.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
||||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "2.0.1",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz",
|
||||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
"integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "^0.0.6"
|
"tunnel": "^0.0.6",
|
||||||
|
"undici": "^6.23.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/io": {
|
"node_modules/@actions/io": {
|
||||||
"version": "1.1.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
||||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@apollo/cache-control-types": {
|
"node_modules/@apollo/cache-control-types": {
|
||||||
@@ -10341,10 +10342,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-rate-limit": {
|
"node_modules/express-rate-limit": {
|
||||||
"version": "7.5.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
|
||||||
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
|
"integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
|
||||||
"license": "MIT",
|
"dependencies": {
|
||||||
|
"ip-address": "10.0.1"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
},
|
},
|
||||||
@@ -11462,11 +11465,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "16.2.0",
|
"version": "17.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz",
|
||||||
"integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
|
"integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -12273,6 +12275,14 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ip-address": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -21701,6 +21711,15 @@
|
|||||||
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "6.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||||
|
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
@@ -22561,37 +22580,38 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": {
|
"@actions/core": {
|
||||||
"version": "1.11.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
||||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^3.0.0",
|
||||||
"@actions/http-client": "^2.0.1"
|
"@actions/http-client": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/exec": {
|
"@actions/exec": {
|
||||||
"version": "1.1.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
||||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "2.0.1",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz",
|
||||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
"integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "^0.0.6"
|
"tunnel": "^0.0.6",
|
||||||
|
"undici": "^6.23.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/io": {
|
"@actions/io": {
|
||||||
"version": "1.1.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
||||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@apollo/cache-control-types": {
|
"@apollo/cache-control-types": {
|
||||||
@@ -29679,10 +29699,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"express-rate-limit": {
|
"express-rate-limit": {
|
||||||
"version": "7.5.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
|
||||||
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
|
"integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
|
||||||
"requires": {}
|
"requires": {
|
||||||
|
"ip-address": "10.0.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"extend": {
|
"extend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
@@ -30438,9 +30460,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"version": "16.2.0",
|
"version": "17.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz",
|
||||||
"integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
|
"integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
@@ -30997,6 +31019,11 @@
|
|||||||
"p-is-promise": "^3.0.0"
|
"p-is-promise": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ip-address": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="
|
||||||
|
},
|
||||||
"ipaddr.js": {
|
"ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -37527,6 +37554,12 @@
|
|||||||
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"undici": {
|
||||||
|
"version": "6.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||||
|
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"undici-types": {
|
"undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "9.3.0-alpha.3",
|
"version": "9.3.0-alpha.4",
|
||||||
"description": "An express module providing a Parse-compatible API server",
|
"description": "An express module providing a Parse-compatible API server",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"cors": "2.8.6",
|
"cors": "2.8.6",
|
||||||
"deepcopy": "2.1.0",
|
"deepcopy": "2.1.0",
|
||||||
"express": "5.2.1",
|
"express": "5.2.1",
|
||||||
"express-rate-limit": "7.5.1",
|
"express-rate-limit": "8.2.1",
|
||||||
"follow-redirects": "1.15.9",
|
"follow-redirects": "1.15.9",
|
||||||
"graphql": "16.11.0",
|
"graphql": "16.11.0",
|
||||||
"graphql-list-fields": "2.0.4",
|
"graphql-list-fields": "2.0.4",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"ws": "8.18.2"
|
"ws": "8.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/core": "1.11.1",
|
"@actions/core": "3.0.0",
|
||||||
"@apollo/client": "3.13.8",
|
"@apollo/client": "3.13.8",
|
||||||
"@babel/cli": "7.27.0",
|
"@babel/cli": "7.27.0",
|
||||||
"@babel/core": "7.29.0",
|
"@babel/core": "7.29.0",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"eslint-plugin-expect-type": "0.6.2",
|
"eslint-plugin-expect-type": "0.6.2",
|
||||||
"eslint-plugin-unused-imports": "4.4.1",
|
"eslint-plugin-unused-imports": "4.4.1",
|
||||||
"form-data": "4.0.5",
|
"form-data": "4.0.5",
|
||||||
"globals": "16.2.0",
|
"globals": "17.3.0",
|
||||||
"graphql-tag": "2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
"jasmine": "5.7.1",
|
"jasmine": "5.7.1",
|
||||||
"jasmine-spec-reporter": "7.0.0",
|
"jasmine-spec-reporter": "7.0.0",
|
||||||
|
|||||||
@@ -1338,6 +1338,244 @@ describe('Auth Adapter features', () => {
|
|||||||
expect(user.get('authData')).toEqual({ adapterB: { id: 'test' } });
|
expect(user.get('authData')).toEqual({ adapterB: { id: 'test' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should unlink a code-based auth provider without triggering adapter validation', async () => {
|
||||||
|
const mockUserId = 'gpgamesUser123';
|
||||||
|
const mockAccessToken = 'mockAccessToken';
|
||||||
|
|
||||||
|
const otherAdapter = {
|
||||||
|
validateAppId: () => Promise.resolve(),
|
||||||
|
validateAuthData: () => Promise.resolve(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFetch([
|
||||||
|
{
|
||||||
|
url: 'https://oauth2.googleapis.com/token',
|
||||||
|
method: 'POST',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ access_token: mockAccessToken }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `https://www.googleapis.com/games/v1/players/${mockUserId}`,
|
||||||
|
method: 'GET',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ playerId: mockUserId }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await reconfigureServer({
|
||||||
|
auth: {
|
||||||
|
gpgames: {
|
||||||
|
clientId: 'testClientId',
|
||||||
|
clientSecret: 'testClientSecret',
|
||||||
|
},
|
||||||
|
otherAdapter,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sign up with username/password, then link providers
|
||||||
|
const user = new Parse.User();
|
||||||
|
await user.signUp({ username: 'gpgamesTestUser', password: 'password123' });
|
||||||
|
|
||||||
|
// Link gpgames code-based provider
|
||||||
|
await user.save({
|
||||||
|
authData: {
|
||||||
|
gpgames: { id: mockUserId, code: 'authCode123', redirect_uri: 'https://example.com/callback' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Link a second provider
|
||||||
|
await user.save({ authData: { otherAdapter: { id: 'other1' } } });
|
||||||
|
|
||||||
|
// Reset fetch spy to track calls during unlink
|
||||||
|
global.fetch.calls.reset();
|
||||||
|
|
||||||
|
// Unlink gpgames by setting authData to null; should not call beforeFind / external APIs
|
||||||
|
const sessionToken = user.getSessionToken();
|
||||||
|
await user.save({ authData: { gpgames: null } }, { sessionToken });
|
||||||
|
|
||||||
|
// No external HTTP calls should have been made during unlink
|
||||||
|
expect(global.fetch.calls.count()).toBe(0);
|
||||||
|
|
||||||
|
// Verify gpgames was removed while the other provider remains
|
||||||
|
await user.fetch({ useMasterKey: true });
|
||||||
|
const authData = user.get('authData');
|
||||||
|
expect(authData).toBeDefined();
|
||||||
|
expect(authData.gpgames).toBeUndefined();
|
||||||
|
expect(authData.otherAdapter).toEqual({ id: 'other1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unlink one code-based provider while echoing back another unchanged', async () => {
|
||||||
|
const gpgamesUserId = 'gpgamesUser1';
|
||||||
|
const instagramUserId = 'igUser1';
|
||||||
|
|
||||||
|
// Mock gpgames API for initial login
|
||||||
|
mockFetch([
|
||||||
|
{
|
||||||
|
url: 'https://oauth2.googleapis.com/token',
|
||||||
|
method: 'POST',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ access_token: 'gpgamesToken' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `https://www.googleapis.com/games/v1/players/${gpgamesUserId}`,
|
||||||
|
method: 'GET',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ playerId: gpgamesUserId }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await reconfigureServer({
|
||||||
|
auth: {
|
||||||
|
gpgames: {
|
||||||
|
clientId: 'testClientId',
|
||||||
|
clientSecret: 'testClientSecret',
|
||||||
|
},
|
||||||
|
instagram: {
|
||||||
|
clientId: 'testClientId',
|
||||||
|
clientSecret: 'testClientSecret',
|
||||||
|
redirectUri: 'https://example.com/callback',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login with gpgames
|
||||||
|
const user = await Parse.User.logInWith('gpgames', {
|
||||||
|
authData: { id: gpgamesUserId, code: 'gpCode1', redirect_uri: 'https://example.com/callback' },
|
||||||
|
});
|
||||||
|
const sessionToken = user.getSessionToken();
|
||||||
|
|
||||||
|
// Mock instagram API for linking
|
||||||
|
mockFetch([
|
||||||
|
{
|
||||||
|
url: 'https://api.instagram.com/oauth/access_token',
|
||||||
|
method: 'POST',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ access_token: 'igToken' }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `https://graph.instagram.com/me?fields=id&access_token=igToken`,
|
||||||
|
method: 'GET',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ id: instagramUserId }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Link instagram as second provider
|
||||||
|
await user.save(
|
||||||
|
{ authData: { instagram: { id: instagramUserId, code: 'igCode1' } } },
|
||||||
|
{ sessionToken }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch to get current authData (afterFind strips credentials, leaving only { id })
|
||||||
|
await user.fetch({ sessionToken });
|
||||||
|
const currentAuthData = user.get('authData');
|
||||||
|
expect(currentAuthData.gpgames).toBeDefined();
|
||||||
|
expect(currentAuthData.instagram).toBeDefined();
|
||||||
|
|
||||||
|
// Reset fetch spy
|
||||||
|
global.fetch.calls.reset();
|
||||||
|
|
||||||
|
// Unlink gpgames while echoing back instagram unchanged — the common client pattern:
|
||||||
|
// fetch current state, spread it, set the one to unlink to null
|
||||||
|
user.set('authData', { ...currentAuthData, gpgames: null });
|
||||||
|
await user.save(null, { sessionToken });
|
||||||
|
|
||||||
|
// No external HTTP calls during unlink (no code exchange for unchanged instagram)
|
||||||
|
expect(global.fetch.calls.count()).toBe(0);
|
||||||
|
|
||||||
|
// Verify gpgames removed, instagram preserved
|
||||||
|
await user.fetch({ useMasterKey: true });
|
||||||
|
const finalAuthData = user.get('authData');
|
||||||
|
expect(finalAuthData).toBeDefined();
|
||||||
|
expect(finalAuthData.gpgames).toBeUndefined();
|
||||||
|
expect(finalAuthData.instagram).toBeDefined();
|
||||||
|
expect(finalAuthData.instagram.id).toBe(instagramUserId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject changing an existing code-based provider id without credentials', async () => {
|
||||||
|
const mockUserId = 'gpgamesUser123';
|
||||||
|
const mockAccessToken = 'mockAccessToken';
|
||||||
|
|
||||||
|
mockFetch([
|
||||||
|
{
|
||||||
|
url: 'https://oauth2.googleapis.com/token',
|
||||||
|
method: 'POST',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ access_token: mockAccessToken }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `https://www.googleapis.com/games/v1/players/${mockUserId}`,
|
||||||
|
method: 'GET',
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve({ playerId: mockUserId }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await reconfigureServer({
|
||||||
|
auth: {
|
||||||
|
gpgames: {
|
||||||
|
clientId: 'testClientId',
|
||||||
|
clientSecret: 'testClientSecret',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sign up and link gpgames with valid credentials
|
||||||
|
const user = new Parse.User();
|
||||||
|
await user.save({
|
||||||
|
authData: {
|
||||||
|
gpgames: { id: mockUserId, code: 'authCode123', redirect_uri: 'https://example.com/callback' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sessionToken = user.getSessionToken();
|
||||||
|
|
||||||
|
// Attempt to change gpgames id without credentials (no code or access_token)
|
||||||
|
await expectAsync(
|
||||||
|
user.save({ authData: { gpgames: { id: 'differentUserId' } } }, { sessionToken })
|
||||||
|
).toBeRejectedWith(
|
||||||
|
jasmine.objectContaining({ message: jasmine.stringContaining('code is required') })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject linking a new code-based provider with only an id and no credentials', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
auth: {
|
||||||
|
gpgames: {
|
||||||
|
clientId: 'testClientId',
|
||||||
|
clientSecret: 'testClientSecret',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sign up with username/password (no gpgames linked)
|
||||||
|
const user = new Parse.User();
|
||||||
|
await user.signUp({ username: 'linkTestUser', password: 'password123' });
|
||||||
|
const sessionToken = user.getSessionToken();
|
||||||
|
|
||||||
|
// Attempt to link gpgames with only { id } — no code or access_token
|
||||||
|
await expectAsync(
|
||||||
|
user.save({ authData: { gpgames: { id: 'victimUserId' } } }, { sessionToken })
|
||||||
|
).toBeRejectedWith(
|
||||||
|
jasmine.objectContaining({ message: jasmine.stringContaining('code is required') })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle multiple providers: add one while another remains unchanged (code-based)', async () => {
|
it('should handle multiple providers: add one while another remains unchanged (code-based)', async () => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
auth: {
|
auth: {
|
||||||
|
|||||||
@@ -72,32 +72,47 @@ export default class BaseAuthCodeAdapter extends AuthAdapter {
|
|||||||
throw new Error('getAccessTokenFromCode is not implemented');
|
throw new Error('getAccessTokenFromCode is not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates auth data on login. In the standard auth flows (login, signup,
|
||||||
|
* update), `beforeFind` runs first and validates credentials, so no
|
||||||
|
* additional credential check is needed here.
|
||||||
|
*/
|
||||||
validateLogin(authData) {
|
validateLogin(authData) {
|
||||||
// User validation is already done in beforeFind
|
|
||||||
return {
|
return {
|
||||||
id: authData.id,
|
id: authData.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates auth data on first setup or when linking a new provider.
|
||||||
|
* In the standard auth flows, `beforeFind` runs first and validates
|
||||||
|
* credentials, so no additional credential check is needed here.
|
||||||
|
*/
|
||||||
validateSetUp(authData) {
|
validateSetUp(authData) {
|
||||||
// User validation is already done in beforeFind
|
|
||||||
return {
|
return {
|
||||||
id: authData.id,
|
id: authData.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the auth data to expose to the client after a query.
|
||||||
|
*/
|
||||||
afterFind(authData) {
|
afterFind(authData) {
|
||||||
return {
|
return {
|
||||||
id: authData.id,
|
id: authData.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates auth data on update. In the standard auth flows, `beforeFind`
|
||||||
|
* runs first for any changed auth data and validates credentials, so no
|
||||||
|
* additional credential check is needed here. Unchanged (echoed-back) data
|
||||||
|
* skips both `beforeFind` and validation entirely.
|
||||||
|
*/
|
||||||
validateUpdate(authData) {
|
validateUpdate(authData) {
|
||||||
// User validation is already done in beforeFind
|
|
||||||
return {
|
return {
|
||||||
id: authData.id,
|
id: authData.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseResponseData(data) {
|
parseResponseData(data) {
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ import linkedin from './linkedin';
|
|||||||
const meetup = require('./meetup');
|
const meetup = require('./meetup');
|
||||||
import mfa from './mfa';
|
import mfa from './mfa';
|
||||||
import microsoft from './microsoft';
|
import microsoft from './microsoft';
|
||||||
const nintendo = require("./nintendo");
|
|
||||||
import oauth2 from './oauth2';
|
import oauth2 from './oauth2';
|
||||||
const phantauth = require('./phantauth');
|
const phantauth = require('./phantauth');
|
||||||
import qq from './qq';
|
import qq from './qq';
|
||||||
import spotify from './spotify';
|
import spotify from './spotify';
|
||||||
const steam = require("./steam");
|
|
||||||
import twitter from './twitter';
|
import twitter from './twitter';
|
||||||
const vkontakte = require('./vkontakte');
|
const vkontakte = require('./vkontakte');
|
||||||
import wechat from './wechat';
|
import wechat from './wechat';
|
||||||
@@ -49,11 +47,9 @@ const providers = {
|
|||||||
linkedin,
|
linkedin,
|
||||||
meetup,
|
meetup,
|
||||||
mfa,
|
mfa,
|
||||||
nintendo,
|
|
||||||
google,
|
google,
|
||||||
github,
|
github,
|
||||||
twitter,
|
twitter,
|
||||||
steam,
|
|
||||||
spotify,
|
spotify,
|
||||||
anonymous,
|
anonymous,
|
||||||
digits,
|
digits,
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
var Parse = require('parse/node').Parse;
|
|
||||||
const { URL } = require('url');
|
|
||||||
var jwt = require('jsonwebtoken');
|
|
||||||
var jwksClient = require('jwks-rsa');
|
|
||||||
|
|
||||||
// Returns a promise that fulfills iff this nsa id token is valid
|
|
||||||
function validateAuthData(authData, authOptions) {
|
|
||||||
//console.log("going to validate for nintendo");
|
|
||||||
//console.log(authData);
|
|
||||||
if ("token" in authData) {
|
|
||||||
try {
|
|
||||||
var token = authData["token"];
|
|
||||||
var decoded = jwt.decode(token, {complete: true});
|
|
||||||
var header = decoded.header;
|
|
||||||
|
|
||||||
// console.log("got nsa id token, header is:");
|
|
||||||
// console.log(header);
|
|
||||||
// console.log("full decoded token is:");
|
|
||||||
// console.log(decoded);
|
|
||||||
|
|
||||||
if (!('alg' in header) || header['alg'] != "RS256") {
|
|
||||||
error("No algorithm specified or it didn't match expected value 'RS256'");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!('kid' in header) || !('jku' in header)) {
|
|
||||||
error("Either 'kid' or 'jku' value not present in token.");
|
|
||||||
}
|
|
||||||
var jwk_name = header['kid'];
|
|
||||||
var jku = header['jku'];
|
|
||||||
|
|
||||||
if (!isValidJKU(jku)) {
|
|
||||||
error("JKU url in token isn't valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
var client = jwksClient({
|
|
||||||
jwksUri: jku
|
|
||||||
});
|
|
||||||
function getKey(header, callback) {
|
|
||||||
client.getSigningKey(header.kid, function (err, key) {
|
|
||||||
var signingKey = key.publicKey || key.rsaPublicKey;
|
|
||||||
callback(null, signingKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var options = {};
|
|
||||||
jwt.verify(token, getKey, options, function(err, decoded) {
|
|
||||||
// console.log("verfied jwt, decoded value is:");
|
|
||||||
// console.log(decoded);
|
|
||||||
if (err != null) {
|
|
||||||
reject("Error verifying jwt: " + err.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!new URL(decoded.iss).hostname.endsWith("nintendo.com")) {
|
|
||||||
reject("iss claim in token is not a nintendo server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var now = Math.floor(Date.now() / 1000);
|
|
||||||
if (Number.parseInt(decoded.iat) > (now + 10000)) {
|
|
||||||
reject("iat value is not in the past");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Number.parseInt(decoded.exp) < (now - 10000)) {
|
|
||||||
reject("exp value is not in the future");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (decoded.nintendo.ai != authOptions.serverId) {
|
|
||||||
reject("application id does not match our id");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(decoded);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
error('Error authenticating NSA id token: ' + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
error('No token found in the request');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// steam auth bundles the app id in the auth data so don't validate seperately
|
|
||||||
function validateAppId() {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidJKU(jku) {
|
|
||||||
// todo - validate this properly?
|
|
||||||
return new URL(jku).hostname.endsWith("nintendo.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(message) {
|
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, message);
|
|
||||||
}
|
|
||||||
module.exports = {
|
|
||||||
validateAppId,
|
|
||||||
validateAuthData
|
|
||||||
};
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* Parse Server authentication adapter for Steam.
|
|
||||||
*
|
|
||||||
* @class SteamAdapter
|
|
||||||
* @param {Object} options - The adapter configuration options.
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* ## Parse Server Configuration
|
|
||||||
* To configure Parse Server for Steam authentication, use the following structure:
|
|
||||||
* ```json
|
|
||||||
* {
|
|
||||||
* "auth": {
|
|
||||||
* "steam": {
|
|
||||||
* "appId": "your-app-id",
|
|
||||||
* "webApiKey": "your-web-api-key"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The adapter requires the following `authData` fields:
|
|
||||||
*
|
|
||||||
* ## Auth Payloads
|
|
||||||
* ```json
|
|
||||||
* {
|
|
||||||
* "steam": {
|
|
||||||
* "??": "??"
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @see {@link https://partner.steamgames.com/doc/api/ISteamUser#GetAuthTicketForWebApi Steam Web API docs}
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
|
||||||
const https = require('https');
|
|
||||||
const querystring = require('querystring');
|
|
||||||
|
|
||||||
|
|
||||||
// Returns a promise that fulfills iff this application ticket is valid
|
|
||||||
function validateAuthData(authData, authOptions) {
|
|
||||||
if ("auth_ticket" in authData) {
|
|
||||||
//console.log("Authenticate steam user using web api and auth ticket");
|
|
||||||
return callSteamWebApi(authData.auth_ticket, authOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// steam auth bundles the app id in the auth data so don't validate seperately
|
|
||||||
function validateAppId() {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
function callSteamWebApi(auth_ticket, authOptions) {
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
// GET parameters
|
|
||||||
const parameters = {
|
|
||||||
key: authOptions.webApiKey,
|
|
||||||
appid: authOptions.appId, // could try the demo id too, but we know that doesn't allow online play so don't worry for now
|
|
||||||
ticket: auth_ticket,
|
|
||||||
identity: authOptions.serverId
|
|
||||||
}
|
|
||||||
|
|
||||||
const get_request_args = querystring.stringify(parameters);
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
host: "partner.steam-api.com",
|
|
||||||
path: "/ISteamUserAuth/AuthenticateUserTicket/v1/?" + get_request_args,
|
|
||||||
headers : {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = https.request(options, (response) => {
|
|
||||||
//console.log("Steam web auth sucess");
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
request.on('error', (error) => {
|
|
||||||
//console.log(error.message);
|
|
||||||
reject('The Steam web api could not authenticate the user with the given auth ticket');
|
|
||||||
});
|
|
||||||
|
|
||||||
request.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
validateAppId,
|
|
||||||
validateAuthData
|
|
||||||
};
|
|
||||||
@@ -1607,20 +1607,28 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
const generate = (jsonb: string, key: string, value: any) => {
|
const generate = (jsonb: string, key: string, value: any) => {
|
||||||
return `json_object_set_key(COALESCE(${jsonb}, '{}'::jsonb), ${key}, ${value})::jsonb`;
|
return `json_object_set_key(COALESCE(${jsonb}, '{}'::jsonb), ${key}, ${value})::jsonb`;
|
||||||
};
|
};
|
||||||
|
const generateRemove = (jsonb: string, key: string) => {
|
||||||
|
return `(COALESCE(${jsonb}, '{}'::jsonb) - ${key})`;
|
||||||
|
};
|
||||||
const lastKey = `$${index}:name`;
|
const lastKey = `$${index}:name`;
|
||||||
const fieldNameIndex = index;
|
const fieldNameIndex = index;
|
||||||
index += 1;
|
index += 1;
|
||||||
values.push(fieldName);
|
values.push(fieldName);
|
||||||
const update = Object.keys(fieldValue).reduce((lastKey: string, key: string) => {
|
const update = Object.keys(fieldValue).reduce((lastKey: string, key: string) => {
|
||||||
|
let value = fieldValue[key];
|
||||||
|
if (value && value.__op === 'Delete') {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
if (value === null) {
|
||||||
|
const str = generateRemove(lastKey, `$${index}::text`);
|
||||||
|
values.push(key);
|
||||||
|
index += 1;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
const str = generate(lastKey, `$${index}::text`, `$${index + 1}::jsonb`);
|
const str = generate(lastKey, `$${index}::text`, `$${index + 1}::jsonb`);
|
||||||
index += 2;
|
index += 2;
|
||||||
let value = fieldValue[key];
|
|
||||||
if (value) {
|
if (value) {
|
||||||
if (value.__op === 'Delete') {
|
value = JSON.stringify(value);
|
||||||
value = null;
|
|
||||||
} else {
|
|
||||||
value = JSON.stringify(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
values.push(key, value);
|
values.push(key, value);
|
||||||
return str;
|
return str;
|
||||||
|
|||||||
18
src/Auth.js
18
src/Auth.js
@@ -417,15 +417,29 @@ Auth.prototype._getAllRolesNamesForRoleIds = function (roleIDs, names = [], quer
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const findUsersWithAuthData = async (config, authData, beforeFind) => {
|
const findUsersWithAuthData = async (config, authData, beforeFind, currentUserAuthData) => {
|
||||||
const providers = Object.keys(authData);
|
const providers = Object.keys(authData);
|
||||||
|
|
||||||
const queries = await Promise.all(
|
const queries = await Promise.all(
|
||||||
providers.map(async provider => {
|
providers.map(async provider => {
|
||||||
const providerAuthData = authData[provider];
|
const providerAuthData = authData[provider];
|
||||||
|
|
||||||
|
// Skip providers being unlinked (null value)
|
||||||
|
if (providerAuthData === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip beforeFind only when incoming data is confirmed unchanged from stored data.
|
||||||
|
// This handles echoed-back authData from afterFind (e.g. client sends back { id: 'x' }
|
||||||
|
// alongside a provider unlink). On login/signup, currentUserAuthData is undefined so
|
||||||
|
// beforeFind always runs, preserving it as the security gate for missing credentials.
|
||||||
|
const storedProviderData = currentUserAuthData?.[provider];
|
||||||
|
const incomingKeys = Object.keys(providerAuthData || {});
|
||||||
|
const isUnchanged = storedProviderData && incomingKeys.length > 0 &&
|
||||||
|
!incomingKeys.some(key => !isDeepStrictEqual(providerAuthData[key], storedProviderData[key]));
|
||||||
|
|
||||||
const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
|
const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
|
||||||
if (beforeFind && typeof adapter?.beforeFind === 'function') {
|
if (beforeFind && typeof adapter?.beforeFind === 'function' && !isUnchanged) {
|
||||||
await adapter.beforeFind(providerAuthData);
|
await adapter.beforeFind(providerAuthData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -541,7 +541,15 @@ RestWrite.prototype.ensureUniqueAuthDataId = async function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RestWrite.prototype.handleAuthData = async function (authData) {
|
RestWrite.prototype.handleAuthData = async function (authData) {
|
||||||
const r = await Auth.findUsersWithAuthData(this.config, authData, true);
|
let currentUserAuthData;
|
||||||
|
if (this.query?.objectId) {
|
||||||
|
const [currentUser] = await this.config.database.find(
|
||||||
|
'_User',
|
||||||
|
{ objectId: this.query.objectId }
|
||||||
|
);
|
||||||
|
currentUserAuthData = currentUser?.authData;
|
||||||
|
}
|
||||||
|
const r = await Auth.findUsersWithAuthData(this.config, authData, true, currentUserAuthData);
|
||||||
const results = this.filteredObjectsByACL(r);
|
const results = this.filteredObjectsByACL(r);
|
||||||
|
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
|
|||||||
Reference in New Issue
Block a user