mirror of
https://github.com/PretendoNetwork/account.git
synced 2026-04-24 23:11:05 -05:00
Merge pull request #194 from PretendoNetwork/feat/mississippi
Mississippi
This commit is contained in:
commit
dc93a750a0
|
|
@ -2,5 +2,7 @@
|
|||
|
||||
Replacement for several account-based services used by the WiiU and 3DS. It replaces the NNID api as well as NASC for the 3DS. It also contains a dedicated PNID api service for getting details of PNIDs outside of the consoles (used by the website)
|
||||
|
||||
Uses the [IP2Location LITE database](https://lite.ip2location.com) for IP geolocation.
|
||||
|
||||
## Setup
|
||||
See [SETUP.md](SETUP.md) for how to self host
|
||||
51
SETUP.md
51
SETUP.md
|
|
@ -64,28 +64,29 @@ The Pretendo Network website uses this server as an API for querying user inform
|
|||
|
||||
Configurations are loaded through environment variables. `.env` files are supported. All configuration options will be gone over, both required and optional. There also exists an example `.env` file
|
||||
|
||||
| Name | Description | Optional |
|
||||
|-----------------------------------------------|--------------------------------------------------------------------------------------------------|----------|
|
||||
| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No |
|
||||
| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No |
|
||||
| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes |
|
||||
| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_SES_REGION` | Amazon SES Region | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_SES_ACCESS_KEY` | Amazon SES Access Key | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_SES_SECRET_KEY` | Amazon SES Access Secret | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes |
|
||||
| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes |
|
||||
| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes |
|
||||
| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes |
|
||||
| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes |
|
||||
| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes |
|
||||
| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes |
|
||||
| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No |
|
||||
| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes |
|
||||
| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No |
|
||||
| `PN_ACT_CONFIG_DATASTORE_SIGNATURE_SECRET` | HMAC secret key (16 bytes in hex format) used to sign uploaded DataStore files | No |
|
||||
| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT` | Master API key to interact with the account gRPC service | No |
|
||||
| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API` | Master API key to interact with the API gRPC service | No |
|
||||
| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No |
|
||||
| `PN_ACT_CONFIG_STRIPE_SECRET_KEY` | Stripe API key. Used to cancel subscriptions when scrubbing PNIDs | Yes |
|
||||
| `PN_ACT_CONFIG_SERVER_ENVIRONMENT` | Server environment. Currently only used by the Wii U Account Settings app. `prod`/`test`/`dev` | Yes |
|
||||
| Name | Description | Optional |
|
||||
| --------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------- |
|
||||
| `PN_ACT_CONFIG_HTTP_PORT` | The HTTP port the server listens on | No |
|
||||
| `PN_ACT_CONFIG_IP2LOCATION_TOKEN` | Download token for https://lite.ip2location.com. Used to download the local IP databases | Yes |
|
||||
| `PN_ACT_CONFIG_MONGO_CONNECTION_STRING` | MongoDB connection string | No |
|
||||
| `PN_ACT_CONFIG_MONGOOSE_CONNECT_OPTIONS_PATH` | Path to a `.json` file containing Mongoose connection options | Yes |
|
||||
| `PN_ACT_CONFIG_REDIS_URL` | Redis URL | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_SES_REGION` | Amazon SES Region | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_SES_ACCESS_KEY` | Amazon SES Access Key | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_SES_SECRET_KEY` | Amazon SES Access Secret | Yes |
|
||||
| `PN_ACT_CONFIG_EMAIL_FROM` | Email "from" address | Yes |
|
||||
| `PN_ACT_CONFIG_S3_ENDPOINT` | s3 server endpoint | Yes |
|
||||
| `PN_ACT_CONFIG_S3_ACCESS_KEY` | s3 secret key | Yes |
|
||||
| `PN_ACT_CONFIG_S3_ACCESS_SECRET` | s3 secret | Yes |
|
||||
| `PN_ACT_CONFIG_HCAPTCHA_SECRET` | hCaptcha secret (in the form `0x...`) | Yes |
|
||||
| `PN_ACT_CONFIG_CDN_SUBDOMAIN` | Subdomain used to serve CDN contents if s3 is disabled | Yes |
|
||||
| `PN_ACT_CONFIG_CDN_DISK_PATH` | File system path used to store CDN contents if s3 is disabled | Yes |
|
||||
| `PN_ACT_CONFIG_CDN_BASE_URL` | URL for serving CDN contents (usually the same as s3 endpoint) | No |
|
||||
| `PN_ACT_CONFIG_WEBSITE_BASE` | Website URL | Yes |
|
||||
| `PN_ACT_CONFIG_AES_KEY` | AES-256 key used for encrypting tokens | No |
|
||||
| `PN_ACT_CONFIG_DATASTORE_SIGNATURE_SECRET` | HMAC secret key (16 bytes in hex format) used to sign uploaded DataStore files | No |
|
||||
| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_ACCOUNT` | Master API key to interact with the account gRPC service | No |
|
||||
| `PN_ACT_CONFIG_GRPC_MASTER_API_KEY_API` | Master API key to interact with the API gRPC service | No |
|
||||
| `PN_ACT_CONFIG_GRPC_PORT` | gRPC server port | No |
|
||||
| `PN_ACT_CONFIG_STRIPE_SECRET_KEY` | Stripe API key. Used to cancel subscriptions when scrubbing PNIDs | Yes |
|
||||
| `PN_ACT_CONFIG_SERVER_ENVIRONMENT` | Server environment. Currently only used by the Wii U Account Settings app. `prod`/`test`/`dev` | Yes |
|
||||
321
package-lock.json
generated
321
package-lock.json
generated
|
|
@ -28,6 +28,7 @@
|
|||
"got": "^11.8.2",
|
||||
"hcaptcha": "^0.1.0",
|
||||
"image-pixels": "^1.1.1",
|
||||
"ip2location-nodejs": "^9.6.3",
|
||||
"is-valid-hostname": "^1.0.2",
|
||||
"joi": "^17.8.3",
|
||||
"mii-js": "github:PretendoNetwork/mii-js#f1741e1f82771dd7c753fd408230373d33caa184",
|
||||
|
|
@ -45,6 +46,7 @@
|
|||
"validator": "^13.7.0",
|
||||
"xmlbuilder": "^13.0.2",
|
||||
"xmlbuilder2": "0.0.4",
|
||||
"yauzl-promise": "^4.0.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -989,7 +991,6 @@
|
|||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
||||
"integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1001,7 +1002,6 @@
|
|||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
|
||||
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1012,7 +1012,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz",
|
||||
"integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1779,7 +1778,6 @@
|
|||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
|
||||
"integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -1788,6 +1786,259 @@
|
|||
"@tybys/wasm-util": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32/-/crc32-1.10.6.tgz",
|
||||
"integrity": "sha512-+llXfqt+UzgoDzT9of5vPQPGqTAVCohU74I9zIBkNo5TH6s2P31DFJOGsJQKN207f0GHnYv5pV3wh3BCY/un/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@node-rs/crc32-android-arm-eabi": "1.10.6",
|
||||
"@node-rs/crc32-android-arm64": "1.10.6",
|
||||
"@node-rs/crc32-darwin-arm64": "1.10.6",
|
||||
"@node-rs/crc32-darwin-x64": "1.10.6",
|
||||
"@node-rs/crc32-freebsd-x64": "1.10.6",
|
||||
"@node-rs/crc32-linux-arm-gnueabihf": "1.10.6",
|
||||
"@node-rs/crc32-linux-arm64-gnu": "1.10.6",
|
||||
"@node-rs/crc32-linux-arm64-musl": "1.10.6",
|
||||
"@node-rs/crc32-linux-x64-gnu": "1.10.6",
|
||||
"@node-rs/crc32-linux-x64-musl": "1.10.6",
|
||||
"@node-rs/crc32-wasm32-wasi": "1.10.6",
|
||||
"@node-rs/crc32-win32-arm64-msvc": "1.10.6",
|
||||
"@node-rs/crc32-win32-ia32-msvc": "1.10.6",
|
||||
"@node-rs/crc32-win32-x64-msvc": "1.10.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-android-arm-eabi": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.10.6.tgz",
|
||||
"integrity": "sha512-vZAMuJXm3TpWPOkkhxdrofWDv+Q+I2oO7ucLRbXyAPmXFNDhHtBxbO1rk9Qzz+M3eep8ieS4/+jCL1Q0zacNMQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-android-arm64": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.10.6.tgz",
|
||||
"integrity": "sha512-Vl/JbjCinCw/H9gEpZveWCMjxjcEChDcDBM8S4hKay5yyoRCUHJPuKr4sjVDBeOm+1nwU3oOm6Ca8dyblwp4/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-darwin-arm64": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.6.tgz",
|
||||
"integrity": "sha512-kARYANp5GnmsQiViA5Qu74weYQ3phOHSYQf0G+U5wB3NB5JmBHnZcOc46Ig21tTypWtdv7u63TaltJQE41noyg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-darwin-x64": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.6.tgz",
|
||||
"integrity": "sha512-Q99bevJVMfLTISpkpKBlXgtPUItrvTWKFyiqoKH5IvscZmLV++NH4V13Pa17GTBmv9n18OwzgQY4/SRq6PQNVA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-freebsd-x64": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.10.6.tgz",
|
||||
"integrity": "sha512-66hpawbNjrgnS9EDMErta/lpaqOMrL6a6ee+nlI2viduVOmRZWm9Rg9XdGTK/+c4bQLdtC6jOd+Kp4EyGRYkAg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-linux-arm-gnueabihf": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.10.6.tgz",
|
||||
"integrity": "sha512-E8Z0WChH7X6ankbVm8J/Yym19Cq3otx6l4NFPS6JW/cWdjv7iw+Sps2huSug+TBprjbcEA+s4TvEwfDI1KScjg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-linux-arm64-gnu": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.10.6.tgz",
|
||||
"integrity": "sha512-LmWcfDbqAvypX0bQjQVPmQGazh4dLiVklkgHxpV4P0TcQ1DT86H/SWpMBMs/ncF8DGuCQ05cNyMv1iddUDugoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-linux-arm64-musl": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.10.6.tgz",
|
||||
"integrity": "sha512-k8ra/bmg0hwRrIEE8JL1p32WfaN9gDlUUpQRWsbxd1WhjqvXea7kKO6K4DwVxyxlPhBS9Gkb5Urq7Y4mXANzaw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-linux-x64-gnu": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.10.6.tgz",
|
||||
"integrity": "sha512-IfjtqcuFK7JrSZ9mlAFhb83xgium30PguvRjIMI45C3FJwu18bnLk1oR619IYb/zetQT82MObgmqfKOtgemEKw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-linux-x64-musl": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-musl/-/crc32-linux-x64-musl-1.10.6.tgz",
|
||||
"integrity": "sha512-LbFYsA5M9pNunOweSt6uhxenYQF94v3bHDAQRPTQ3rnjn+mK6IC7YTAYoBjvoJP8lVzcvk9hRj8wp4Jyh6Y80g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-wasm32-wasi": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-wasm32-wasi/-/crc32-wasm32-wasi-1.10.6.tgz",
|
||||
"integrity": "sha512-KaejdLgHMPsRaxnM+OG9L9XdWL2TabNx80HLdsCOoX9BVhEkfh39OeahBo8lBmidylKbLGMQoGfIKDjq0YMStw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@napi-rs/wasm-runtime": "^0.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-win32-arm64-msvc": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.10.6.tgz",
|
||||
"integrity": "sha512-x50AXiSxn5Ccn+dCjLf1T7ZpdBiV1Sp5aC+H2ijhJO4alwznvXgWbopPRVhbp2nj0i+Gb6kkDUEyU+508KAdGQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-win32-ia32-msvc": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.10.6.tgz",
|
||||
"integrity": "sha512-DpDxQLaErJF9l36aghe1Mx+cOnYLKYo6qVPqPL9ukJ5rAGLtCdU0C+Zoi3gs9ySm8zmbFgazq/LvmsZYU42aBw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@node-rs/crc32-win32-x64-msvc": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.10.6.tgz",
|
||||
"integrity": "sha512-5B1vXosIIBw1m2Rcnw62IIfH7W9s9f7H7Ma0rRuhT8HR4Xh8QCgw6NJSI2S2MCngsGktYnAhyUvs81b7efTyQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
@ -2924,7 +3175,6 @@
|
|||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
|
||||
"integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -4795,6 +5045,18 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csv-parser": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz",
|
||||
"integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"csv-parser": "bin/csv-parser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/d": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
|
||||
|
|
@ -4943,7 +5205,6 @@
|
|||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
|
|
@ -4961,7 +5222,6 @@
|
|||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.0.1",
|
||||
|
|
@ -6628,7 +6888,6 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
|
||||
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-properties": "^1.2.1",
|
||||
|
|
@ -6749,7 +7008,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
|
|
@ -7036,6 +7294,15 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/ip2location-nodejs": {
|
||||
"version": "9.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ip2location-nodejs/-/ip2location-nodejs-9.6.3.tgz",
|
||||
"integrity": "sha512-npgq6Dwk0G53GKbxUCzZvosr0KPfreufohFKSzM7vAGtbmpuO2KwULQb5AxkCtlBIZ7xFShri7R1iVUbe7BKTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csv-parser": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
|
@ -7303,6 +7570,18 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-it-type": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/is-it-type/-/is-it-type-5.1.3.tgz",
|
||||
"integrity": "sha512-AX2uU0HW+TxagTgQXOJY7+2fbFHemC7YFBwN1XqD8qQMKdtfbOC8OC3fUb4s5NU59a3662Dzwto8tWDdZYRXxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"globalthis": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
||||
|
|
@ -8461,7 +8740,6 @@
|
|||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -9575,6 +9853,15 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-invariant": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-invariant/-/simple-invariant-2.0.1.tgz",
|
||||
"integrity": "sha512-1sbhsxqI+I2tqlmjbz99GXNmZtr6tKIyEgGGnJw/MKGblalqk/XoOYYFJlBzTKZCxx8kLaD3FD5s9BEEjx5Pyg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
|
|
@ -10817,6 +11104,20 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yauzl-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl-promise/-/yauzl-promise-4.0.0.tgz",
|
||||
"integrity": "sha512-/HCXpyHXJQQHvFq9noqrjfa/WpQC2XYs3vI7tBiAi4QiIU1knvYhZGaO1QPjwIVMdqflxbmwgMXtYeaRiAE0CA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@node-rs/crc32": "^1.7.0",
|
||||
"is-it-type": "^5.1.2",
|
||||
"simple-invariant": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"build": "npm run lint && npm run clean && npx tsc && npx tsc-alias && npm run copy-static",
|
||||
"build": "npm run lint && npm run clean && npx tsc && npx tsc-alias && npm run copy-static && node ./scripts/download-ip2location-databases.js",
|
||||
"clean": "rimraf ./dist",
|
||||
"copy-static": "copyfiles -e \"src/**/*.ts\" -u 1 \"src/**/*\" dist",
|
||||
"start": "node .",
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
"got": "^11.8.2",
|
||||
"hcaptcha": "^0.1.0",
|
||||
"image-pixels": "^1.1.1",
|
||||
"ip2location-nodejs": "^9.6.3",
|
||||
"is-valid-hostname": "^1.0.2",
|
||||
"joi": "^17.8.3",
|
||||
"mii-js": "github:PretendoNetwork/mii-js#f1741e1f82771dd7c753fd408230373d33caa184",
|
||||
|
|
@ -60,6 +61,7 @@
|
|||
"validator": "^13.7.0",
|
||||
"xmlbuilder": "^13.0.2",
|
||||
"xmlbuilder2": "0.0.4",
|
||||
"yauzl-promise": "^4.0.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -85,4 +87,4 @@
|
|||
"ndarray": "^1.0.19",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
scripts/download-ip2location-databases.js
Normal file
47
scripts/download-ip2location-databases.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const { pipeline } = require('node:stream/promises');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const yauzl = require('yauzl-promise');
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const databases = {
|
||||
DB3LITEBIN: {
|
||||
file_name: 'IP2LOCATION-LITE-DB3.BIN',
|
||||
save_path: path.join(__dirname, '..', 'dist', 'IP2LOCATION-LITE-DB3.IPV4.BIN')
|
||||
},
|
||||
DB3LITEBINIPV6: {
|
||||
file_name: 'IP2LOCATION-LITE-DB3.IPV6.BIN',
|
||||
save_path: path.join(__dirname, '..', 'dist', 'IP2LOCATION-LITE-DB3.IPV6.BIN')
|
||||
}
|
||||
};
|
||||
|
||||
async function main() {
|
||||
if (!process.env.PN_ACT_CONFIG_IP2LOCATION_TOKEN) {
|
||||
// * Optional
|
||||
return;
|
||||
}
|
||||
|
||||
for (const name in databases) {
|
||||
const database = databases[name];
|
||||
const response = await fetch(`https://www.ip2location.com/download/?token=${process.env.PN_ACT_CONFIG_IP2LOCATION_TOKEN}&file=${name}`);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
const zip = await yauzl.fromBuffer(buffer);
|
||||
|
||||
try {
|
||||
for await (const entry of zip) {
|
||||
if (entry.filename === database.file_name) {
|
||||
const readStream = await entry.openReadStream();
|
||||
const writeStream = fs.createWriteStream(database.save_path);
|
||||
|
||||
await pipeline(readStream, writeStream);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error downloading IP2Location databases:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
55
src/ip2location.ts
Normal file
55
src/ip2location.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import path from 'node:path';
|
||||
import net from 'node:net';
|
||||
import fs from 'fs-extra';
|
||||
import * as IP2Location from 'ip2location-nodejs';
|
||||
import { LOG_WARN } from '@/logger';
|
||||
|
||||
class IP2LocationManager {
|
||||
private ipv4?: IP2Location.IP2Location;
|
||||
private ipv6?: IP2Location.IP2Location;
|
||||
|
||||
constructor() {
|
||||
const ipv4Path = path.join(__dirname, 'IP2LOCATION-LITE-DB3.IPV4.BIN');
|
||||
const ipv6Path = path.join(__dirname, 'IP2LOCATION-LITE-DB3.IPV6.BIN');
|
||||
|
||||
if (!fs.existsSync(ipv4Path)) {
|
||||
LOG_WARN('Could not find IP2LOCATION-LITE-DB3.IPV4.BIN. IP location checking disabled. To enable, run `node scripts/download-ip2location-databases.js` and restart the server.');
|
||||
} else {
|
||||
this.ipv4 = new IP2Location.IP2Location();
|
||||
this.ipv4.open(ipv4Path);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(ipv6Path)) {
|
||||
LOG_WARN('Could not find IP2LOCATION-LITE-DB3.IPV6.BIN. IP location checking disabled. To enable, run `node scripts/download-ip2location-databases.js` and restart the server.');
|
||||
} else {
|
||||
this.ipv6 = new IP2Location.IP2Location();
|
||||
this.ipv6.open(ipv6Path);
|
||||
}
|
||||
}
|
||||
|
||||
public lookup(ip: string): { country: string; region: string } | null {
|
||||
if (!this.ipv4 || !this.ipv6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ipVersion = net.isIP(ip);
|
||||
let result;
|
||||
|
||||
if (ipVersion === 4) {
|
||||
result = this.ipv4.getAll(ip);
|
||||
} else if (ipVersion === 6) {
|
||||
result = this.ipv6.getAll(ip);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
country: result.countryShort,
|
||||
region: result.region
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const manager = new IP2LocationManager();
|
||||
|
||||
export default manager;
|
||||
|
|
@ -31,6 +31,7 @@ const app = express();
|
|||
// * START APPLICATION
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('trust proxy', true); // TODO - Make this configurable
|
||||
|
||||
// * Create router
|
||||
LOG_INFO('Setting up Middleware');
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import moment from 'moment';
|
|||
import hcaptcha from 'hcaptcha';
|
||||
import Mii from 'mii-js';
|
||||
import { doesPNIDExist, connection as databaseConnection } from '@/database';
|
||||
import { nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util';
|
||||
import { isValidBirthday, getAgeFromDate, nintendoPasswordHash, sendConfirmationEmail, generateToken } from '@/util';
|
||||
import IP2LocationManager from '@/ip2location';
|
||||
import { SystemType } from '@/types/common/system-types';
|
||||
import { TokenType } from '@/types/common/token-types';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
|
|
@ -37,6 +38,8 @@ const DEFAULT_MII_DATA = Buffer.from('AwAAQOlVognnx0GC2/uogAOzuI0n2QAAAEBEAGUAZg
|
|||
* Description: Creates a new user PNID
|
||||
*/
|
||||
router.post('/', async (request: express.Request, response: express.Response): Promise<void> => {
|
||||
const clientIP = request.body.ip?.trim(); // * This has to be forwarded since this request comes from the websites server
|
||||
const birthday = request.body.birthday?.trim();
|
||||
const email = request.body.email?.trim();
|
||||
const username = request.body.username?.trim();
|
||||
const miiName = request.body.mii_name?.trim();
|
||||
|
|
@ -68,6 +71,53 @@ router.post('/', async (request: express.Request, response: express.Response): P
|
|||
}
|
||||
}
|
||||
|
||||
if (!clientIP || clientIP === '') {
|
||||
response.status(400).json({
|
||||
app: 'api',
|
||||
status: 400,
|
||||
error: 'IP must be forwarded to check local laws'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!birthday || birthday === '') {
|
||||
response.status(400).json({
|
||||
app: 'api',
|
||||
status: 400,
|
||||
error: 'Birthday must be set'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidBirthday(birthday)) {
|
||||
response.status(400).json({
|
||||
app: 'api',
|
||||
status: 400,
|
||||
error: 'Birthday must be a valid date'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const age = getAgeFromDate(birthday);
|
||||
|
||||
if (age < 18) {
|
||||
// TODO - Enable `CF-IPCountry` in Cloudflare and only use IP2Location as a fallback
|
||||
const location = IP2LocationManager.lookup(clientIP);
|
||||
if (location?.country === 'US' && location?.region === 'Mississippi') {
|
||||
// * See https://bsky.social/about/blog/08-22-2025-mississippi-hb1126 for details
|
||||
response.status(403).json({
|
||||
app: 'api',
|
||||
status: 403,
|
||||
error: 'Mississippi law prevents us from collecting any data from any users under the age of 18 without extreme parental verification methods.' // TODO - Expand on this and translate it? this will be shown on the website
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!email || email === '') {
|
||||
response.status(400).json({
|
||||
app: 'api',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import moment from 'moment';
|
|||
import deviceCertificateMiddleware from '@/middleware/device-certificate';
|
||||
import ratelimit from '@/middleware/ratelimit';
|
||||
import { connection as databaseConnection, doesPNIDExist, getPNIDProfileJSONByPID } from '@/database';
|
||||
import { getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail, sendPNIDDeletedEmail } from '@/util';
|
||||
import { getAgeFromDate, getValueFromHeaders, nintendoPasswordHash, sendConfirmationEmail, sendPNIDDeletedEmail } from '@/util';
|
||||
import IP2LocationManager from '@/ip2location';
|
||||
import { PNID } from '@/models/pnid';
|
||||
import { NEXAccount } from '@/models/nex-account';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
|
|
@ -63,6 +64,28 @@ router.post('/', ratelimit, deviceCertificateMiddleware, async (request: express
|
|||
}
|
||||
|
||||
const person: Person = request.body.person;
|
||||
const age = getAgeFromDate(person.birth_date);
|
||||
|
||||
if (age < 18) {
|
||||
// TODO - Enable `CF-IPCountry` in Cloudflare and only use IP2Location as a fallback
|
||||
const ip = request.ip;
|
||||
if (ip) {
|
||||
const location = IP2LocationManager.lookup(ip);
|
||||
if (location?.country === 'US' && location?.region === 'Mississippi') {
|
||||
// * See https://bsky.social/about/blog/08-22-2025-mississippi-hb1126 for details
|
||||
response.status(403).send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
code: '1228', // TODO - This is made up because 228 is a Mississippi area code /shrug
|
||||
message: 'Mississippi law prevents us from collecting any data from any users under the age of 18 without extreme parental verification methods.' // TODO - Translate this? It wont show to end users so maybe not though
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userExists = await doesPNIDExist(person.user_id);
|
||||
|
||||
|
|
|
|||
46
src/util.ts
46
src/util.ts
|
|
@ -338,3 +338,49 @@ export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string):
|
|||
export function mapToObject(map: Map<any, any>): object {
|
||||
return Object.fromEntries(Array.from(map.entries(), ([k, v]) => v instanceof Map ? [k, mapToObject(v)] : [k, v]));
|
||||
}
|
||||
|
||||
export function isValidBirthday(dateString: string): boolean {
|
||||
// * Birthdays MUST be in the format YYYY-MM-DD. This is how the
|
||||
// * console sends them, regardless of region
|
||||
|
||||
// * Make sure general format is right
|
||||
const regex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
if (!regex.test(dateString)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// * Actually check that it's a valid date
|
||||
const parts = dateString.split('-');
|
||||
const year = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10);
|
||||
const day = parseInt(parts[2], 10);
|
||||
|
||||
const date = new Date(year, month - 1, day);
|
||||
|
||||
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
||||
}
|
||||
|
||||
export function getAgeFromDate(dateString: string): number {
|
||||
if (!isValidBirthday(dateString)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const parts = dateString.split('-');
|
||||
const birthYear = parseInt(parts[0], 10);
|
||||
const birthMonth = parseInt(parts[1], 10);
|
||||
const birthDay = parseInt(parts[2], 10);
|
||||
|
||||
const today = new Date();
|
||||
const currentYear = today.getFullYear();
|
||||
const currentMonth = today.getMonth() + 1;
|
||||
const currentDay = today.getDate();
|
||||
|
||||
let age = currentYear - birthYear;
|
||||
|
||||
// * Check if birthday has actually happened this year yet
|
||||
if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) {
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user