Add project files.
This commit is contained in:
commit
0a12c6baa0
41 changed files with 2698 additions and 0 deletions
83
.env
Normal file
83
.env
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
##################################
|
||||||
|
# API CONFIGURATION
|
||||||
|
##################################
|
||||||
|
|
||||||
|
# Set the port for receiving connections
|
||||||
|
PORT=3000
|
||||||
|
AUTH_PORT=8070
|
||||||
|
BILLING_PORT=8080
|
||||||
|
|
||||||
|
Determines whether the helmet middleware is enabled or disabled. If enabled https need to be used for the api.
|
||||||
|
If set to true, the helmet middleware is included in the middleware stack, which adds various security-related HTTP headers to the application's responses to help prevent common web vulnerabilities.
|
||||||
|
If set to false, the helmet middleware is not included in the middleware stack, and the application's responses will not have these extra headers.
|
||||||
|
ENABLE_HELMET=false
|
||||||
|
|
||||||
|
# Set the server timezone
|
||||||
|
TZ=America/New_York
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# LOGGING CONFIGURATION #
|
||||||
|
##################################
|
||||||
|
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
LOG_AUTH_CONSOLE=true
|
||||||
|
LOG_BILLING_CONSOLE=true
|
||||||
|
LOG_ACCOUNT_CONSOLE=false
|
||||||
|
LOG_MAILER_CONSOLE=false
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# API DATABASE CONFIGURATION #
|
||||||
|
##################################
|
||||||
|
|
||||||
|
# Set a host to connect to the SQL server database.
|
||||||
|
DB_SERVER=127.0.0.1
|
||||||
|
|
||||||
|
# Set the name of database
|
||||||
|
DB_DATABASE=RustyHearts_Account
|
||||||
|
|
||||||
|
# Set the user to connect to database
|
||||||
|
DB_USER=sa
|
||||||
|
|
||||||
|
# Set the password to connect to database
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
Set to encrypt the connection to the database
|
||||||
|
DB_ENCRYPT=false
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# GATEWAY API CONFIGURATION #
|
||||||
|
##################################
|
||||||
|
|
||||||
|
# Set the host for receiving connections to the gateserver
|
||||||
|
GATESERVER_IP=YOUR_SERVER_IP
|
||||||
|
|
||||||
|
# Set the port for receiving connections to the gateserver
|
||||||
|
GATESERVER_PORT=50001
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# EMAIL CONFIGURATION #
|
||||||
|
##################################
|
||||||
|
using gmail smtp server
|
||||||
|
|
||||||
|
# To generate app passwords, first you have to enable 2-Step Verification on our Google account.
|
||||||
|
# Go to your Google account security settings (https://myaccount.google.com/security) and enable 2-Step Verification
|
||||||
|
# Now, you can select the App passwords option to set up a new app password. https://myaccount.google.com/u/2/apppasswords
|
||||||
|
|
||||||
|
The hostname or IP address of the SMTP server
|
||||||
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
|
||||||
|
The port number of the SMTP server
|
||||||
|
SMTP_PORT=465
|
||||||
|
|
||||||
|
The encryption protocol to use (e.g. ssl, tls)
|
||||||
|
SMTP_ENCRYPTION=ssl
|
||||||
|
|
||||||
|
# your gmail
|
||||||
|
SMTP_USERNAME=your.email@gmail.com
|
||||||
|
|
||||||
|
# app password
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
|
||||||
|
The name to use as the sender in emails
|
||||||
|
SMTP_FROMNAME=Rusty Hearts
|
||||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [JuniorDark]
|
||||||
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- directory: /
|
||||||
|
package-ecosystem: github-actions
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
labels: []
|
||||||
|
- directory: /
|
||||||
|
package-ecosystem: npm
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
labels: []
|
||||||
130
.gitignore
vendored
Normal file
130
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
#.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
12
LICENSE
Normal file
12
LICENSE
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
BSD Zero Clause License (0BSD)
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||||
|
with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
150
README.md
Normal file
150
README.md
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
# RustyHearts-API
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
RustyHearts-API is a Node.js-based REST API that enables authentication, billing, and launcher functionalities for Rusty Hearts.
|
||||||
|
|
||||||
|
The API consists of three independent servers (Auth API, Billing API and Launcher API) running on different ports.
|
||||||
|
|
||||||
|
### API region support
|
||||||
|
The api currently only support the **usa** (PWE) region.
|
||||||
|
|
||||||
|
* **usa** (PWE) - Full api support
|
||||||
|
* **chn** (Xunlei) - Only launcher support
|
||||||
|
|
||||||
|
## Server Descriptions
|
||||||
|
|
||||||
|
- The Auth API is responsible for in-game authentication, while the Billing API manages the shop's zen balance and purchases. It is essential to bind the Auth/Billing API only to a local IP address and prevent external access to these APIs.
|
||||||
|
- The Launcher API is a web server intended to handle the client connection to the gateserver and for the [Rusty Hearts Launcher](https://github.com/JuniorDark/RustyHearts-Launcher), which handles registration, login, client updates, and processing static elements (public directory). This API must be accessible from the outside and proxied by Nginx or bound to an external IP.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
* [Public folder](#public-folder)
|
||||||
|
* [Requirements](#requirements)
|
||||||
|
* [Deployment](#deployment)
|
||||||
|
* [Basic Installation](#basic-installation)
|
||||||
|
* [.env file setup](#env-file-setup)
|
||||||
|
* [Available endpoints](#available-endpoints)
|
||||||
|
* [Preview](#preview)
|
||||||
|
* [License](#license)
|
||||||
|
* [Contributing](#contributing)
|
||||||
|
* [FAQ](#faq)
|
||||||
|
* [Support](#support)
|
||||||
|
|
||||||
|
## Public folder description
|
||||||
|
|
||||||
|
### Launcher self-update
|
||||||
|
|
||||||
|
In order for the launcher to automatically update itself, you need to use the launcher_info.ini in the `launcher_update` directory of the api. This file specifies the version of the launcher. After each update of the launcher, you need to change the version in the ini, as well in the launcher executable file.
|
||||||
|
|
||||||
|
### Client patch
|
||||||
|
|
||||||
|
In order to create client patches, you need to use the `patch` directory of the api.
|
||||||
|
The tool for creating the patch files is available in the repository: https://github.com/JuniorDark/RustyHearts-MIPTool
|
||||||
|
|
||||||
|
### News panel
|
||||||
|
Used to use the html page displayed in the launcher, uses the `news` directory of the api
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Before deploying RustyHearts-API, ensure that you have the following software installed:
|
||||||
|
|
||||||
|
* [Node.js](https://nodejs.org/en/) version 18.5.0 or higher
|
||||||
|
* [Microsoft SQL Server](https://go.microsoft.com/fwlink/p/?linkid=2215158) version 2019 or 2022 Developer edition
|
||||||
|
* [Rusty Hearts Retail Server](https://forum.ragezone.com)
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
To deploy RustyHearts-API, follow these steps:
|
||||||
|
|
||||||
|
### Basic Installation
|
||||||
|
|
||||||
|
1. Install the latest version of Node.js from the [official website](https://nodejs.org/).
|
||||||
|
2. Copy all RustyHearts-API files to a directory of your choice (e.g., **c:\RustyHearts-API**).
|
||||||
|
3. Open a terminal window, navigate to the RustyHearts-API directory, and execute the `npm install` command. Alternatively, you can run the **install.bat** file.
|
||||||
|
4. Import the [database file](share/RustyHearts_Account.sql) to your Microsoft SQL Server.
|
||||||
|
5. Configure the parameters in the **.env** file.
|
||||||
|
6. Start RustyHearts-API servers by executing the `node src/app` command or running the **rh-api.bat** file.
|
||||||
|
7. The server region must be set to **usa** on [service_control.xml](share/service_control.xml)
|
||||||
|
|
||||||
|
## .env file setup:
|
||||||
|
|
||||||
|
### API CONFIGURATION
|
||||||
|
|
||||||
|
- **PORT**: The port number for receiving connections (default 3000).
|
||||||
|
- **AUTH_PORT**: The port number for the Auth API.
|
||||||
|
- **BILLING_PORT**: The port number for the Billing API.
|
||||||
|
- **ENABLE_HELMET**: Determines whether the helmet middleware is enabled or disabled. If enabled, https need to be used for the api.
|
||||||
|
- **TZ**: The timezone for the server.
|
||||||
|
|
||||||
|
### LOGGING CONFIGURATION
|
||||||
|
|
||||||
|
- **LOG_LEVEL**: The level of logging to use (e.g. debug, info, warn, error).
|
||||||
|
- **LOG_AUTH_CONSOLE**: Whether to log Auth API messages to the console.
|
||||||
|
- **LOG_BILLING_CONSOLE**: Whether to log Billing API messages to the console.
|
||||||
|
- **LOG_ACCOUNT_CONSOLE**: Whether to log Account API messages to the console.
|
||||||
|
- **LOG_MAILER_CONSOLE**: Whether to log email messages to the console.
|
||||||
|
|
||||||
|
### DATABASE CONFIGURATION
|
||||||
|
|
||||||
|
- **DB_SERVER**: The IP address or hostname of the SQL Server.
|
||||||
|
- **DB_DATABASE**: The name of the database to connect to (RustyHearts_Account).
|
||||||
|
- **DB_USER**: The user to connect to the database.
|
||||||
|
- **DB_PASSWORD**: The password for the database user.
|
||||||
|
- **DB_ENCRYPT**: Whether to encrypt the connection to the database.
|
||||||
|
|
||||||
|
### GATEWAY API CONFIGURATION
|
||||||
|
|
||||||
|
- **GATESERVER_IP**: The IP address of the gate server.
|
||||||
|
- **GATESERVER_PORT**: The port number of the gate server.
|
||||||
|
|
||||||
|
### EMAIL CONFIGURATION
|
||||||
|
|
||||||
|
- **SMTP_HOST**: The hostname or IP address of the SMTP server.
|
||||||
|
- **SMTP_PORT**: The port number of the SMTP server.
|
||||||
|
- **SMTP_ENCRYPTION**: The encryption protocol to use (e.g. ssl, tls).
|
||||||
|
- **SMTP_USERNAME**: The username for the SMTP server.
|
||||||
|
- **SMTP_PASSWORD**: The password for the SMTP server.
|
||||||
|
- **SMTP_FROMNAME**: The name to use as the sender in emails.
|
||||||
|
|
||||||
|
## Available endpoints
|
||||||
|
|
||||||
|
The api provides the following endpoints:
|
||||||
|
|
||||||
|
Endpoint | Method | Arguments | Description
|
||||||
|
--- | --- | --- | ---
|
||||||
|
/serverApi/auth | POST | XML with account, password, game and IP | Authenticates a user based on their account information and sends an XML response with their user ID, user type, and success status. If authentication fails, it sends an XML response with a failure status.
|
||||||
|
/serverApi/billing | POST | XML with currency-request or item-purchase-request and associated arguments | Handles billing requests. For currency requests, it retrieves the user's Zen balance from the database and sends an XML response with the balance. For item purchase requests, it deducts the cost of the item from the user's Zen balance and logs the transaction in the database. If the transaction is successful, it sends an XML response with the success status. If the transaction fails, it sends an XML response with a failure status and an error message.
|
||||||
|
/serverApi/gateway | GET | | Returns an XML response containing the IP address and port number of the gateway server.
|
||||||
|
/serverApi/gateway/status | GET | | Checks the status of the gateway server by attempting to establish a connection to the server. Returns a JSON object with the status of the server (online or offline) and an HTTP status code indicating the success or failure of the connection attempt.
|
||||||
|
/accountApi/register | POST | windyCode, email, password | Create a new account with the provided windyCode, email, and password. The password is first combined with the windyCode to create an MD5 hash, which is then salted and hashed again using bcrypt before being stored in the database. An email confirmation is sent to the provided email address, and a success or error message is returned.
|
||||||
|
/accountApi/login | POST | account, password | Authenticates a user account by username or email address and password. Return a token if the authentication is successful (unsued).
|
||||||
|
/accountApi/codeVerification | POST | email, verification_code_type, verification_code | Verify a user's email by checking the verification code
|
||||||
|
/accountApi/sendPasswordResetEmail | POST | email | Sends an email with a password reset verification code to the specified email address
|
||||||
|
/accountApi/changePassword | POST | email, password, verification_code | Change the password of a user's account, given the email and password verification code
|
||||||
|
/accountApi/sendVerificationEmail | POST | email | Sends a verification email to the specified email address.
|
||||||
|
/launcherApi/launcherUpdater/getLauncherVersion | GET | | Returns the version of the launcher by reading the launcher_info.ini file.
|
||||||
|
/launcherApi/launcherUpdater/updateLauncherVersion | POST | version | Downloads the new version of the launcher from the launcher_update folder.
|
||||||
|
|
||||||
|
### Preview
|
||||||
|

|
||||||
|
|
||||||
|
## License
|
||||||
|
This project is licensed under the terms found in [`LICENSE-0BSD`](LICENSE).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
Contributions from the community are welcome! If you encounter a bug or have a feature request, please submit an issue on GitHub. If you would like to contribute code, please fork the repository and submit a pull request.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
* Q: How do I report a bug?
|
||||||
|
* A: Please submit an issue on GitHub with a detailed description of the bug and steps to reproduce it.
|
||||||
|
* Q: How do I request a new feature?
|
||||||
|
* A: Please submit an issue on GitHub with a detailed description of the feature and why it would be useful.
|
||||||
|
* Q: How do I contribute code?
|
||||||
|
* A: Please fork the repository, make your changes, and submit a pull request.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
If you need help with the api, please submit an issue on GitHub.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
* Add support for client download/repair
|
||||||
|
* Improve performance and stability
|
||||||
|
* Add support for other regions
|
||||||
BIN
api.png
Normal file
BIN
api.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
4
install.bat
Normal file
4
install.bat
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
@echo off
|
||||||
|
cd .
|
||||||
|
npm install
|
||||||
|
pause
|
||||||
46
package.json
Normal file
46
package.json
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "RustyHearts-API",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Rusty Hearts REST API implementation on node.js",
|
||||||
|
"main": "src/app.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/app"
|
||||||
|
},
|
||||||
|
"author": "JuniorDark",
|
||||||
|
"keywords": [
|
||||||
|
"api",
|
||||||
|
"rustyhearts",
|
||||||
|
"launcher"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/JuniorDark/RustyHearts-API",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/JuniorDark/RustyHearts-API/RustyHearts-API.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/JuniorDark/RustyHearts-API/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^5.1.0",
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-rate-limit": "^6.7.0",
|
||||||
|
"express-validator": "^7.0.1",
|
||||||
|
"express-winston": "^4.2.0",
|
||||||
|
"handlebars": "^4.7.7",
|
||||||
|
"helmet": "^7.0.0",
|
||||||
|
"joi": "^17.9.2",
|
||||||
|
"logger": "^0.0.1",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
|
"mssql": "^9.1.1",
|
||||||
|
"nodemailer": "^6.9.1",
|
||||||
|
"winston": "^3.8.2",
|
||||||
|
"xml2js": "^0.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.15.0"
|
||||||
|
},
|
||||||
|
"license": "0BSD"
|
||||||
|
}
|
||||||
0
public/launcher/launcher_update/launcher_1.0.0.zip.txt
Normal file
0
public/launcher/launcher_update/launcher_1.0.0.zip.txt
Normal file
2
public/launcher/launcher_update/launcher_info.ini
Normal file
2
public/launcher/launcher_update/launcher_info.ini
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[LAUNCHER]
|
||||||
|
version=1.0.0
|
||||||
BIN
public/launcher/news/favicon.ico
Normal file
BIN
public/launcher/news/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
public/launcher/news/images/lb_cashshop_banner07.png
Normal file
BIN
public/launcher/news/images/lb_cashshop_banner07.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 307 KiB |
BIN
public/launcher/news/images/lb_cashshop_banner08.png
Normal file
BIN
public/launcher/news/images/lb_cashshop_banner08.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
BIN
public/launcher/news/images/lb_cashshop_banner09.png
Normal file
BIN
public/launcher/news/images/lb_cashshop_banner09.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 311 KiB |
47
public/launcher/news/news-panel.html
Normal file
47
public/launcher/news/news-panel.html
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>News Panel</title>
|
||||||
|
<link rel="stylesheet" href="/launcher/news/style.css">
|
||||||
|
</head>
|
||||||
|
<body oncontextmenu="return false;">
|
||||||
|
<div class="slider-container">
|
||||||
|
<div class="slider">
|
||||||
|
<img src="/launcher/news/images/lb_cashshop_banner07.png?text=Halloween" alt="Halloween" onclick="location.href='https://your-website.com/news/Halloween.html';">
|
||||||
|
<img src="/launcher/news/images/lb_cashshop_banner08.png?text=Winter" alt="Winter" onclick="location.href='https://your-website.com/news/Winter.html';">
|
||||||
|
<img src="/launcher/news/images/lb_cashshop_banner09.png?text=Happy New Year" alt="Happy New Year" onclick="location.href='https://your-website.com/news/Happy_New_Year.html';">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="slider-arrow slider-arrow-left"><</div>
|
||||||
|
<div class="slider-arrow slider-arrow-right">></div>
|
||||||
|
<div class="slider-dots">
|
||||||
|
<div class="slider-dot active"></div>
|
||||||
|
<div class="slider-dot"></div>
|
||||||
|
<div class="slider-dot"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-links">
|
||||||
|
<div class="tab-link active" data-tab="events">Events</div>
|
||||||
|
<div class="tab-link" data-tab="notices">Notices</div>
|
||||||
|
<div class="tab-link" data-tab="info">Info</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content tab events active">
|
||||||
|
<a href="https://your-website.com/news/Halloween.html">Halloween</a> - <span class="tab-date">20/10/2023</span><br>
|
||||||
|
<a href="https://your-website.com/news/Winter.html">Winter</a> - <span class="tab-date">10/12/2023</span><br>
|
||||||
|
<a href="https://your-website.com/news/Happy_New_Year.html">Happy New Year</a> - <span class="tab-date">01/01/2024</span><br>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content tab notices">
|
||||||
|
<a href="#">Notice 1</a> - <span class="tab-date">01/01/2023</span><br>
|
||||||
|
<a href="#">Notice 2</a> - <span class="tab-date">02/01/2023</span><br>
|
||||||
|
<a href="#">Notice 3</a> - <span class="tab-date">03/01/2023</span><br>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content tab info">
|
||||||
|
<a href="#">Info 1</a> - <span class="tab-date">01/01/2023</span><br>
|
||||||
|
<a href="#">Info 2</a> - <span class="tab-date">02/01/2023</span><br>
|
||||||
|
<a href="#">Info 3</a> - <span class="tab-date">03/01/2023</span><br>
|
||||||
|
</div>
|
||||||
|
<script src="/launcher/news/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
91
public/launcher/news/script.js
Normal file
91
public/launcher/news/script.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* Slider */
|
||||||
|
const slider = document.querySelector('.slider');
|
||||||
|
const slides = slider.querySelectorAll('img');
|
||||||
|
const arrowLeft = document.querySelector('.slider-arrow-left');
|
||||||
|
const arrowRight = document.querySelector('.slider-arrow-right');
|
||||||
|
const dots = document.querySelectorAll('.slider-dot');
|
||||||
|
let currentSlide = 0;
|
||||||
|
let interval;
|
||||||
|
|
||||||
|
function showSlide(index) {
|
||||||
|
slides.forEach(slide => slide.style.transform = `translateX(-${index * 100}%)`);
|
||||||
|
dots.forEach(dot => dot.classList.remove('active'));
|
||||||
|
dots[index].classList.add('active');
|
||||||
|
currentSlide = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() {
|
||||||
|
currentSlide++;
|
||||||
|
if (currentSlide >= slides.length) {
|
||||||
|
currentSlide = 0;
|
||||||
|
}
|
||||||
|
showSlide(currentSlide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevSlide() {
|
||||||
|
currentSlide--;
|
||||||
|
if (currentSlide < 0) {
|
||||||
|
currentSlide = slides.length - 1;
|
||||||
|
}
|
||||||
|
showSlide(currentSlide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startInterval() {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
nextSlide();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopInterval() {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrowLeft.addEventListener('click', () => {
|
||||||
|
stopInterval();
|
||||||
|
prevSlide();
|
||||||
|
startInterval();
|
||||||
|
});
|
||||||
|
|
||||||
|
arrowRight.addEventListener('click', () => {
|
||||||
|
stopInterval();
|
||||||
|
nextSlide();
|
||||||
|
startInterval();
|
||||||
|
});
|
||||||
|
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.addEventListener('click', () => {
|
||||||
|
stopInterval();
|
||||||
|
showSlide(index);
|
||||||
|
startInterval();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
slider.addEventListener('mouseenter', () => {
|
||||||
|
arrowLeft.style.display = 'block';
|
||||||
|
arrowRight.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
slider.addEventListener('mouseleave', () => {
|
||||||
|
arrowLeft.style.display = 'none';
|
||||||
|
arrowRight.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
startInterval();
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
const tabLinks = document.querySelectorAll('.tab-link');
|
||||||
|
const tabs = document.querySelectorAll('.tab');
|
||||||
|
|
||||||
|
function showTab(tab) {
|
||||||
|
tabs.forEach(tab => tab.classList.remove('active'));
|
||||||
|
tab.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
tabLinks.forEach(link => {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
const tab = document.querySelector(`.tab.${link.dataset.tab}`);
|
||||||
|
showTab(tab);
|
||||||
|
tabLinks.forEach(link => link.classList.remove('active'));
|
||||||
|
link.classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
135
public/launcher/news/style.css
Normal file
135
public/launcher/news/style.css
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
body {
|
||||||
|
background-color: #151d4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slider styles */
|
||||||
|
.slider-container {
|
||||||
|
position: relative;
|
||||||
|
width: 460px;
|
||||||
|
height: 214px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 400px;
|
||||||
|
background-color: rgb(0 0 0 / 75%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 400px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-arrow:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-arrow-left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-arrow-right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-dots {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-dot {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgb(68 66 66);
|
||||||
|
margin: -8px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-dot.active {
|
||||||
|
background-color: rgb(255 255 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab styles */
|
||||||
|
.tab {
|
||||||
|
display: none;
|
||||||
|
width: 460px;
|
||||||
|
height: 100px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-links {
|
||||||
|
display: flex;
|
||||||
|
width: 460px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-link {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
color: #e5e6eb;
|
||||||
|
background-color: #151d4c;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-link.active {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
color: #151d4c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #ffffffe3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #000;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container:hover .slider-arrow {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
4
rh-api.bat
Normal file
4
rh-api.bat
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
@echo off
|
||||||
|
title Rusty Hearts API
|
||||||
|
node src/app
|
||||||
|
pause
|
||||||
755
share/RustyHearts_Account.sql
Normal file
755
share/RustyHearts_Account.sql
Normal file
|
|
@ -0,0 +1,755 @@
|
||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : RH VM
|
||||||
|
Source Server Type : SQL Server
|
||||||
|
Source Server Version : 16001050
|
||||||
|
Source Host : 192.168.100.125:1433
|
||||||
|
Source Catalog : RustyHearts_Account
|
||||||
|
Source Schema : dbo
|
||||||
|
|
||||||
|
Target Server Type : SQL Server
|
||||||
|
Target Server Version : 16001050
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 12/05/2023 14:59:51
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for AccountTable
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[AccountTable]') AND type IN ('U'))
|
||||||
|
DROP TABLE [dbo].[AccountTable]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE TABLE [dbo].[AccountTable] (
|
||||||
|
[AccountID] int IDENTITY(1,1) NOT NULL,
|
||||||
|
[WindyCode] varchar(50) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[AccountPwd] varchar(255) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[Email] varchar(255) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[RegisterIP] varchar(16) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[CreatedAt] datetime DEFAULT getdate() NOT NULL,
|
||||||
|
[LastLogin] datetime DEFAULT getdate() NOT NULL,
|
||||||
|
[IsLocked] bit NOT NULL,
|
||||||
|
[LoginAttempts] int NOT NULL,
|
||||||
|
[LastLoginIP] varchar(16) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[Token] varchar(255) COLLATE Chinese_PRC_CI_AS NULL
|
||||||
|
)
|
||||||
|
GO
|
||||||
|
|
||||||
|
ALTER TABLE [dbo].[AccountTable] SET (LOCK_ESCALATION = TABLE)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for BillingLog
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[BillingLog]') AND type IN ('U'))
|
||||||
|
DROP TABLE [dbo].[BillingLog]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE TABLE [dbo].[BillingLog] (
|
||||||
|
[bid] int IDENTITY(1,1) NOT NULL,
|
||||||
|
[BuyTime] datetime NULL,
|
||||||
|
[WindyCode] varchar(50) COLLATE Chinese_PRC_CI_AS NULL,
|
||||||
|
[CharId] varchar(128) COLLATE Chinese_PRC_CI_AS NULL,
|
||||||
|
[UniqueId] varchar(128) COLLATE Chinese_PRC_CI_AS NULL,
|
||||||
|
[Amount] int NULL,
|
||||||
|
[ItemId] int NULL,
|
||||||
|
[ItemCount] int NULL
|
||||||
|
)
|
||||||
|
GO
|
||||||
|
|
||||||
|
ALTER TABLE [dbo].[BillingLog] SET (LOCK_ESCALATION = TABLE)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for CashTable
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[CashTable]') AND type IN ('U'))
|
||||||
|
DROP TABLE [dbo].[CashTable]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE TABLE [dbo].[CashTable] (
|
||||||
|
[WindyCode] varchar(255) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[WorldId] int NULL,
|
||||||
|
[Zen] bigint NULL
|
||||||
|
)
|
||||||
|
GO
|
||||||
|
|
||||||
|
ALTER TABLE [dbo].[CashTable] SET (LOCK_ESCALATION = TABLE)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for VerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[VerificationCode]') AND type IN ('U'))
|
||||||
|
DROP TABLE [dbo].[VerificationCode]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE TABLE [dbo].[VerificationCode] (
|
||||||
|
[id] int IDENTITY(1,1) NOT NULL,
|
||||||
|
[Email] varchar(255) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[VerificationCode] varchar(10) COLLATE Chinese_PRC_CI_AS NOT NULL,
|
||||||
|
[ExpirationTime] datetime NOT NULL,
|
||||||
|
[Type] varchar(20) COLLATE Chinese_PRC_CI_AS NOT NULL
|
||||||
|
)
|
||||||
|
GO
|
||||||
|
|
||||||
|
ALTER TABLE [dbo].[VerificationCode] SET (LOCK_ESCALATION = TABLE)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for GetVerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[GetVerificationCode]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[GetVerificationCode]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[GetVerificationCode]
|
||||||
|
@VerificationCode varchar(10),
|
||||||
|
@Email varchar(255),
|
||||||
|
@VerificationCodeType varchar(20)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
DECLARE @Result varchar(30)
|
||||||
|
DECLARE @ExpirationTime DATETIME
|
||||||
|
DECLARE @Now DATETIME = GETDATE()
|
||||||
|
DECLARE @VerificationCodeExists int;
|
||||||
|
|
||||||
|
SELECT @VerificationCodeExists = COUNT(*) FROM VerificationCode
|
||||||
|
WHERE Email = @Email AND VerificationCode = @VerificationCode AND Type = @VerificationCodeType
|
||||||
|
|
||||||
|
-- Check if VerificationCode exists
|
||||||
|
IF @VerificationCodeExists > 0
|
||||||
|
SET @Result = 'VerificationCodeExists';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'InvalidVerificationCode';
|
||||||
|
|
||||||
|
|
||||||
|
SELECT @ExpirationTime = ExpirationTime
|
||||||
|
FROM VerificationCode
|
||||||
|
WHERE Email = @Email AND VerificationCode = @VerificationCode
|
||||||
|
|
||||||
|
IF @Result = 'VerificationCodeExists'
|
||||||
|
BEGIN
|
||||||
|
IF @ExpirationTime > @Now
|
||||||
|
|
||||||
|
SET @Result = 'ValidVerificationCode';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'ExpiredVerificationCode';
|
||||||
|
END
|
||||||
|
|
||||||
|
SELECT @Result as Result;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for UpdateAccountPassword
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[UpdateAccountPassword]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[UpdateAccountPassword]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[UpdateAccountPassword]
|
||||||
|
@AccountPwd varchar(255),
|
||||||
|
@Email varchar(255)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @AccountExists int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
SELECT @AccountExists = COUNT(*) FROM AccountTable
|
||||||
|
WHERE Email = @Email;
|
||||||
|
|
||||||
|
|
||||||
|
-- Check if account exists
|
||||||
|
IF @AccountExists > 0
|
||||||
|
SET @Result = 'AccountExists';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'Failed';
|
||||||
|
|
||||||
|
-- Update password
|
||||||
|
IF @Result = 'AccountExists'
|
||||||
|
BEGIN
|
||||||
|
UPDATE AccountTable SET AccountPwd = @AccountPwd
|
||||||
|
WHERE Email = @Email;
|
||||||
|
|
||||||
|
SET @Result = 'PasswordChanged';
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result;
|
||||||
|
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for ClearVerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[ClearVerificationCode]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[ClearVerificationCode]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[ClearVerificationCode]
|
||||||
|
@Email varchar(255)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @Result varchar(30)
|
||||||
|
DECLARE @VerificationCodeExists int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
SELECT @VerificationCodeExists = COUNT(*) FROM VerificationCode
|
||||||
|
WHERE Email = @Email;
|
||||||
|
|
||||||
|
|
||||||
|
-- Check if VerificationCode exists
|
||||||
|
IF @VerificationCodeExists > 0
|
||||||
|
SET @Result = 'VerificationCodeExists';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'NoVerificationCode';
|
||||||
|
|
||||||
|
-- DELETE VerificationCodes
|
||||||
|
IF @Result = 'VerificationCodeExists'
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM VerificationCode WHERE Email = @Email;
|
||||||
|
|
||||||
|
SET @Result = 'VerificationCodeClean';
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result;
|
||||||
|
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for GetAccount
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[GetAccount]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[GetAccount]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[GetAccount]
|
||||||
|
@Identifier varchar(255)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @AccountExists int;
|
||||||
|
DECLARE @WindyCode varchar(50)
|
||||||
|
DECLARE @AccountPwd varchar(255)
|
||||||
|
|
||||||
|
|
||||||
|
SELECT @AccountExists = COUNT(*) FROM AccountTable
|
||||||
|
WHERE Email = @Identifier OR WindyCode = @Identifier;
|
||||||
|
SELECT @WindyCode = WindyCode FROM AccountTable
|
||||||
|
WHERE Email = @Identifier OR WindyCode = @Identifier;
|
||||||
|
SELECT @AccountPwd = AccountPwd FROM AccountTable
|
||||||
|
WHERE Email = @Identifier OR WindyCode = @Identifier;
|
||||||
|
|
||||||
|
|
||||||
|
-- Check if account exists
|
||||||
|
IF @AccountExists > 0
|
||||||
|
SET @Result = 'AccountExists';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'AccountNotFound';
|
||||||
|
|
||||||
|
SELECT @Result as Result, @WindyCode as WindyCode, @AccountPwd as AccountPwd;
|
||||||
|
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for GetCurrency
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[GetCurrency]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[GetCurrency]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[GetCurrency]
|
||||||
|
@UserId varchar(50),
|
||||||
|
@ServerId int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @Zen int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
-- Check if entry with given UserId and ServerId exists
|
||||||
|
SELECT @Zen = Zen FROM CashTable
|
||||||
|
WHERE WindyCode = @UserId AND WorldId = @ServerId;
|
||||||
|
|
||||||
|
IF @@ROWCOUNT > 0 -- entry exists
|
||||||
|
BEGIN
|
||||||
|
SET @Result = 'Success';
|
||||||
|
END
|
||||||
|
ELSE -- entry does not exist, insert new one
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO CashTable (WindyCode, WorldId, Zen)
|
||||||
|
VALUES (@UserId, @ServerId, 0);
|
||||||
|
|
||||||
|
SET @Result = 'Success';
|
||||||
|
SET @Zen = 0;
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
SET @Zen = 0;
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result, @Zen as Zen;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for SetPasswordVerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[SetPasswordVerificationCode]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[SetPasswordVerificationCode]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[SetPasswordVerificationCode]
|
||||||
|
@VerificationCode varchar(10),
|
||||||
|
@Email varchar(255),
|
||||||
|
@ExpirationTime DATETIME
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20);
|
||||||
|
DECLARE @VerificationCodeCount int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- Retrieve count of existing verification codes for the user
|
||||||
|
SELECT @VerificationCodeCount = COUNT(*) FROM VerificationCode
|
||||||
|
WHERE Email = @Email;
|
||||||
|
|
||||||
|
-- Check if count of existing verification codes is less than 5
|
||||||
|
IF @VerificationCodeCount < 5
|
||||||
|
BEGIN
|
||||||
|
-- Insert new verification code
|
||||||
|
INSERT INTO VerificationCode (VerificationCode, Email, ExpirationTime, Type)
|
||||||
|
VALUES (@VerificationCode, @Email, @ExpirationTime, 'Password');
|
||||||
|
SET @Result = 'Success';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
-- Delete all existing verification codes for the user
|
||||||
|
DELETE FROM VerificationCode WHERE Email = @Email;
|
||||||
|
|
||||||
|
-- Insert new verification code
|
||||||
|
INSERT INTO VerificationCode (VerificationCode, Email, ExpirationTime, Type)
|
||||||
|
VALUES (@VerificationCode, @Email, @ExpirationTime, 'Password');
|
||||||
|
SET @Result = 'Success';
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH;
|
||||||
|
|
||||||
|
SELECT @Result as Result;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for SetCurrency
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[SetCurrency]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[SetCurrency]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[SetCurrency]
|
||||||
|
@UserId varchar(50),
|
||||||
|
@ServerId int,
|
||||||
|
@NewBalance int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @Zen int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
-- Check if entry with given UserId and ServerId exists
|
||||||
|
SELECT @Zen = Zen FROM CashTable
|
||||||
|
WHERE WindyCode = @UserId AND WorldId = @ServerId;
|
||||||
|
|
||||||
|
IF @@ROWCOUNT > 0 -- entry exists
|
||||||
|
BEGIN
|
||||||
|
UPDATE CashTable SET Zen = @NewBalance
|
||||||
|
WHERE WindyCode = @UserId AND WorldId = @ServerId;
|
||||||
|
|
||||||
|
SET @Zen = @NewBalance;
|
||||||
|
SET @Result = 'Success';
|
||||||
|
END
|
||||||
|
ELSE -- entry does not exist
|
||||||
|
BEGIN
|
||||||
|
SET @Result = 'Failed';
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result, @Zen as Zen;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for SetBillingLog
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[SetBillingLog]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[SetBillingLog]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[SetBillingLog]
|
||||||
|
(
|
||||||
|
@userid varchar(50),
|
||||||
|
@charid varchar(128),
|
||||||
|
@uniqueid varchar(128),
|
||||||
|
@amount int,
|
||||||
|
@itemid int,
|
||||||
|
@itemcount int
|
||||||
|
)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
-- Insert the values into the BillingLog table
|
||||||
|
INSERT INTO BillingLog (BuyTime, WindyCode, CharId, UniqueId, Amount, ItemId, ItemCount)
|
||||||
|
VALUES (GETDATE(), @userid, @charid, @uniqueid, @amount, @itemid, @itemcount);
|
||||||
|
|
||||||
|
-- Return a success message
|
||||||
|
SELECT 'Success' AS Result;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
-- Log the error and return an error message
|
||||||
|
DECLARE @errorMessage varchar(4000) = ERROR_MESSAGE();
|
||||||
|
RAISERROR(@errorMessage, 16, 1);
|
||||||
|
|
||||||
|
-- Return an error message
|
||||||
|
SELECT 'Error: ' + @errorMessage AS Result;
|
||||||
|
END CATCH;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for CreateAccount
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[CreateAccount]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[CreateAccount]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[CreateAccount]
|
||||||
|
@WindyCode varchar(50),
|
||||||
|
@AccountPwd varchar(255),
|
||||||
|
@Email varchar(255),
|
||||||
|
@RegisterIP varchar(16)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @AccountExists int;
|
||||||
|
DECLARE @WindyCodeExists int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
|
||||||
|
SELECT @AccountExists = COUNT(*) FROM AccountTable
|
||||||
|
WHERE WindyCode = @WindyCode OR Email = @Email;
|
||||||
|
SELECT @WindyCodeExists = COUNT(*) FROM RustyHearts_Auth.dbo.AuthTable
|
||||||
|
WHERE WindyCode = @WindyCode;
|
||||||
|
|
||||||
|
|
||||||
|
-- Check if account exists
|
||||||
|
IF @AccountExists > 0
|
||||||
|
SET @Result = 'AccountExists';
|
||||||
|
ELSE IF @WindyCodeExists > 0
|
||||||
|
SET @Result = 'WindyCodeExists';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'NewUser';
|
||||||
|
|
||||||
|
-- Create new account
|
||||||
|
IF @Result = 'NewUser'
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO AccountTable (WindyCode, AccountPwd, Email, RegisterIP, CreatedAt, LastLogin, IsLocked, LoginAttempts, LastLoginIP)
|
||||||
|
VALUES (@WindyCode, @AccountPwd, @Email, @RegisterIP, GETDATE(), GETDATE(), 0, 0, @RegisterIP);
|
||||||
|
|
||||||
|
INSERT INTO RustyHearts_Auth.dbo.AuthTable (WindyCode, world_id, AuthID, Tcount, online, CTime, BTime, LTime, IP, LCount, ServerIP, ServerType, HostID, DBCIndex, InquiryCount, event_inquiry, CashMileage, channelling, pc_room_point, externcash, mac_addr, mac_addr02, mac_addr03, second_pass)
|
||||||
|
VALUES (@WindyCode, 0, NEWID(), 0, '0', GETDATE(), GETDATE(), GETDATE(), @RegisterIP, 0, 0, 0, 0, 0, 5, 1, 0, 1, 0, 0, '00-00-00-00-00-00', '00-00-00-00-00-00', '00-00-00-00-00-00', '');
|
||||||
|
|
||||||
|
INSERT INTO CashTable (WindyCode, WorldId, Zen)
|
||||||
|
VALUES (@WindyCode, 10101, 0);
|
||||||
|
|
||||||
|
|
||||||
|
SET @Result = 'AccountCreated';
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result;
|
||||||
|
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for AuthenticateUser
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[AuthenticateUser]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[AuthenticateUser]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[AuthenticateUser]
|
||||||
|
@Identifier varchar(255),
|
||||||
|
@password_verify_result BIT,
|
||||||
|
@LastLoginIP varchar(15)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @WindyCode varchar(50)
|
||||||
|
DECLARE @AuthID varchar(50)
|
||||||
|
DECLARE @LoginAttempts int
|
||||||
|
DECLARE @IsLocked BIT
|
||||||
|
DECLARE @Now datetime = GETDATE()
|
||||||
|
DECLARE @LastLogin datetime
|
||||||
|
DECLARE @Token NVARCHAR(64)
|
||||||
|
DECLARE @RandomBytes VARBINARY(32)
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
-- Retrieve account information
|
||||||
|
SELECT @WindyCode = WindyCode, @LoginAttempts = LoginAttempts, @IsLocked = IsLocked, @LastLogin = LastLogin
|
||||||
|
FROM AccountTable
|
||||||
|
WHERE WindyCode = @Identifier OR Email = @Identifier;
|
||||||
|
SELECT @AuthID = AuthID
|
||||||
|
FROM RustyHearts_Auth.dbo.AuthTable
|
||||||
|
WHERE WindyCode = @WindyCode;
|
||||||
|
|
||||||
|
-- Check if last login attempt is within 5 minutes
|
||||||
|
IF DATEDIFF(minute, @LastLogin, @Now) > 5
|
||||||
|
BEGIN
|
||||||
|
UPDATE AccountTable SET LoginAttempts = 0 WHERE WindyCode = @Identifier OR Email = @Identifier;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- Verify password
|
||||||
|
IF @password_verify_result = 1
|
||||||
|
BEGIN
|
||||||
|
SET @Result = 'LoginSuccess';
|
||||||
|
|
||||||
|
SET @RandomBytes = CAST(CRYPT_GEN_RANDOM(32) AS VARBINARY(32)) -- Generate 32 random bytes
|
||||||
|
SET @Token = LOWER(CONVERT(NVARCHAR(64), HashBytes('SHA2_256', @RandomBytes), 2)) -- Hash the random bytes using SHA256 and convert to lowercase hexadecimal string
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'InvalidCredentials';
|
||||||
|
|
||||||
|
-- Check account status
|
||||||
|
IF @Result = 'LoginSuccess' AND @IsLocked = 1
|
||||||
|
SET @Result = 'Locked';
|
||||||
|
ELSE IF @LoginAttempts >= 10
|
||||||
|
SET @Result = 'TooManyAttempts';
|
||||||
|
ELSE
|
||||||
|
|
||||||
|
-- Update login attempts, token, and last login IP
|
||||||
|
IF @Result = 'LoginSuccess'
|
||||||
|
BEGIN
|
||||||
|
UPDATE AccountTable SET LoginAttempts = 0, Token = @Token, LastLoginIP = @LastLoginIP, LastLogin = @Now WHERE (WindyCode = @Identifier OR Email = @Identifier);
|
||||||
|
END
|
||||||
|
ELSE IF @Result = 'InvalidCredentials'
|
||||||
|
BEGIN
|
||||||
|
UPDATE AccountTable SET LoginAttempts = @LoginAttempts + 1, LastLogin = @Now WHERE (WindyCode = @Identifier OR Email = @Identifier);
|
||||||
|
END
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result, @WindyCode as WindyCode, @AuthID as AuthID, @Token as Token;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- procedure structure for SetAccountVerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[SetAccountVerificationCode]') AND type IN ('P', 'PC', 'RF', 'X'))
|
||||||
|
DROP PROCEDURE[dbo].[SetAccountVerificationCode]
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[SetAccountVerificationCode]
|
||||||
|
@VerificationCode varchar(10),
|
||||||
|
@Email varchar(255),
|
||||||
|
@ExpirationTime DATETIME
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
DECLARE @Result varchar(20)
|
||||||
|
DECLARE @AccountExists int;
|
||||||
|
DECLARE @VerificationCodeCount int;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION
|
||||||
|
|
||||||
|
SELECT @AccountExists = COUNT(*) FROM AccountTable
|
||||||
|
WHERE Email = @Email;
|
||||||
|
|
||||||
|
|
||||||
|
-- Check if account exists
|
||||||
|
IF @AccountExists > 0
|
||||||
|
SET @Result = 'AccountExists';
|
||||||
|
ELSE
|
||||||
|
SET @Result = 'AccountDontExists';
|
||||||
|
|
||||||
|
IF @Result = 'AccountDontExists'
|
||||||
|
-- Retrieve count of existing verification codes for the user
|
||||||
|
SELECT @VerificationCodeCount = COUNT(*) FROM VerificationCode
|
||||||
|
WHERE Email = @Email;
|
||||||
|
|
||||||
|
-- Check if count of existing verification codes is less than 5
|
||||||
|
IF @VerificationCodeCount < 5
|
||||||
|
BEGIN
|
||||||
|
-- Insert new verification code
|
||||||
|
INSERT INTO VerificationCode (VerificationCode, Email, ExpirationTime, Type)
|
||||||
|
VALUES (@VerificationCode, @Email, @ExpirationTime, 'Account');
|
||||||
|
SET @Result = 'Success';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
-- Delete all existing verification codes for the user
|
||||||
|
DELETE FROM VerificationCode WHERE Email = @Email;
|
||||||
|
|
||||||
|
-- Insert new verification code
|
||||||
|
INSERT INTO VerificationCode (VerificationCode, Email, ExpirationTime, Type)
|
||||||
|
VALUES (@VerificationCode, @Email, @ExpirationTime, 'Account');
|
||||||
|
SET @Result = 'Success';
|
||||||
|
END;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
SET @Result = 'TransactionFailed';
|
||||||
|
END CATCH
|
||||||
|
|
||||||
|
SELECT @Result as Result;
|
||||||
|
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Auto increment value for AccountTable
|
||||||
|
-- ----------------------------
|
||||||
|
DBCC CHECKIDENT ('[dbo].[AccountTable]', RESEED, 2)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Primary Key structure for table AccountTable
|
||||||
|
-- ----------------------------
|
||||||
|
ALTER TABLE [dbo].[AccountTable] ADD CONSTRAINT [PK__AccountT__349DA586E13EC640] PRIMARY KEY CLUSTERED ([AccountID])
|
||||||
|
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||||
|
ON [PRIMARY]
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Auto increment value for BillingLog
|
||||||
|
-- ----------------------------
|
||||||
|
DBCC CHECKIDENT ('[dbo].[BillingLog]', RESEED, 1)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Primary Key structure for table BillingLog
|
||||||
|
-- ----------------------------
|
||||||
|
ALTER TABLE [dbo].[BillingLog] ADD CONSTRAINT [PK_BillingLog] PRIMARY KEY CLUSTERED ([bid])
|
||||||
|
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||||
|
ON [PRIMARY]
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Auto increment value for VerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
DBCC CHECKIDENT ('[dbo].[VerificationCode]', RESEED, 1)
|
||||||
|
GO
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Primary Key structure for table VerificationCode
|
||||||
|
-- ----------------------------
|
||||||
|
ALTER TABLE [dbo].[VerificationCode] ADD CONSTRAINT [PK__Password__3213E83FA2A48C58] PRIMARY KEY CLUSTERED ([id])
|
||||||
|
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||||
|
ON [PRIMARY]
|
||||||
|
GO
|
||||||
|
|
||||||
9
share/service_control.xml
Normal file
9
share/service_control.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<service>
|
||||||
|
<active_area country="usa" />
|
||||||
|
|
||||||
|
<area country="usa" auth_url="http://localhost:8070/serverApi/auth" billing_url="http://localhost:8080/serverApi/billing" billing_idc="10101" xtrap_use="0" server_mode="WAG" betazone="0" />
|
||||||
|
|
||||||
|
<area country="chn" skip_auth="1" free_cash="1" skip_abuse_nick ="1" enc_xml_use ="1" billing_idc="10101" xtrap_use="0" server_mode="WAG" betazone="0" />
|
||||||
|
</service>
|
||||||
101
src/app.js
Normal file
101
src/app.js
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Load environment variables
|
||||||
|
const env = require('./utils/env');
|
||||||
|
|
||||||
|
// Import modules
|
||||||
|
const express = require('express');
|
||||||
|
const helmet = require('helmet');
|
||||||
|
const cors = require('cors');
|
||||||
|
const compression = require('compression');
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
|
const expressWinston = require('express-winston');
|
||||||
|
const { logger } = require('./utils/logger');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Import routes
|
||||||
|
const authRouter = require('./routes/auth');
|
||||||
|
const billingRouter = require('./routes/billing');
|
||||||
|
const gatewayRouter = require('./routes/gateway');
|
||||||
|
const loginRouter = require('./routes/launcher/login');
|
||||||
|
const registerRouter = require('./routes/launcher/register');
|
||||||
|
const codeVerificationRouter = require('./routes/launcher/codeVerification');
|
||||||
|
const passwordResetEmailRouter = require('./routes/launcher/passwordResetEmail');
|
||||||
|
const passwordChangeRouter = require('./routes/launcher/changePassword');
|
||||||
|
const verificationEmailRouter = require('./routes/launcher/verificationEmail');
|
||||||
|
const launcherUpdaterRouter = require('./routes/launcher/launcherUpdater');
|
||||||
|
|
||||||
|
// Set up rate limiter
|
||||||
|
const limiter = rateLimit({
|
||||||
|
windowMs: 60 * 1000, // 1 minute
|
||||||
|
max: 60, // limit each IP to 60 requests per minute
|
||||||
|
message: 'Too many requests from this IP, please try again later'
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Set up middleware
|
||||||
|
const middleware = [
|
||||||
|
cors(),
|
||||||
|
compression(),
|
||||||
|
express.json(),
|
||||||
|
express.urlencoded({ extended: false }),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (process.env.ENABLE_HELMET === 'true') {
|
||||||
|
middleware.unshift(helmet());
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(...middleware);
|
||||||
|
|
||||||
|
const authPort = process.env.AUTH_PORT || 8070;
|
||||||
|
const billingPort = process.env.BILLING_PORT || 8080;
|
||||||
|
|
||||||
|
// Set up routes
|
||||||
|
app.use('/serverApi/auth', limiter, authRouter).listen(authPort, '127.0.0.1');
|
||||||
|
app.use('/serverApi/billing', limiter , billingRouter).listen(billingPort, '127.0.0.1');
|
||||||
|
app.use('/serverApi/gateway', limiter , gatewayRouter);
|
||||||
|
app.use('/accountApi/register', limiter , registerRouter);
|
||||||
|
app.use('/accountApi/login', limiter , loginRouter);
|
||||||
|
app.use('/accountApi/codeVerification', limiter , codeVerificationRouter);
|
||||||
|
app.use('/accountApi/sendPasswordResetEmail', limiter , passwordResetEmailRouter);
|
||||||
|
app.use('/accountApi/changePassword', limiter , passwordChangeRouter);
|
||||||
|
app.use('/accountApi/sendVerificationEmail', limiter , verificationEmailRouter);
|
||||||
|
app.use('/launcherApi/launcherUpdater', launcherUpdaterRouter);
|
||||||
|
|
||||||
|
// Serve static files from public folder
|
||||||
|
app.use(express.static('../public'));
|
||||||
|
|
||||||
|
// Serve static files for the launcher
|
||||||
|
app.get('/launcher/news', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, '../public/launcher/news/news-panel.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/launcher/agreement', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, '../public/launcher/news/agreement.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/favicon.ico', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, '../public/launcher/news/favicon.ico'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use('/launcher/news/images', express.static(path.join(__dirname, '../public/launcher/news/images')));
|
||||||
|
app.use('/launcher/news', express.static(path.join(__dirname, '../public/launcher/news')));
|
||||||
|
app.use('/launcher/patch', express.static(path.join(__dirname, '../public/launcher/patch')));
|
||||||
|
|
||||||
|
|
||||||
|
// Set up error handling middleware
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
if (env.LOG_LEVEL && env.LOG_LEVEL === 'error') {
|
||||||
|
logger.error(err.stack);
|
||||||
|
} else {
|
||||||
|
logger.info(err.stack);
|
||||||
|
}
|
||||||
|
res.status(500).send('Something went wrong' + err.stack);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
app.listen(port, '0.0.0.0', () => {
|
||||||
|
logger.info(`API listening on *:${port}`);
|
||||||
|
logger.info(`Auth API listening on 127.0.0.1:${authPort}`);
|
||||||
|
logger.info(`Billing API listening on 127.0.0.1:${billingPort}`);
|
||||||
|
});
|
||||||
114
src/mailer/mailer.js
Normal file
114
src/mailer/mailer.js
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
const handlebars = require('handlebars');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { mailerLogger } = require('../utils/logger');
|
||||||
|
|
||||||
|
// Load the email templates
|
||||||
|
const emailTemplates = {
|
||||||
|
confirmation: fs.readFileSync(path.join(__dirname, 'templates', 'confirmationTemplate.hbs'), 'utf-8'),
|
||||||
|
verification: fs.readFileSync(path.join(__dirname, 'templates', 'verificationTemplate.hbs'), 'utf-8'),
|
||||||
|
passwordReset: fs.readFileSync(path.join(__dirname, 'templates', 'passwordResetTemplate.hbs'), 'utf-8'),
|
||||||
|
passwordChanged: fs.readFileSync(path.join(__dirname, 'templates', 'passwordChangedTemplate.hbs'), 'utf-8')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compile the email templates
|
||||||
|
const compiledTemplates = {
|
||||||
|
confirmation: handlebars.compile(emailTemplates.confirmation),
|
||||||
|
verification: handlebars.compile(emailTemplates.verification),
|
||||||
|
passwordReset: handlebars.compile(emailTemplates.passwordReset),
|
||||||
|
passwordChanged: handlebars.compile(emailTemplates.passwordChanged)
|
||||||
|
};
|
||||||
|
|
||||||
|
// SMTP transport configuration
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.SMTP_HOST,
|
||||||
|
port: process.env.SMTP_PORT,
|
||||||
|
secure: process.env.SMTP_ENCRYPTION === 'ssl' || process.env.SMTP_ENCRYPTION === 'tls',
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMTP_USERNAME,
|
||||||
|
pass: process.env.SMTP_PASSWORD
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendConfirmationEmail(email, windyCode) {
|
||||||
|
const template = compiledTemplates.confirmation;
|
||||||
|
const emailContent = template({ windyCode });
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: `"${process.env.SMTP_FROMNAME}" <${process.env.SMTP_USERNAME}>`,
|
||||||
|
to: email,
|
||||||
|
subject: '[Rusty Hearts] Account Creation Confirmation',
|
||||||
|
html: emailContent
|
||||||
|
};
|
||||||
|
|
||||||
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
if (error) {
|
||||||
|
mailerLogger.error('[Mailer] Error sending confirmation email: ' + error.message);
|
||||||
|
} else {
|
||||||
|
mailerLogger.info('[Mailer] Confirmation email sent: ' + info.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendVerificationEmail(email, verificationCode) {
|
||||||
|
const template = compiledTemplates.verification;
|
||||||
|
const emailContent = template({ verificationCode });
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: `"${process.env.SMTP_FROMNAME}" <${process.env.SMTP_USERNAME}>`,
|
||||||
|
to: email,
|
||||||
|
subject: '[Rusty Hearts] Account Creation',
|
||||||
|
html: emailContent
|
||||||
|
};
|
||||||
|
|
||||||
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
if (error) {
|
||||||
|
mailerLogger.error('[Mailer] Error sending verification email: ' + error.message);
|
||||||
|
} else {
|
||||||
|
mailerLogger.info('[Mailer] Verification email sent: ' + info.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPasswordResetEmail(email, verificationCode) {
|
||||||
|
const template = compiledTemplates.passwordReset;
|
||||||
|
const emailContent = template({ verificationCode });
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: `"${process.env.SMTP_FROMNAME}" <${process.env.SMTP_USERNAME}>`,
|
||||||
|
to: email,
|
||||||
|
subject: '[Rusty Hearts] Password Reset Request',
|
||||||
|
html: emailContent
|
||||||
|
};
|
||||||
|
|
||||||
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
if (error) {
|
||||||
|
mailerLogger.error('[Mailer] Error sending password reset email: ' + error.message);
|
||||||
|
} else {
|
||||||
|
mailerLogger.info('[Mailer] Password reset email sent: ' + info.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPasswordChangedEmail(email, windyCode) {
|
||||||
|
const template = compiledTemplates.passwordChanged;
|
||||||
|
const emailContent = template({ windyCode });
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: `"${process.env.SMTP_FROMNAME}" <${process.env.SMTP_USERNAME}>`,
|
||||||
|
to: email,
|
||||||
|
subject: '[Rusty Hearts] Account Password Changed',
|
||||||
|
html: emailContent
|
||||||
|
};
|
||||||
|
|
||||||
|
transporter.sendMail(mailOptions, (error, info) => {
|
||||||
|
if (error) {
|
||||||
|
mailerLogger.error('[Mailer] Error sending password changed email: ' + error.message);
|
||||||
|
} else {
|
||||||
|
mailerLogger.info('[Mailer] Password changed email sent: ' + info.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {sendConfirmationEmail, sendVerificationEmail, sendPasswordResetEmail, sendPasswordChangedEmail};
|
||||||
14
src/mailer/templates/confirmationTemplate.hbs
Normal file
14
src/mailer/templates/confirmationTemplate.hbs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Welcome to Rusty Hearts!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to Rusty Hearts</h1>
|
||||||
|
<p>Dear {{windyCode}},</p>
|
||||||
|
<p>Thank you for creating an account with Rusty Hearts! We are thrilled to have you as part of our community.</p>
|
||||||
|
<p>You are now ready to login and start playing Rusty Hearts. As you embark on your journey, remember to have fun and enjoy the game!</p>
|
||||||
|
<p>If you have any questions or need assistance with anything, please do not hesitate to contact our support team. We are always here to help you.</p>
|
||||||
|
<p>Thank you again for choosing Rusty Hearts. We look forward to seeing you in the game!</p>
|
||||||
|
<p>Best regards,<br>Rusty Hearts Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
src/mailer/templates/passwordChangedTemplate.hbs
Normal file
16
src/mailer/templates/passwordChangedTemplate.hbs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>[Rusty Hearts] Account Password Changed</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello, {{windyCode}}</p>
|
||||||
|
<p>We're writing to let you know that the password for your Rusty Hearts account has been changed. If you made this change yourself, you can disregard this message.</p>
|
||||||
|
<p>However, if you didn't change your password or if you're unsure if someone else has gained access to your account, we recommend taking immediate action to secure your account. Here are some steps you can take:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Change your password again and make sure it's strong and unique</li>
|
||||||
|
<li>Contact our support team if you need further assistance or if you believe your account has been compromised</li>
|
||||||
|
</ul>
|
||||||
|
<p>Best regards,<br>Rusty Hearts Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
src/mailer/templates/passwordResetTemplate.hbs
Normal file
17
src/mailer/templates/passwordResetTemplate.hbs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>[Rusty Hearts] Password Reset Request</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello {{windycode}},</p>
|
||||||
|
<p>We received a request to reset your Rusty Hearts account password. To proceed with the password reset, please enter the verification code below:</p>
|
||||||
|
<div style="background-color: #f2f2f2; border: 1px solid #000; padding: 10px;">
|
||||||
|
{{verificationCode}}
|
||||||
|
</div>
|
||||||
|
<p>This code will expire in 10 minutes for security reasons. If you do not enter the code within this timeframe, you may need to request a new one.</p>
|
||||||
|
<p>If you did not initiate this password reset or do not recognize this email, please disregard it and contact our support team immediately to protect your account.</p>
|
||||||
|
<p>Thank you for playing Rusty Hearts!</p>
|
||||||
|
<p>Best regards,<br>Rusty Hearts Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
src/mailer/templates/verificationTemplate.hbs
Normal file
17
src/mailer/templates/verificationTemplate.hbs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>[Rusty Hearts] Account Creation</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>Thank you for registering your email address with your Rusty Hearts account. To complete the registration process, please enter the verification code below:</p>
|
||||||
|
<div style="background-color: #f2f2f2; border: 1px solid #000; padding: 10px;">
|
||||||
|
{{verificationCode}}
|
||||||
|
</div>
|
||||||
|
<p>This code will expire in 10 minutes for security reasons. If you do not enter the code within this timeframe, you may need to request a new one.</p>
|
||||||
|
<p>If you did not initiate this registration or do not recognize this email, please disregard it and contact our support team immediately.</p>
|
||||||
|
<p>Thank you for playing Rusty Hearts!</p>
|
||||||
|
<p>Best regards,<br>Rusty Hearts Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
77
src/routes/auth.js
Normal file
77
src/routes/auth.js
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const xml2js = require('xml2js');
|
||||||
|
const parser = new xml2js.Parser();
|
||||||
|
const sql = require('mssql');
|
||||||
|
const router = express.Router();
|
||||||
|
const { authLogger } = require('../utils/logger');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../utils/dbConfig');
|
||||||
|
|
||||||
|
// Route for handling login requests
|
||||||
|
router.post('/', bodyParser.text({
|
||||||
|
type: '*/xml'
|
||||||
|
}), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const xml = req.body;
|
||||||
|
const result = await parser.parseStringPromise(xml);
|
||||||
|
const loginRequest = result['login-request'];
|
||||||
|
const account = loginRequest.account[0];
|
||||||
|
const password = loginRequest.password[0];
|
||||||
|
const game = loginRequest.game[0];
|
||||||
|
const ip = loginRequest.ip[0];
|
||||||
|
|
||||||
|
authLogger.info(`[Auth] Account [${account}] is trying to login from [${ip}]`);
|
||||||
|
|
||||||
|
// Create a connection pool for the database
|
||||||
|
const pool = await connAccount;
|
||||||
|
|
||||||
|
// Get the account information from the database
|
||||||
|
const {
|
||||||
|
recordset
|
||||||
|
} = await pool
|
||||||
|
.request()
|
||||||
|
.input('Identifier', sql.VarChar(50), account)
|
||||||
|
.execute('GetAccount');
|
||||||
|
|
||||||
|
// Check if the account exists
|
||||||
|
const row = recordset[0];
|
||||||
|
if (!row || row.Result !== 'AccountExists') {
|
||||||
|
authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`);
|
||||||
|
return res.send('<status>failed</status>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the password using bcrypt
|
||||||
|
const passwordMatch = await bcrypt.compare(password, row.AccountPwd);
|
||||||
|
|
||||||
|
// Authenticate the user and update the database
|
||||||
|
const {
|
||||||
|
recordset: authRecordset
|
||||||
|
} = await pool
|
||||||
|
.request()
|
||||||
|
.input('Identifier', sql.VarChar(50), account)
|
||||||
|
.input('password_verify_result', sql.Bit, passwordMatch ? 1 : 0)
|
||||||
|
.input('LastLoginIP', sql.VarChar(50), ip)
|
||||||
|
.execute('AuthenticateUser');
|
||||||
|
|
||||||
|
const authRow = authRecordset[0];
|
||||||
|
if (!authRow || authRow.Result !== 'LoginSuccess') {
|
||||||
|
authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`);
|
||||||
|
return res.send('<status>failed</status>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the authentication response
|
||||||
|
const response = `<userid>${authRow.AuthID}</userid><user-type>F</user-type><status>success</status>`;
|
||||||
|
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||||
|
res.send(response);
|
||||||
|
|
||||||
|
authLogger.info(`[Auth] Account [${account}] successfully logged in from [${ip}]`);
|
||||||
|
} catch (error) {
|
||||||
|
authLogger.error('Error handling login request: ' + error.message);
|
||||||
|
res.status(500).send('<status>failed</status>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
122
src/routes/billing.js
Normal file
122
src/routes/billing.js
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const xml2js = require('xml2js');
|
||||||
|
const parser = new xml2js.Parser();
|
||||||
|
const sql = require('mssql');
|
||||||
|
const router = express.Router();
|
||||||
|
const { billingLogger} = require('../utils/logger');;
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../utils/dbConfig');
|
||||||
|
|
||||||
|
// Route for handling billing requests
|
||||||
|
router.post('/', bodyParser.text({
|
||||||
|
type: '*/xml'
|
||||||
|
}), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const xml = req.body;
|
||||||
|
const result = await parser.parseStringPromise(xml);
|
||||||
|
const name = result['currency-request'] ? 'currency-request' : 'item-purchase-request';
|
||||||
|
const request = result[name];
|
||||||
|
const userid = request.userid[0];
|
||||||
|
const server = request.server[0];
|
||||||
|
|
||||||
|
billingLogger.info(`[Billing] Received [${name}] from user [${userid}]`);
|
||||||
|
|
||||||
|
// Create a connection pool for the database
|
||||||
|
const pool = await connAccount;
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case 'currency-request':
|
||||||
|
const {
|
||||||
|
recordset
|
||||||
|
} = await pool
|
||||||
|
.request()
|
||||||
|
.input('UserId', sql.VarChar(50), userid)
|
||||||
|
.input('ServerId', sql.VarChar(50), server)
|
||||||
|
.execute('GetCurrency');
|
||||||
|
|
||||||
|
const row = recordset[0];
|
||||||
|
|
||||||
|
if (row && row.Result === 'Success') {
|
||||||
|
const response = `<result><balance>${row.Zen}</balance></result>`;
|
||||||
|
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||||
|
res.send(response);
|
||||||
|
} else {
|
||||||
|
res.send('<status>failed</status>');
|
||||||
|
return res.status(400).send(row.Result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'item-purchase-request':
|
||||||
|
const charid = request.charid[0];
|
||||||
|
const uniqueid = request.uniqueid[0];
|
||||||
|
const amount = request.amount[0];
|
||||||
|
const itemid = request.itemid[0];
|
||||||
|
const itemcount = request.count[0];
|
||||||
|
|
||||||
|
const {
|
||||||
|
recordset: currencyRecordset
|
||||||
|
} = await pool
|
||||||
|
.request()
|
||||||
|
.input('UserId', sql.VarChar(50), userid)
|
||||||
|
.input('ServerId', sql.VarChar(50), server)
|
||||||
|
.execute('GetCurrency');
|
||||||
|
|
||||||
|
const currencyRow = currencyRecordset[0];
|
||||||
|
|
||||||
|
if (currencyRow && currencyRow.Result === 'Success') {
|
||||||
|
const balance = currencyRow.Zen;
|
||||||
|
|
||||||
|
if (amount > 0) {
|
||||||
|
if (amount > balance) {
|
||||||
|
res.send('<status>failed</status>');
|
||||||
|
billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] failed. Not enough Zen [${balance}]. charid: [${charid}] itemid: [${itemid}] itemcount: [${itemcount}] price: [${amount}]`);
|
||||||
|
} else {
|
||||||
|
const newbalance = balance - amount;
|
||||||
|
|
||||||
|
await pool
|
||||||
|
.request()
|
||||||
|
.input('UserId', sql.VarChar(50), userid)
|
||||||
|
.input('ServerId', sql.VarChar(50), server)
|
||||||
|
.input('NewBalance', sql.BigInt, newbalance)
|
||||||
|
.execute('SetCurrency');
|
||||||
|
|
||||||
|
await pool
|
||||||
|
.request()
|
||||||
|
.input('userid', sql.VarChar(50), userid)
|
||||||
|
.input('charid', sql.VarChar(50), charid)
|
||||||
|
.input('uniqueid', sql.VarChar(50), uniqueid)
|
||||||
|
.input('amount', sql.BigInt, amount)
|
||||||
|
.input('itemid', sql.VarChar(50), itemid)
|
||||||
|
.input('itemcount', sql.Int, itemcount)
|
||||||
|
.execute('SetBillingLog');
|
||||||
|
|
||||||
|
billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] success. charid: [${charid}] itemid: [${itemid}] itemcount: [${itemcount}] price: [${amount}]`);
|
||||||
|
billingLogger.info(`[Billing] Item purchase from user [${userid}] success. New zen balance: [${newbalance}]`);
|
||||||
|
|
||||||
|
const response = `<result><status>success</status><new-balance>${newbalance}</new-balance></result>`;
|
||||||
|
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||||
|
res.send(response);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = `<result><balance>${currencyRow.Zen}</balance></result>`;
|
||||||
|
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||||
|
res.send(response);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.send('<status>failed</status>');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.send('<status>failed</status>');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
billingLogger.error(`[Billing] Error handling request: $ {
|
||||||
|
error.message
|
||||||
|
}`);
|
||||||
|
res.status(500).send('<status>failed</status>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
54
src/routes/gateway.js
Normal file
54
src/routes/gateway.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const net = require('net');
|
||||||
|
|
||||||
|
// Define the gateway route
|
||||||
|
router.get('/', (req, res) => {
|
||||||
|
const ip = process.env.GATESERVER_IP;
|
||||||
|
const port = process.env.GATESERVER_PORT || '50001';
|
||||||
|
|
||||||
|
// Generate the XML content with the IP and port values
|
||||||
|
const xml = `<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<network>
|
||||||
|
<gateserver ip="${ip}" port="${port}" />
|
||||||
|
</network>`;
|
||||||
|
|
||||||
|
res.set('Content-Type', 'application/xml');
|
||||||
|
|
||||||
|
res.send(xml);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define the gateway info route
|
||||||
|
router.get('/info', (req, res) => {
|
||||||
|
const gatewayRoute = `1|${req.protocol}://${req.headers.host}/serverApi/gateway|${req.protocol}://${req.headers.host}/serverApi/gateway|`;
|
||||||
|
res.send(gatewayRoute);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define the gateway status route
|
||||||
|
router.get('/status', async (req, res) => {
|
||||||
|
const ip = process.env.GATESERVER_IP;
|
||||||
|
const port = process.env.GATESERVER_PORT || '50001';
|
||||||
|
|
||||||
|
const timeout = 2000;
|
||||||
|
|
||||||
|
// Create a new socket and connect to the gateserver
|
||||||
|
const socket = new net.Socket();
|
||||||
|
socket.setTimeout(timeout);
|
||||||
|
socket.connect(port, ip);
|
||||||
|
|
||||||
|
// Handle the socket events to check the connection status
|
||||||
|
socket.on('connect', () => {
|
||||||
|
res.status(200).json({ status: 'online' });
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
socket.on('timeout', () => {
|
||||||
|
res.status(408).json({ status: 'offline' });
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
socket.on('error', () => {
|
||||||
|
res.status(503).json({ status: 'offline' });
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
108
src/routes/launcher/changePassword.js
Normal file
108
src/routes/launcher/changePassword.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const { logger, accountLogger } = require('../../utils/logger');
|
||||||
|
const { sendPasswordChangedEmail } = require('../../mailer/mailer');
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../../utils/dbConfig');
|
||||||
|
|
||||||
|
// Joi schema for request body validation
|
||||||
|
const schema = Joi.object({
|
||||||
|
email: Joi.string().email().required(),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
verification_code: Joi.string().pattern(new RegExp('^[0-9]+$')).required()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route for registering an account
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Validate request body
|
||||||
|
const { error, value } = schema.validate(req.body);
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).send(error.details[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = value.email;
|
||||||
|
const password = value.password;
|
||||||
|
const verificationCode = value.verification_code;
|
||||||
|
|
||||||
|
// Use a prepared statement to get the verification code
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Email', sql.VarChar, email);
|
||||||
|
request.input('VerificationCode', sql.VarChar, verificationCode);
|
||||||
|
request.input('VerificationCodeType', sql.VarChar, 'Password');
|
||||||
|
const inputResult = await request.execute('GetVerificationCode');
|
||||||
|
const inputRow = inputResult.recordset[0];
|
||||||
|
|
||||||
|
if (inputRow && inputRow.Result === 'ValidVerificationCode') {
|
||||||
|
|
||||||
|
// Use a prepared statement to retrieve the account information
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Identifier', sql.VarChar, email);
|
||||||
|
const getResult = await request.execute('GetAccount');
|
||||||
|
const getRow = getResult.recordset[0];
|
||||||
|
|
||||||
|
if (getRow && getRow.Result === 'AccountExists') {
|
||||||
|
const windyCode = getRow.WindyCode
|
||||||
|
const hash = getRow.AccountPwd;
|
||||||
|
|
||||||
|
// Verify the password
|
||||||
|
const md5_password = crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(windyCode + password)
|
||||||
|
.digest('hex');
|
||||||
|
const password_verify_result = await bcrypt.compare(
|
||||||
|
md5_password,
|
||||||
|
hash
|
||||||
|
);
|
||||||
|
if (password_verify_result === true) {
|
||||||
|
return res.status(400).send('SamePassword');
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const passwordHash = await bcrypt.hash(md5_password, 10);
|
||||||
|
|
||||||
|
// Use a prepared statement to update the password
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Email', sql.VarChar, email);
|
||||||
|
request.input('AccountPwd', sql.VarChar, passwordHash);
|
||||||
|
const updateResult = await request.execute('UpdateAccountPassword');
|
||||||
|
const updateRow = updateResult.recordset[0];
|
||||||
|
|
||||||
|
if (updateRow && updateRow.Result === 'PasswordChanged') {
|
||||||
|
accountLogger.info(`[Account] Password for [${windyCode}] changed successfully`);
|
||||||
|
sendPasswordChangedEmail(email, windyCode);
|
||||||
|
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Email', sql.VarChar, email);
|
||||||
|
const clearResult = await request.execute('ClearVerificationCode');
|
||||||
|
const clearRow = clearResult.recordset[0];
|
||||||
|
|
||||||
|
return res.status(200).send('PasswordChanged');
|
||||||
|
} else {
|
||||||
|
accountLogger.info(`[Account] Password change for [${windyCode}] failed: ${row.Result}`);
|
||||||
|
return res.status(400).send(updateRow.Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return res.status(400).send(getRow.Result);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return res.status(400).send(inputRow.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Account] Database query failed: ' + error.message);
|
||||||
|
return res.status(500).send('[Account] Database query failed: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
53
src/routes/launcher/codeVerification.js
Normal file
53
src/routes/launcher/codeVerification.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { logger } = require('../../utils/logger');
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../../utils/dbConfig');
|
||||||
|
|
||||||
|
// Joi schema for request body validation
|
||||||
|
const schema = Joi.object({
|
||||||
|
email: Joi.string().email().required(),
|
||||||
|
verification_code_type: Joi.string().required(),
|
||||||
|
verification_code: Joi.string().pattern(new RegExp('^[0-9]+$')).required()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route for registering an account
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Validate request body
|
||||||
|
const { error, value } = schema.validate(req.body);
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).send(error.details[0].message);
|
||||||
|
}
|
||||||
|
const email = req.body.email;
|
||||||
|
const verificationCode = req.body.verification_code;
|
||||||
|
const verificationCodeType = req.body.verification_code_type;
|
||||||
|
|
||||||
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
|
return res.status(400).send('InvalidEmailFormat');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^\d+$/.test(verificationCode)) {
|
||||||
|
return res.status(400).send('InvalidVerificationCodeFormat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a prepared statement to check verification code
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Email', sql.VarChar, email);
|
||||||
|
request.input('VerificationCode', sql.VarChar, verificationCode);
|
||||||
|
request.input('VerificationCodeType', sql.VarChar, verificationCodeType);
|
||||||
|
const result = await request.execute('GetVerificationCode');
|
||||||
|
const row = result.recordset[0];
|
||||||
|
|
||||||
|
return res.status(200).send(row.Result);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Database query failed: ' + error.message);
|
||||||
|
return res.status(500).send('Database query failed: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
41
src/routes/launcher/launcherUpdater.js
Normal file
41
src/routes/launcher/launcherUpdater.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
const express = require('express');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { logger } = require('../../utils/logger');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Endpoint to get the launcher version from the launcher_info.ini file
|
||||||
|
router.get('/getLauncherVersion', (req, res) => {
|
||||||
|
const launcherInfoPath = path.join(__dirname, '..', '..', '..', 'public', 'launcher', 'launcher_update', 'launcher_info.ini');
|
||||||
|
fs.readFile(launcherInfoPath, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return res.status(500).send('Error reading launcher_info.ini');
|
||||||
|
}
|
||||||
|
const versionRegex = /version=(.*)/i;
|
||||||
|
const match = data.match(versionRegex);
|
||||||
|
if (match) {
|
||||||
|
const launcherVersion = match[1];
|
||||||
|
return res.json({ version: launcherVersion });
|
||||||
|
}
|
||||||
|
return res.status(500).send('Invalid launcher_info.ini format');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Endpoint to download the new launcher version from the launcher_update folder
|
||||||
|
router.post('/updateLauncherVersion', (req, res) => {
|
||||||
|
const launcherUpdatePath = path.join(__dirname, '..', '..', '..', 'public', 'launcher', 'launcher_update');
|
||||||
|
const version = req.body.version;
|
||||||
|
if (!req.body.version) {
|
||||||
|
return res.status(400).send('Missing version parameter');
|
||||||
|
}
|
||||||
|
const file = path.join(launcherUpdatePath, `launcher_${version}.zip`);
|
||||||
|
if (!fs.existsSync(file)) {
|
||||||
|
return res.status(404).send(`File ${file} not found`);
|
||||||
|
logger.error(`[Launcher Updater] File ${file} not found`);
|
||||||
|
}
|
||||||
|
res.download(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
100
src/routes/launcher/login.js
Normal file
100
src/routes/launcher/login.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { logger, accountLogger } = require('../../utils/logger');
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../../utils/dbConfig');
|
||||||
|
|
||||||
|
// Define the validation schema for the request body
|
||||||
|
const schema = Joi.object({
|
||||||
|
account: Joi.string().required(),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Validate the request body against the schema
|
||||||
|
const { error, value } = schema.validate(req.body);
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).send(error.details[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = value.account;
|
||||||
|
const password = value.password;
|
||||||
|
const userIp = req.ip;
|
||||||
|
|
||||||
|
// Check the format of the account identifier
|
||||||
|
if (
|
||||||
|
!/^[A-Za-z0-9_-]{6,50}$/.test(account) &&
|
||||||
|
!/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(account)
|
||||||
|
) {
|
||||||
|
return res.status(400).json({ Result: 'InvalidUsernameFormat' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a prepared statement to retrieve the account information
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Identifier', sql.VarChar, account);
|
||||||
|
const result = await request.execute('GetAccount');
|
||||||
|
const row = result.recordset[0];
|
||||||
|
|
||||||
|
if (row && row.Result === 'AccountExists') {
|
||||||
|
const windyCode = row.WindyCode;
|
||||||
|
const hash = row.AccountPwd;
|
||||||
|
|
||||||
|
// Verify the password
|
||||||
|
const md5_password = crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(windyCode + password)
|
||||||
|
.digest('hex');
|
||||||
|
const password_verify_result = await bcrypt.compare(
|
||||||
|
md5_password,
|
||||||
|
hash
|
||||||
|
);
|
||||||
|
|
||||||
|
const authRequest = pool.request();
|
||||||
|
authRequest.input('Identifier', sql.VarChar, account);
|
||||||
|
authRequest.input(
|
||||||
|
'password_verify_result',
|
||||||
|
sql.Bit,
|
||||||
|
password_verify_result
|
||||||
|
);
|
||||||
|
authRequest.input('LastLoginIP', sql.VarChar, userIp);
|
||||||
|
const authResult = await authRequest.execute('AuthenticateUser');
|
||||||
|
const authRow = authResult.recordset[0];
|
||||||
|
|
||||||
|
if (authRow && authRow.Result === 'LoginSuccess') {
|
||||||
|
accountLogger.info(
|
||||||
|
`[Account] Launcher Login: Account [${windyCode}] successfully logged in from [${userIp}]`
|
||||||
|
);
|
||||||
|
return res.status(200).json({
|
||||||
|
Result: authRow.Result,
|
||||||
|
Token: authRow.Token,
|
||||||
|
WindyCode: authRow.WindyCode,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
accountLogger.info(
|
||||||
|
`[Account] Launcher Login: Account [${windyCode}] login failed: ${authRow.Result} `
|
||||||
|
);
|
||||||
|
return res.status(400).json({
|
||||||
|
Result: authRow.Result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({ Result: 'AccountNotFound' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
'[Account] Launcher Login: Database query failed: ' + error.message
|
||||||
|
);
|
||||||
|
return res
|
||||||
|
.status(500)
|
||||||
|
.send('Database query failed: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
74
src/routes/launcher/passwordResetEmail.js
Normal file
74
src/routes/launcher/passwordResetEmail.js
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { logger, accountLogger } = require('../../utils/logger');
|
||||||
|
const { sendPasswordResetEmail } = require('../../mailer/mailer');
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../../utils/dbConfig');
|
||||||
|
|
||||||
|
// Joi schema for request body validation
|
||||||
|
const schema = Joi.object({
|
||||||
|
email: Joi.string().email().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route for registering an account
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Validate request body
|
||||||
|
const { error, value } = schema.validate(req.body);
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).send(error.details[0].message);
|
||||||
|
}
|
||||||
|
const email = req.body.email;
|
||||||
|
|
||||||
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
|
logger.info('Invalid email format');
|
||||||
|
return res.status(400).send('InvalidEmailFormat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a prepared statement to retrieve the account information
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Identifier', sql.VarChar, email);
|
||||||
|
const result = await request.execute('GetAccount');
|
||||||
|
const row = result.recordset[0];
|
||||||
|
|
||||||
|
if (row && row.Result === 'AccountExists') {
|
||||||
|
const emailAdress = row.Email;
|
||||||
|
const windycode = row.WindyCode;
|
||||||
|
const verificationCode = Math.floor(10000 + Math.random() * 90000).toString();
|
||||||
|
const expirationTime = new Date(Date.now() + 600000).toISOString(); // 10 minutes from now
|
||||||
|
|
||||||
|
// Prepare the second statement to insert the verification code information
|
||||||
|
const insertRequest = pool.request();
|
||||||
|
insertRequest.input('Email', sql.VarChar, email);
|
||||||
|
insertRequest.input('VerificationCode', sql.VarChar, verificationCode);
|
||||||
|
insertRequest.input('ExpirationTime', sql.DateTime, expirationTime);
|
||||||
|
const insertResult = await insertRequest.execute('SetPasswordVerificationCode');
|
||||||
|
const insertRow = insertResult.recordset[0];
|
||||||
|
|
||||||
|
if (insertRow && insertRow.Result === 'Success') {
|
||||||
|
|
||||||
|
// Send verification code email
|
||||||
|
sendPasswordResetEmail(email, verificationCode);
|
||||||
|
|
||||||
|
return res.status(200).send('EmailSent');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
accountLogger.info(`[Account] Failed to insert verification code for email: ${email}`);
|
||||||
|
return res.status(500).send(insertRow.Result);
|
||||||
|
}
|
||||||
|
} else if (row && row.Result === 'AccountNotFound') {
|
||||||
|
return res.status(400).send('AccountNotFound');
|
||||||
|
} else {
|
||||||
|
return res.status(500).send(row.Result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Account] Database query failed: ' + error.message);
|
||||||
|
return res.status(500).send('Database query failed: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
67
src/routes/launcher/register.js
Normal file
67
src/routes/launcher/register.js
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const { logger, accountLogger } = require('../../utils/logger');
|
||||||
|
const { sendConfirmationEmail } = require('../../mailer/mailer');
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../../utils/dbConfig');
|
||||||
|
|
||||||
|
// Joi schema for validating request data
|
||||||
|
const schema = Joi.object({
|
||||||
|
windyCode: Joi.string().alphanum().min(1).max(16).required(),
|
||||||
|
email: Joi.string().email().required(),
|
||||||
|
password: Joi.string().min(6).required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route for registering an account
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { error, value } = schema.validate(req.body);
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).send(error.details[0].message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const windyCode = value.windyCode;
|
||||||
|
const email = value.email;
|
||||||
|
const password = value.password;
|
||||||
|
const userIp = req.ip;
|
||||||
|
|
||||||
|
const md5_password = crypto.createHash('md5').update(windyCode + password).digest('hex'); // Generate MD5 hash
|
||||||
|
|
||||||
|
const passwordHash = await bcrypt.hash(md5_password, 10);
|
||||||
|
|
||||||
|
// Use a prepared statement to create the account
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('WindyCode', sql.VarChar, windyCode);
|
||||||
|
request.input('AccountPwd', sql.VarChar, passwordHash);
|
||||||
|
request.input('Email', sql.VarChar, email);
|
||||||
|
request.input('RegisterIP', sql.VarChar, userIp);
|
||||||
|
const result = await request.execute('CreateAccount');
|
||||||
|
const row = result.recordset[0];
|
||||||
|
|
||||||
|
if (row && row.Result === 'AccountCreated') {
|
||||||
|
accountLogger.info(`[Account] Account [${windyCode}] created successfully`);
|
||||||
|
sendConfirmationEmail(email, windyCode);
|
||||||
|
|
||||||
|
const clearRequest = pool.request();
|
||||||
|
clearRequest.input('Email', sql.VarChar, email);
|
||||||
|
const clearResult = await clearRequest.execute('ClearVerificationCode');
|
||||||
|
const clearRow = clearResult.recordset[0];
|
||||||
|
|
||||||
|
return res.status(200).send('Success');
|
||||||
|
} else {
|
||||||
|
accountLogger.info(`[Account] Account [${windyCode}] creation failed: ${row.Result}`);
|
||||||
|
return res.status(400).send(row.Result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Account] Database query failed: ' + error.message);
|
||||||
|
return res.status(500).send('Database query failed: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
68
src/routes/launcher/verificationEmail.js
Normal file
68
src/routes/launcher/verificationEmail.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Load environment variables
|
||||||
|
const env = require('../../utils/env');
|
||||||
|
|
||||||
|
const sql = require('mssql');
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { logger } = require('../../utils/logger');
|
||||||
|
const { sendVerificationEmail } = require('../../mailer/mailer');
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
const { connAccount } = require('../../utils/dbConfig');
|
||||||
|
|
||||||
|
// Joi schema for request body validation
|
||||||
|
const schema = Joi.object({
|
||||||
|
email: Joi.string().email().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route for registering an account
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Validate request body
|
||||||
|
const { error, value } = schema.validate(req.body);
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).send(error.details[0].message);
|
||||||
|
}
|
||||||
|
const email = req.body.email;
|
||||||
|
|
||||||
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
|
return res.status(400).send('InvalidEmailFormat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a prepared statement to retrieve the account information
|
||||||
|
const pool = await connAccount;
|
||||||
|
const request = pool.request();
|
||||||
|
request.input('Identifier', sql.VarChar, email);
|
||||||
|
const result = await request.execute('GetAccount');
|
||||||
|
const row = result.recordset[0];
|
||||||
|
|
||||||
|
if (row && row.Result === 'AccountNotFound') {
|
||||||
|
const verificationCode = Math.floor(10000 + Math.random() * 90000).toString();
|
||||||
|
const timeZone = process.env.TZ;
|
||||||
|
|
||||||
|
// Set the expiration time 10 minutes from now in the specified timezone
|
||||||
|
const expirationTime = new Date(Date.now() + 600000).toLocaleString('en-US', { timeZone });
|
||||||
|
|
||||||
|
// Prepare the second statement to insert the verification code information
|
||||||
|
const insertRequest = pool.request();
|
||||||
|
insertRequest.input('Email', sql.VarChar, email);
|
||||||
|
insertRequest.input('VerificationCode', sql.VarChar, verificationCode);
|
||||||
|
insertRequest.input('ExpirationTime', sql.DateTime, expirationTime);
|
||||||
|
const insertResult = await insertRequest.execute('SetAccountVerificationCode');
|
||||||
|
const insertRow = insertResult.recordset[0];
|
||||||
|
|
||||||
|
// Send verification code email
|
||||||
|
sendVerificationEmail(email, verificationCode);
|
||||||
|
|
||||||
|
return res.status(200).send('EmailSent');
|
||||||
|
} else {
|
||||||
|
return res.status(400).send(row.Result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Account] Database query failed: ' + error.message);
|
||||||
|
return res.status(500).send('Database query failed: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
21
src/utils/dbConfig.js
Normal file
21
src/utils/dbConfig.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
const sql = require('mssql');
|
||||||
|
const env = require('./env');
|
||||||
|
const { logger } = require('../utils/logger');
|
||||||
|
|
||||||
|
const dbConfig = {
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
server: process.env.DB_SERVER,
|
||||||
|
database: process.env.DB_DATABASE,
|
||||||
|
options: {
|
||||||
|
encrypt: process.env.DB_ENCRYPT === 'true'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const connAccount = new sql.ConnectionPool(dbConfig);
|
||||||
|
connAccount.connect();
|
||||||
|
logger.info(`Database: Connected.`);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
connAccount
|
||||||
|
};
|
||||||
6
src/utils/env.js
Normal file
6
src/utils/env.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
// Access environment variables with a function
|
||||||
|
module.exports = {
|
||||||
|
get: (key) => process.env[key],
|
||||||
|
};
|
||||||
144
src/utils/logger.js
Normal file
144
src/utils/logger.js
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const util = require("util");
|
||||||
|
const winston = require("winston");
|
||||||
|
|
||||||
|
const logsDirectory = 'logs';
|
||||||
|
|
||||||
|
if (!fs.existsSync(logsDirectory)) {
|
||||||
|
fs.mkdirSync(logsDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const authLogger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: `logs/auth-${new Date().toISOString().slice(0, 10)}.log`,
|
||||||
|
level: 'info',
|
||||||
|
filter: (log) => log.message.includes('[Auth]')
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.LOG_AUTH_CONSOLE === 'true') {
|
||||||
|
authLogger.add(new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple(),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const billingLogger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: `logs/billing-${new Date().toISOString().slice(0, 10)}.log`,
|
||||||
|
level: 'info',
|
||||||
|
filter: (log) => log.message.includes('[Billing]')
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.LOG_BILLING_CONSOLE === 'true') {
|
||||||
|
billingLogger.add(new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple(),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mailerLogger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: `logs/mailer-${new Date().toISOString().slice(0, 10)}.log`,
|
||||||
|
level: 'info',
|
||||||
|
filter: (log) => log.message.includes('[Mailer]')
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.LOG_MAILER_CONSOLE === 'true') {
|
||||||
|
mailerLogger.add(new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple(),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountLogger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: `logs/account-${new Date().toISOString().slice(0, 10)}.log`,
|
||||||
|
level: 'info',
|
||||||
|
filter: (log) => log.message.includes('[Account]')
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.LOG_ACCOUNT_CONSOLE === 'true') {
|
||||||
|
accountLogger.add(new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple(),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple(),
|
||||||
|
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: `logs/api-${new Date().toISOString().slice(0, 10)}.log`,
|
||||||
|
level: 'info'
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: `logs/error-${new Date().toISOString().slice(0, 10)}.log`,
|
||||||
|
level: 'error'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
authLogger,
|
||||||
|
billingLogger,
|
||||||
|
mailerLogger,
|
||||||
|
accountLogger,
|
||||||
|
logger
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue