From 8714005454c1e8b57b9950affefe7269fa0eb4a6 Mon Sep 17 00:00:00 2001 From: Faintastic <110469682+faintastic@users.noreply.github.com> Date: Tue, 8 Apr 2025 21:02:00 +0000 Subject: [PATCH 1/2] init: initialize the project :) --- README | 311 ++++++++++++++ README.md | 433 ------------------- javascript/.gitignore | 38 ++ javascript/jsconfig.json | 28 ++ javascript/package.json | 17 + javascript/src/index.js | 139 +++++++ javascript/src/keyauth.js | 848 ++++++++++++++++++++++++++++++++++++++ typescript/.gitignore | 38 ++ typescript/package.json | 20 + typescript/src/index.ts | 140 +++++++ typescript/src/keyauth.ts | 841 +++++++++++++++++++++++++++++++++++++ typescript/tsconfig.json | 113 +++++ 12 files changed, 2533 insertions(+), 433 deletions(-) create mode 100644 README delete mode 100644 README.md create mode 100644 javascript/.gitignore create mode 100644 javascript/jsconfig.json create mode 100644 javascript/package.json create mode 100644 javascript/src/index.js create mode 100644 javascript/src/keyauth.js create mode 100644 typescript/.gitignore create mode 100644 typescript/package.json create mode 100644 typescript/src/index.ts create mode 100644 typescript/src/keyauth.ts create mode 100644 typescript/tsconfig.json diff --git a/README b/README new file mode 100644 index 0000000..23b3d7a --- /dev/null +++ b/README @@ -0,0 +1,311 @@ +# KeyAuth-JS-Example : Please star 🌟 + +KeyAuth JavaScript / TypeScript example SDK for https://keyauth.cc license key API auth. + +_Javascript code is converted from TypeScript using this converter: https://transform.tools/typescript-to-javascript_ + +**Coded using [bun](https://bun.sh) runtime, it is highly recommended to use bun instead of node or npm, it is faster and way better.** + +## **Bugs** + +If you are using our example with no significant changes, and you are having problems, please Report Bug here https://keyauth.cc/app/?page=forms + +However, we do **NOT** provide support for adding KeyAuth to your project. If you can't figure this out you should use Google or YouTube to learn more about the programming language you want to sell a program in. + +## Copyright License + +KeyAuth is licensed under **Elastic License 2.0** + +* You may not provide the software to third parties as a hosted or managed +service, where the service provides users with access to any substantial set of +the features or functionality of the software. + +* You may not move, change, disable, or circumvent the license key functionality +in the software, and you may not remove or obscure any functionality in the +software that is protected by the license key. + +* You may not alter, remove, or obscure any licensing, copyright, or other notices +of the licensor in the software. Any use of the licensor’s trademarks is subject +to applicable law. + +Thank you for your compliance, we work hard on the development of KeyAuth and do not appreciate our copyright being infringed. + +## **What is KeyAuth?** + +KeyAuth is an Open source authentication system with cloud hosting plans as well. Client SDKs available for [C#](https://github.com/KeyAuth/KeyAuth-CSHARP-Example), [C++](https://github.com/KeyAuth/KeyAuth-CPP-Example), [Python](https://github.com/KeyAuth/KeyAuth-Python-Example), [Java](https://github.com/KeyAuth-Archive/KeyAuth-JAVA-api), [JavaScript](https://github.com/KeyAuth/KeyAuth-JavaScript-Example), [VB.NET](https://github.com/KeyAuth/KeyAuth-VB-Example), [PHP](https://github.com/KeyAuth/KeyAuth-PHP-Example), [Rust](https://github.com/KeyAuth/KeyAuth-Rust-Example), [Go](https://github.com/KeyAuth/KeyAuth-Go-Example), [Lua](https://github.com/mazkdevf/KeyAuth-Lua-Examples), [Ruby](https://github.com/mazkdevf/KeyAuth-Ruby-Example), and [Perl](https://github.com/mazkdevf/KeyAuth-Perl-Example). KeyAuth has several unique features such as memory streaming, webhook function where you can send requests to API without leaking the API, discord webhook notifications, ban the user securely through the application at your discretion. Feel free to join https://t.me/keyauth if you have questions or suggestions. + +## **Customer connection issues?** + +This is common amongst all authentication systems. Program obfuscation causes false positives in virus scanners, and with the scale of KeyAuth this is perceived as a malicious domain. So, `keyauth.com` and `keyauth.win` have been blocked by many internet providers. For dashboard, reseller panel, customer panel, use `keyauth.cc`. + +For API, `keyauth.cc` will not work because I purposefully blocked it on there so `keyauth.cc` doesn't get blocked also. So, you should create your own domain and follow this tutorial video https://www.youtube.com/watch?v=a2SROFJ0eYc. The tutorial video shows you how to create a domain name for 100% free if you don't want to purchase one. + +## **`KeyAuthApp` instance definition** + +Visit https://keyauth.cc/app/ and select your application, then click on the **Javascript** tab. + +It'll provide you with the code which you should replace in the `keyauth.ts` file. + +```typescript +const KeyAuthApp = new KeyAuth({ + name: "", // App name (Manage Applications --> Application name) + ownerid: "", // Owner ID (Account-Settings --> OwnerID) + version: "", +}); +``` + +## **Initialize application** + +```typescript +await KeyAuthApp.init(); +``` + +## **Display application information** + +```typescript +await KeyAuthApp.fetchStats(); +console.log(` +App data: +Number of users: ${KeyAuthApp.app_data?.numUsers} +Number of online users: ${KeyAuthApp.app_data?.onlineUsers} +Number of keys: ${KeyAuthApp.app_data?.numKeys} +Application Version: ${KeyAuthApp.app_data?.app_ver} +Customer panel link: ${KeyAuthApp.app_data?.customer_panel} +`); +``` + +## **Check session validation** + +Use this to see if the user is logged in or not. + +```typescript +console.log(`Current Session Validation Status: ${await KeyAuthApp.check()}`); +``` + +## **Check blacklist status** + +Check if HWID or IP Address is blacklisted. You can add this if you want, just to make sure nobody can open your program for less than a second if they're blacklisted. Though, if you don't mind a blacklisted user having the program for a few seconds until they try to login and register, and you care about having the quickest program for your users, you shouldn't use this function then. If a blacklisted user tries to login/register, the KeyAuth server will check if they're blacklisted and deny entry if so. So the check blacklist function is just an auxiliary function that's optional. + +```typescript +if (await KeyAuthApp.checkblacklist()) { + console.log("You've been blacklisted from our application."); + process.exit(1); +} +``` + +## **Login with username/password** + +```typescript +const username = "your_username"; +const password = "your_password"; +await KeyAuthApp.login(username, password); +``` + +## **Register with username/password/key** + +```typescript +const username = "your_username"; +const password = "your_password"; +const license = "your_license_key"; +await KeyAuthApp.register(username, password, license); +``` + +## **Upgrade user username/key** + +Used so the user can add extra time to their account by claiming a new key. + +> [!Warning] +> No password is needed to upgrade the account. So, unlike login, register, and license functions - you should **not** log the user in after a successful upgrade. + +```typescript +const username = "your_username"; +const license = "your_license_key"; +await KeyAuthApp.upgrade(username, license); +``` + +## **Login with just license key** + +Users can use this function if their license key has never been used before, and if it has been used before. So if you plan to just allow users to use keys, you can remove the login and register functions from your code. + +```typescript +const license = "your_license_key"; +await KeyAuthApp.license(license); +``` + +## **User Data** + +Show information for the current logged-in user. + +```typescript +console.log("\nUser data: "); +console.log("Username: " + KeyAuthApp.user_data?.username); +console.log("IP address: " + KeyAuthApp.user_data?.ip); +console.log("Hardware-Id: " + KeyAuthApp.user_data?.hwid); + +const subs = KeyAuthApp.user_data?.subscriptions || []; +subs.forEach((sub, index) => { + const expiry = new Date(sub.expiry * 1000).toISOString(); + console.log(`[${index + 1} / ${subs.length}] | Subscription: ${sub.subscription} - Expiry: ${expiry}`); +}); +console.log("Created at: " + new Date(KeyAuthApp.user_data?.createdate * 1000).toISOString()); +console.log("Last login at: " + new Date(KeyAuthApp.user_data?.lastlogin * 1000).toISOString()); +console.log("Expires at: " + new Date(KeyAuthApp.user_data?.expires * 1000).toISOString()); +console.log(`Current Session Validation Status: ${await KeyAuthApp.check()}`); +``` + +## **Show list of online users** + +```typescript +const onlineUsers = await KeyAuthApp.fetchOnline(); +let OU = ""; // KEEP THIS EMPTY FOR NOW, THIS WILL BE USED TO CREATE ONLINE USER STRING. +if (!onlineUsers) { + OU = "No online users"; +} else { + onlineUsers.forEach(user => { + OU += user.credential + " "; + }); +} + +console.log("\n" + OU + "\n"); +``` + +## **Application variables** + +A string that is kept on the server-side of KeyAuth. On the dashboard you can choose for each variable to be authenticated (only logged in users can access), or not authenticated (any user can access before login). These are global and static for all users, unlike User Variables which will be discussed below this section. + +```typescript +// Get normal variable and print it +const data = await KeyAuthApp.var("varName"); +console.log(data); +``` + +## **User Variables** + +User variables are strings kept on the server-side of KeyAuth. They are specific to users. They can be set on Dashboard in the Users tab, via SellerAPI, or via your loader using the code below. `discord` is the user variable name you fetch the user variable by. `test#0001` is the variable data you get when fetching the user variable. + +```typescript +// Set up user variable +await KeyAuthApp.setvar("varName", "varValue"); +``` + +And here's how you fetch the user variable: + +```typescript +// Get user variable and print it +const data = await KeyAuthApp.getvar("varName"); +console.log(data); +``` + +## **Application Logs** + +Can be used to log data. Good for anti-debug alerts and maybe error debugging. If you set Discord webhook in the app settings of the Dashboard, it will send log messages to your Discord webhook rather than store them on site. It's recommended that you set Discord webhook, as logs on site are deleted 1 month after being sent. + +You can use the log function before login & after login. + +```typescript +// Log message to the server and then to your webhook what is set on app settings +await KeyAuthApp.log("Message"); +``` + +## **Ban the user** + +Ban the user and blacklist their HWID and IP Address. Good function to call upon if you use anti-debug and have detected an intrusion attempt. + +Function only works after login. + +```typescript +await KeyAuthApp.ban(); +``` + +## **Enable Two Factor Authentication (2fa)** + +Enable two factor authentication (2fa) on a client account. + +```typescript +await KeyAuthApp.enable2fa(); +``` + +## **Disable Two Factor Authentication (2fa)** + +Disable two factor authentication (2fa) on a client account. + +```typescript +await KeyAuthApp.disable2fa(); +``` + +## **Logout session** + +Logout the user's session and close the application. + +This only works if the user is authenticated (logged in). + +```typescript +await KeyAuthApp.logout(); +``` + +## **Server-sided webhooks** + +Tutorial video https://www.youtube.com/watch?v=ENRaNPPYJbc + +> [!NOTE] +> Read documentation for KeyAuth webhooks here https://keyauth.readme.io/reference/webhooks-1 + +Send HTTP requests to URLs securely without leaking the URL in your application. You should definitely use if you want to send requests to SellerAPI from your application, otherwise if you don't use you'll be leaking your seller key to everyone. And then someone can mess up your application. + +1st example is how to send request with no POST data. just a GET request to the URL. `7kR0UedlVI` is the webhook ID, `https://keyauth.win/api/seller/?sellerkey=sellerkeyhere&type=black` is what you should put as the webhook endpoint on the dashboard. This is the part you don't want users to see. And then you have `&ip=1.1.1.1&hwid=abc` in your program code which will be added to the webhook endpoint on the keyauth server and then the request will be sent. + +2nd example includes post data. it is form data. it is an example request to the KeyAuth API. `7kR0UedlVI` is the webhook ID, `https://keyauth.win/api/1.2/` is the webhook endpoint. + +3rd examples included post data though it's JSON. It's an example request to Discord webhook `7kR0UedlVI` is the webhook ID, `https://discord.com/api/webhooks/...` is the webhook endpoint. + +```typescript +// Example to send normal request with no POST data +const data1 = await KeyAuthApp.webhook("7kR0UedlVI", "&ip=1.1.1.1&hwid=abc"); + +// Example to send form data +const data2 = await KeyAuthApp.webhook("7kR0UedlVI", "", "type=init&name=test&ownerid=j9Gj0FTemM", "application/x-www-form-urlencoded"); + +// Example to send JSON +const data3 = await KeyAuthApp.webhook("7kR0UedlVI", "", "{\"content\": \"webhook message here\",\"embeds\": null}", "application/json"); +``` + +## **Download file** + +> [!NOTE] +> Read documentation for KeyAuth files here https://docs.keyauth.cc/website/dashboard/files + +Keep files secure by providing KeyAuth your file download link on the KeyAuth dashboard. Make sure this is a direct download link (as soon as you go to the link, it starts downloading without you clicking anything). The KeyAuth download function provides the bytes, and then you get to decide what to do with those. This example shows how to write it to a file named `text.txt` in the same folder as the program, though you could execute with RunPE or whatever you want. + +`385624` is the file ID you get from the dashboard after adding file. + +```typescript +// Download Files from the server to your computer using the download function in the api class +const bytes = await KeyAuthApp.file("385624"); +const fs = require("fs"); +fs.writeFileSync("example.exe", bytes); +``` + +## **Chat channels** + +Allow users to communicate amongst themselves in your program. + +Example from the form example on how to fetch the chat messages. + +```typescript +// Get chat messages +const messages = await KeyAuthApp.chatGet("CHANNEL"); + +let Messages = ""; +messages.forEach(message => { + Messages += new Date(message.timestamp * 1000).toISOString() + " - " + message.author + ": " + message.message + "\n"; +}); + +console.log("\n\n" + Messages); +``` + +Example on how to send chat message. + +```typescript +// Send chat message +await KeyAuthApp.chatSend("MESSAGE", "CHANNEL"); +``` \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index dcaab2c..0000000 --- a/README.md +++ /dev/null @@ -1,433 +0,0 @@ -# KeyAuth-JavaScript-Example : Please star 🌟 - -KeyAuth JavaScript example SDK for https://keyauth.cc license key API auth. - -### Tutorial Video - -This video explains both how to use this example, but also how to add KeyAuth to your **OWN PROJECT** Link coming soon: - -## **Bugs** - -If you are using our example with no significant changes, and you are having problems, please Report Bug here https://keyauth.cc/app/?page=forms - -However, we do **NOT** provide support for adding KeyAuth to your project. If you can't figure this out you should use Google or YouTube to learn more about the programming language you want to sell a program in. - -## **Security practices** - -* Utilize obfuscation provided by companies such as VMProtect or Themida (utilize their SDKs too for greater protection) -* Preform frequent integrity checks to ensure the memory of the program has not been modified -* Don't write the bytes of a file you've downloaded to disk if you don't want that file to be retrieved by the user. Rather, execute the file in memory and erase it from memory the moment execution finishes - -While our API ensures licenses validation, it's crucial to implement robust client-side protection like obfuscation and integrity checks to prevent software tampering, as vulnerabilities often stem from insufficient client security. - -## Copyright License - -KeyAuth is licensed under **Elastic License 2.0** - -* You may not provide the software to third parties as a hosted or managed -service, where the service provides users with access to any substantial set of -the features or functionality of the software. - -* You may not move, change, disable, or circumvent the license key functionality -in the software, and you may not remove or obscure any functionality in the -software that is protected by the license key. - -* You may not alter, remove, or obscure any licensing, copyright, or other notices -of the licensor in the software. Any use of the licensor’s trademarks is subject -to applicable law. - -Thank you for your compliance, we work hard on the development of KeyAuth and do not appreciate our copyright being infringed. - -## What is KeyAuth? - -KeyAuth is an Open source authentication system with cloud hosting plans as well. Client SDKs available for [C#](https://github.com/KeyAuth/KeyAuth-CSHARP-Example), [C++](https://github.com/KeyAuth/KeyAuth-CPP-Example), [Python](https://github.com/KeyAuth/KeyAuth-Python-Example), [Java](https://github.com/KeyAuth-Archive/KeyAuth-JAVA-api), [JavaScript](https://github.com/mazkdevf/KeyAuth-JS-Example), [VB.NET](https://github.com/KeyAuth/KeyAuth-VB-Example), [PHP](https://github.com/KeyAuth/KeyAuth-PHP-Example), [Rust](https://github.com/KeyAuth/KeyAuth-Rust-Example), [Go](https://github.com/mazkdevf/KeyAuth-Go-Example), [Lua](https://github.com/mazkdevf/KeyAuth-Lua-Examples), [Ruby](https://github.com/mazkdevf/KeyAuth-Ruby-Example), and [Perl](https://github.com/mazkdevf/KeyAuth-Perl-Example). KeyAuth has several unique features such as memory streaming, webhook function where you can send requests to API without leaking the API, discord webhook notifications, ban the user securely through the application at your discretion. Feel free to join https://t.me/keyauth if you have questions or suggestions. - -> [!TIP] -> https://vaultcord.com FREE Discord bot to Backup server, members, channels, messages & more. Custom verify page, block alt accounts, VPNs & more. - -## Customer connection issues? - -This is common amongst all authentication systems. Program obfuscation causes false positives in virus scanners, and with the scale of KeyAuth this is perceived as a malicious domain. So, `keyauth.com` and `keyauth.win` have been blocked by many internet providers. for dashbord, reseller panel, customer panel, use `keyauth.cc` - -For API, `keyauth.cc` will not work because I purposefully blocked it on there so `keyauth.cc` doesn't get blocked also. So, you should create your own domain and follow this tutorial video https://www.youtube.com/watch?v=a2SROFJ0eYc. The tutorial video shows you how to create a domain name for 100% free if you don't want to purchase one. - -## `KeyAuthApp` instance definition - -Visit https://keyauth.cc/app/ and select your application, then click on the **C#** tab - -It'll provide you with the code which you should replace with in the `Program.cs` file (or `Login.cs` file if using Form example) - -```cs -public static api KeyAuthApp = new api( - name: "example", - ownerid: "JjPMBVlIOd", - secret: "db40d586f4b189e04e5c18c3c94b7e72221be3f6551995adc05236948d1762bc", - version: "1.0" -); -``` - -## Initialize application - -You must call this function prior to using any other KeyAuth function. Otherwise the other KeyAuth function won't work. - -```cs -KeyAuthApp.init(); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -``` - -## Display application information - -```cs -KeyAuthApp.fetchStats(); -Console.WriteLine("\n Application Version: " + KeyAuthApp.app_data.version); -Console.WriteLine(" Customer panel link: " + KeyAuthApp.app_data.customerPanelLink); -Console.WriteLine(" Number of users: " + KeyAuthApp.app_data.numUsers); -Console.WriteLine(" Number of online users: " + KeyAuthApp.app_data.numOnlineUsers); -Console.WriteLine(" Number of keys: " + KeyAuthApp.app_data.numKeys); -``` - -## Check session validation - -Use this to see if the user is logged in or not. - -```cs -KeyAuthApp.check(); -Console.WriteLine($" Current Session Validation Status: {KeyAuthApp.response.message}"); -``` - -## Check blacklist status - -Check if HWID or IP Address is blacklisted. You can add this if you want, just to make sure nobody can open your program for less than a second if they're blacklisted. Though, if you don't mind a blacklisted user having the program for a few seconds until they try to login and register, and you care about having the quickest program for your users, you shouldn't use this function then. If a blacklisted user tries to login/register, the KeyAuth server will check if they're blacklisted and deny entry if so. So the check blacklist function is just auxiliary function that's optional. - -```cs -if(KeyAuthApp.checkblack()) { - Environment.Exit(0); // terminate program if user blacklisted. -} -``` - -## Login with username/password - -```cs -string username; -string password; -Console.WriteLine("\n\n Enter username: "); -username = Console.ReadLine(); -Console.WriteLine("\n\n Enter password: "); -password = Console.ReadLine(); -KeyAuthApp.login(username, password); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -``` - -## Register with username/password/key - -```cs -string username, password, key, email; -Console.Write("\n\n Enter username: "); -username = Console.ReadLine(); -Console.Write("\n\n Enter password: "); -password = Console.ReadLine(); -Console.Write("\n\n Enter license: "); -key = Console.ReadLine(); -Console.Write("\n\n Enter email (just press enter if none): "); -email = Console.ReadLine(); -KeyAuthApp.register(username, password, key, email); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -``` - -## Upgrade user username/key - -Used so the user can add extra time to their account by claiming new key. - -> [!WARNING] -> No password is needed to upgrade account. So, unlike login, register, and license functions - you should **not** log user in after successful upgrade. - -``` -string username; -string password; -string key; -Console.WriteLine("\n\n Enter username: "); -username = Console.ReadLine(); -Console.WriteLine("\n\n Enter license: "); -key = Console.ReadLine(); -KeyAuthApp.upgrade(username, key); -// don't proceed to app, user hasn't authenticated yet. -Console.WriteLine("\n Status: " + KeyAuthApp.response.message); -Thread.Sleep(2500); -Environment.Exit(0); -``` - -## Login with just license key - -Users can use this function if their license key has never been used before, and if it has been used before. So if you plan to just allow users to use keys, you can remove the login and register functions from your code. - -```cs -string key; -Console.WriteLine("\n\n Enter license: "); -key = Console.ReadLine(); -KeyAuthApp.license(key); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -``` - -## Login with web loader - -Have your users login through website. Tutorial video here https://www.youtube.com/watch?v=9-qgmsUUCK4 you can use your own domain for customer panel also, https://www.youtube.com/watch?v=iHQe4GLvgaE - -```cs -KeyAuthApp.web_login(); - -Console.WriteLine("\n Waiting for button to be clicked"); -KeyAuthApp.button("close"); -``` - -## Forgot password - -Allow users to enter their account information and recieve an email to reset their password. - -```cs -string username, email; -Console.Write("\n\n Enter username: "); -username = Console.ReadLine(); -Console.Write("\n\n Enter email: "); -email = Console.ReadLine(); -KeyAuthApp.forgot(username, email); -// don't proceed to app, user hasn't authenticated yet. -Console.WriteLine("\n Status: " + KeyAuthApp.response.message); -Thread.Sleep(2500); -Environment.Exit(0); -``` - -## User Data - -Show information for current logged-in user. - -```cs -Console.WriteLine("\n User data:"); -Console.WriteLine(" Username: " + KeyAuthApp.user_data.username); -Console.WriteLine(" IP address: " + KeyAuthApp.user_data.ip); -Console.WriteLine(" Hardware-Id: " + KeyAuthApp.user_data.hwid); -Console.WriteLine(" Created at: " + UnixTimeToDateTime(long.Parse(KeyAuthApp.user_data.createdate))); -if (!String.IsNullOrEmpty(KeyAuthApp.user_data.lastlogin)) // don't show last login on register since there is no last login at that point - Console.WriteLine(" Last login at: " + UnixTimeToDateTime(long.Parse(KeyAuthApp.user_data.lastlogin))); -Console.WriteLine(" Your subscription(s):"); -for (var i = 0; i < KeyAuthApp.user_data.subscriptions.Count; i++) -{ - Console.WriteLine(" Subscription name: " + KeyAuthApp.user_data.subscriptions[i].subscription + " - Expires at: " + UnixTimeToDateTime(long.Parse(KeyAuthApp.user_data.subscriptions[i].expiry)) + " - Time left in seconds: " + KeyAuthApp.user_data.subscriptions[i].timeleft); -} -``` - -## Check subscription name of user - -If you want to wall off parts of your app to only certain users, you can have multiple subscriptions with different names. Then, when you create licenses that correspond to the level of that subscription, users who use those licenses will get a subscription with the name of the subscription that corresponds to the level of the license key they used. The `SubExist` function is in the `Program.cs` file - -```cs -if (SubExist("default")) -{ - Console.WriteLine(" Default Subscription Exists"); -} -// See if another sub exists with name -if (SubExist("premium")) -{ - Console.WriteLine(" Premium Subscription Exists"); -} -``` - -## Show list of online users - -```cs -var onlineUsers = KeyAuthApp.fetchOnline(); -if (onlineUsers != null) -{ - Console.Write("\n Online users: "); - foreach (var user in onlineUsers) - { - Console.Write(user.credential + ", "); - } - Console.WriteLine("\n"); -} -``` - -## Application variables - -A string that is kept on the server-side of KeyAuth. On the dashboard you can choose for each variable to be authenticated (only logged in users can access), or not authenticated (any user can access before login). These are global and static for all users, unlike User Variables which will be dicussed below this section. - -```cs -string appvar = KeyAuthApp.var("variableNameHere"); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -else - Console.WriteLine("\n App variable data: " + appvar); -``` - -## User Variables - -User variables are strings kept on the server-side of KeyAuth. They are specific to users. They can be set on Dashboard in the Users tab, via SellerAPI, or via your loader using the code below. `discord` is the user variable name you fetch the user variable by. `test#0001` is the variable data you get when fetching the user variable. - -```cs -KeyAuthApp.setvar("discord", "test#0001"); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -else - Console.WriteLine("\n Successfully set user variable"); -``` - -And here's how you fetch the user variable: - -```cs -string uservar = KeyAuthApp.getvar("discord"); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -else - Console.WriteLine("\n User variable value: " + uservar); -``` - -## Application Logs - -Can be used to log data. Good for anti-debug alerts and maybe error debugging. If you set Discord webhook in the app settings of the Dashboard, it will send log messages to your Discord webhook rather than store them on site. It's recommended that you set Discord webhook, as logs on site are deleted 1 month after being sent. - -You can use the log function before login & after login. - -```cs -KeyAuthApp.log("hello I wanted to log this"); -``` - -## Ban the user - -Ban the user and blacklist their HWID and IP Address. Good function to call upon if you use anti-debug and have detected an intrusion attempt. - -Function only works after login. - -```cs -KeyAuthApp.ban(); -``` - -## Ban the user (with reason) - -Ban the user and blacklist their HWID and IP Address. Good function to call upon if you use anti-debug and have detected an intrusion attempt. - -Function only works after login. - -The reason paramater will be the ban reason displayed to the user if they try to login, and visible on the KeyAuth dashboard. - -```cs -KeyAuthApp.ban("You've been banned for a reason."); -``` - -## Server-sided webhooks - -Tutorial video https://www.youtube.com/watch?v=ENRaNPPYJbc - -> [!NOTE] -> Read documentation for KeyAuth webhooks here https://keyauth.readme.io/reference/webhooks-1 - -Send HTTP requests to URLs securely without leaking the URL in your application. You should definitely use if you want to send requests to SellerAPI from your application, otherwise if you don't use you'll be leaking your seller key to everyone. And then someone can mess up your application. - -1st example is how to send request with no POST data. just a GET request to the URL. `7kR0UedlVI` is the webhook ID, `https://keyauth.win/api/seller/?sellerkey=sellerkeyhere&type=black` is what you should put as the webhook endpoint on the dashboard. This is the part you don't want users to see. And then you have `&ip=1.1.1.1&hwid=abc` in your program code which will be added to the webhook endpoint on the keyauth server and then the request will be sent. - -2nd example includes post data. it is form data. it is an example request to the KeyAuth API. `7kR0UedlVI` is the webhook ID, `https://keyauth.win/api/1.2/` is the webhook endpoint. - -3rd examples included post data though it's JSON. It's an example reques to Discord webhook `7kR0UedlVI` is the webhook ID, `https://discord.com/api/webhooks/...` is the webhook endpoint. - -```cs -// example to send normal request with no POST data -string resp = KeyAuthApp.webhook("7kR0UedlVI", "&ip=1.1.1.1&hwid=abc"); - -// example to send form data -resp = KeyAuthApp.webhook("7kR0UedlVI", "", "type=init&name=test&ownerid=j9Gj0FTemM", "application/x-www-form-urlencoded"); - -// example to send JSON -resp = KeyAuthApp.webhook("7kR0UedlVI", "", "{\"content\": \"webhook message here\",\"embeds\": null}", "application/json"); // if Discord webhook message successful, response will be empty - -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -else - Console.WriteLine("\n Response received from webhook request: " + resp); -``` - -## Download file - -> [!NOTE] -> Read documentation for KeyAuth files here https://docs.keyauth.cc/website/dashboard/files - -Keep files secure by providing KeyAuth your file download link on the KeyAuth dashboard. Make sure this is a direct download link (as soon as you go to the link, it starts downloading without you clicking anything). The KeyAuth download function provides the bytes, and then you get to decide what to do with those. This example shows how to write it to a file named `text.txt` in the same folder as the program, though you could execute with RunPE or whatever you want. - -`385624` is the file ID you get from the dashboard after adding file. - -```cs -byte[] result = KeyAuthApp.download("385624"); -if (!KeyAuthApp.response.success) -{ - Console.WriteLine("\n Status: " + KeyAuthApp.response.message); - Thread.Sleep(2500); - Environment.Exit(0); -} -else - File.WriteAllBytes(Directory.GetCurrentDirectory() + "\\test.txt", result); -``` - -## Chat channels - -Allow users to communicate amongst themselves in your program. - -Example from the form example on how to fetch the chat messages. - -```cs -var messages = Login.KeyAuthApp.chatget("chatChannelNameHere"); -if (messages == null) -{ - dataGridView1.Rows.Insert(0, "KeyAuth", "No Chat Messages", UnixTimeToDateTime(DateTimeOffset.Now.ToUnixTimeSeconds())); -} -else -{ - foreach (var message in messages) - { - dataGridView1.Rows.Insert(0, message.author, message.message, UnixTimeToDateTime(long.Parse(message.timestamp))); - } -} -``` - -Example from the form example on how to send chat message. - -```cs -if (Login.KeyAuthApp.chatsend(chatmsg.Text, chatchannel)) -{ - dataGridView1.Rows.Insert(0, Login.KeyAuthApp.user_data.username, chatmsg.Text, UnixTimeToDateTime(DateTimeOffset.Now.ToUnixTimeSeconds())); -} -else - chatmsg.Text = "Status: " + Login.KeyAuthApp.response.message; -``` diff --git a/javascript/.gitignore b/javascript/.gitignore new file mode 100644 index 0000000..b9c7e4a --- /dev/null +++ b/javascript/.gitignore @@ -0,0 +1,38 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +# Bun runtime (https://bun.sh/) +bun.lock +bun.lockb \ No newline at end of file diff --git a/javascript/jsconfig.json b/javascript/jsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/javascript/jsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/javascript/package.json b/javascript/package.json new file mode 100644 index 0000000..3e67723 --- /dev/null +++ b/javascript/package.json @@ -0,0 +1,17 @@ +{ + "name": "keyauth-js-example", + "module": "src/index.js", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "@types/qrcode": "^1.5.5" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "discord-interactions": "^4.1.1", + "qrcode": "^1.5.4", + "tweetnacl": "^1.0.3" + } +} diff --git a/javascript/src/index.js b/javascript/src/index.js new file mode 100644 index 0000000..d7cfc60 --- /dev/null +++ b/javascript/src/index.js @@ -0,0 +1,139 @@ +import KeyAuth from "./keyauth" +import { createInterface } from "readline/promises" + +const readline = createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}) + +const KeyAuthApp = new KeyAuth({ + name: "", + ownerid: "", + version: "", +}) + +async function answer() { + try { + await KeyAuthApp.init() + + console.log("[1] Login\n[2] Register\n[3] License\n[4] Upgrade") + + const optionRaw = await readline.question("Select an option: ") + const option = parseInt(optionRaw) + + let username = "", + password = "", + license = "" + + switch (option) { + case 1: + username = await readline.question("Username: ") + password = await readline.question("Password: ") + await KeyAuthApp.login(username, password) + dashboard() + break + + case 2: + username = await readline.question("Username: ") + password = await readline.question("Password: ") + license = await readline.question("License: ") + await KeyAuthApp.register(username, password, license) + dashboard() + break + + case 3: + license = await readline.question("License: ") + await KeyAuthApp.license(license) + dashboard() + break + + case 4: + username = await readline.question("Username: ") + license = await readline.question("License: ") + await KeyAuthApp.upgrade(username, license) + dashboard() + break + + default: + console.log("Invalid option selected.") + break + } + } catch (error) { + if (error instanceof Error) { + console.error("An error occurred:", error.message) + } else { + console.error("An unknown error occurred:", error) + } + } +} + +answer() + +async function dashboard() { + await KeyAuthApp.fetchStats() + console.log("Application data:") + console.log(" App Version: ", KeyAuthApp.app_data?.app_ver) + console.log(" Customer panel: ", KeyAuthApp.app_data?.customer_panel) + console.log(" Number of Keys: ", KeyAuthApp.app_data?.numKeys) + console.log(" Number of Users: ", KeyAuthApp.app_data?.numUsers) + console.log(" Online Users: ", KeyAuthApp.app_data?.onlineUsers) + + console.log("\nUser data:") + console.log(" Username: ", KeyAuthApp.user_data?.username) + console.log(" IP Address: ", KeyAuthApp.user_data?.ip) + console.log(" Hardware-id: ", KeyAuthApp.user_data?.hwid) + + const subs = KeyAuthApp.user_data?.subscriptions || [] + + for (let i = 0; i < subs.length; i++) { + const sub = subs[i] + const expiry = new Date(Number(sub.expiry) * 1000) + + console.log( + `[${i + 1}/${subs.length}] | Subscription: ${ + sub.subscription + } - Expiry: ${expiry.toLocaleString()}` + ) + } + + console.log( + `Created at: ${new Date( + (KeyAuthApp.user_data?.createdate || 0) * 1000 + ).toLocaleString()}` + ) + console.log( + `Last Login: ${new Date( + (KeyAuthApp.user_data?.lastlogin || 0) * 1000 + ).toLocaleString()}` + ) + console.log( + `Expires: ${new Date( + (KeyAuthApp.user_data?.expires || 0) * 1000 + ).toLocaleString()}` + ) + + console.log("\n2-factor authentication:") + console.log("[1] Enable\n[2] Disable") + + const optionRaw = await readline.question("Select an option: ") + const option = parseInt(optionRaw) + + switch (option) { + case 1: + await KeyAuthApp.enable2fa() + break + case 2: + await KeyAuthApp.disable2fa() + break + default: + console.log("Invalid option selected.") + break + } + + console.log("Closing app in 10 seconds...") + await new Promise(resolve => setTimeout(resolve, 10000)) + readline.close() + await KeyAuthApp.logout() + process.exit(0) +} diff --git a/javascript/src/keyauth.js b/javascript/src/keyauth.js new file mode 100644 index 0000000..ffdf77d --- /dev/null +++ b/javascript/src/keyauth.js @@ -0,0 +1,848 @@ +import { Buffer } from "buffer" +import { createInterface } from "readline" +import { execSync, exec } from "child_process" +import { verifyKey } from "discord-interactions" + +import os from "os" +import fs from "fs" +import crypto from "crypto" +import * as QRCode from "qrcode" + +const readline = createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}) + +export default class KeyAuth { + url = "https://keyauth.win/api/1.3/" + public_key = + "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b" + loggingEnabled = true + + initialized = false + + user_data = null + app_data = null + + constructor(options) { + this.name = options.name + this.ownerid = options.ownerid + this.version = options.version + this.hash_to_check = options.hash_to_check + this.url = options.url ?? "https://keyauth.win/api/1.3/" + this.path = options.path + + if (!this.name || !this.ownerid || !this.version) { + throw new Error("Name, ownerid, and version are required") + } + } + + async init() { + if (this.sessionid && this.initialized) { + console.log("Application already initialized") + return + } + + let token = "" + + if (this.path) { + try { + token = fs.readFileSync(this.path, "utf-8").trim() + } catch (error) { + console.error(`Failed to read file at path ${this.path}:`, error) + this.sleep(5000) + process.exit(0) + } + } + + const post_data = { + type: "init", + name: this.name, + ownerid: this.ownerid, + version: this.version, + hash: this.hash_to_check, + ...(this.path && { + token: token, + thash: crypto + .createHash("sha256") + .update(token) + .digest("hex") + }) + } + + const response = await this.__do_request(post_data) + + if (response === "KeyAuth_Invalid") { + console.log("This application does not exist") + this.sleep(5000) + process.exit(0) + } + + if (response["message"] === "invalidver") { + if (response["download"]) { + console.log("Your application is outdated.") + exec(`start ${response["download"]}`, error => { + if (error) { + console.error("Failed to open the download link:", error) + } + }) + this.sleep(5000) + process.exit(0) + } else { + console.log( + "Your application is outdated and no download link was provided, contact the owner for the latest app version." + ) + this.sleep(5000) + process.exit(0) + } + } + + if (response["success"] === false) { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + + this.sessionid = response["sessionid"] + this.initialized = true + } + + async register(username, password, license, hwid) { + this.checkinit() + if (!hwid) hwid = this.get_hwid() + + const post_data = { + type: "register", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + username: username, + pass: password, + key: license, + hwid: hwid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + console.log(response["message"]) + this.__load_user_data(response["info"]) + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async upgrade(username, license) { + this.checkinit() + + const post_data = { + type: "upgrade", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + username: username, + key: license + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + console.log(response["message"]) + console.log("Restart the application to apply the changes.") + this.sleep(5000) + process.exit(0) + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async login(username, password, code, hwid) { + this.checkinit() + if (!hwid) hwid = this.get_hwid() + + const post_data = { + type: "register", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + username: username, + pass: password, + hwid: hwid, + + ...(code && { code: code }) + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + console.log(response["message"]) + this.__load_user_data(response["info"]) + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async license(license, code, hwid) { + this.checkinit() + if (!hwid) hwid = this.get_hwid() + + const post_data = { + type: "license", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + key: license, + hwid: hwid, + + ...(code && { code: code }) + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + console.log(response["message"]) + this.__load_user_data(response["info"]) + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async var(name) { + this.checkinit() + + const post_data = { + type: "var", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + varid: name + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return response["message"] + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async getvar(name) { + this.checkinit() + + const post_data = { + type: "getvar", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + var: name + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return response["message"] + } else { + console.log( + 'NOTE: This is commonly misunderstood. This is for user variables, not the normal variables.\nUse KeyAuthApp.var("{var_name}") for normal variables' + ) + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async setvar(name, value) { + this.checkinit() + + const post_data = { + type: "setvar", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + varid: name, + data: value + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return true + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async ban() { + this.checkinit() + + const post_data = { + type: "ban", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return true + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async file(id) { + this.checkinit() + + const post_data = { + type: "file", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + fileid: id + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return Buffer.from(response["contents"], "hex") + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async webhook(id, param, body, conttype) { + this.checkinit() + + const post_data = { + type: "webhook", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + webid: id, + params: param, + body: body, + conttype: conttype + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return response["message"] + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async check() { + this.checkinit() + + const post_data = { + type: "check", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return true + } else { + return false + } + } + + async checkblacklist() { + this.checkinit() + const hwid = this.get_hwid() + + const post_data = { + type: "checkblacklist", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + hwid: hwid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return true + } else { + return false + } + } + + async log(message) { + this.checkinit() + + const post_data = { + type: "log", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + message: message, + pcuser: os.userInfo().username + } + + await this.__do_request(post_data) + } + + async fetchOnline() { + this.checkinit() + + const post_data = { + type: "fetchOnline", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + if (Number(response["users"]) === 0) { + return null + } else { + return Number(response["users"]) + } + } else { + return false + } + } + + async fetchStats() { + this.checkinit() + + const post_data = { + type: "fetchStats", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + this.__load_app_data(response["appinfo"]) + } + } + + async chatGet(channel) { + this.checkinit() + + const post_data = { + type: "chatget", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + channel: channel + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return response["messages"] + } else { + return false + } + } + + async chatSend(message, channel) { + this.checkinit() + + const post_data = { + type: "chatsend", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + channel: channel, + message: message + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + return true + } else { + return false + } + } + + async changeUsername(username) { + this.checkinit() + + const post_data = { + type: "changeUsername", + newUsername: username, + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + console.log("Username changed successfully") + } else { + return false + } + } + + async logout() { + this.checkinit() + + const post_data = { + type: "logout", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + console.log("Logged out successfully") + this.sleep(5000) + process.exit(0) + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async enable2fa(code) { + this.checkinit() + + const post_data = { + type: "2faenable", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + ...(code && { code: code }) + } + + const response = await this.__do_request(post_data) + + if (response["success"] === true) { + if (!code) { + console.log(response) + console.log( + "Your 2fa secret code is: " + response["2fa"]["secret_code"] + ) + + this.displayQrCode(response["2fa"]["QRCode"]) + console.log( + "Please enter the code you received in your authenticator app." + ) + + const userCode = await new Promise(resolve => { + readline.question("Enter the code: ", input => { + resolve(input) + }) + }) + + await this.enable2fa(userCode) // Recursively call with the entered code + } else { + console.log("2FA enabled successfully") + } + } else { + console.log(response["message"]) + this.sleep(5000) + process.exit(0) + } + } + + async disable2fa() { + this.checkinit() + + const code = await new Promise(resolve => { + readline.question("Enter the code: ", input => { + resolve(input) + }) + }) + + const post_data = { + type: "2fadisable", + name: this.name, + ownerid: this.ownerid, + sessionid: this.sessionid, + code: code + } + + const response = await this.__do_request(post_data) + + console.log(response["message"]) + await this.sleep(5000) + } + + async checkinit() { + if (!this.sessionid && !this.initialized) { + console.log("Application not initialized") + this.sleep(5000) + process.exit(0) + } + } + + get_hwid() { + const platform = os.platform() + + if (platform === "linux") { + try { + const hwid = fs.readFileSync("/etc/machine-id", "utf-8").trim() + return hwid + } catch (error) { + console.error("Error reading /etc/machine-id:", error) + throw new Error("Failed to retrieve HWID on Linux") + } + } else if (platform === "win32") { + try { + const winUser = os.userInfo().username + const sidOutput = execSync( + `wmic useraccount where name='${winUser}' get sid` + ) + .toString() + .split("\n") + const sid = sidOutput[1]?.trim() + if (!sid) { + throw new Error("Failed to retrieve SID on Windows: SID is undefined") + } + return sid + } catch (error) { + console.error("Error retrieving SID on Windows:", error) + throw new Error("Failed to retrieve HWID on Windows") + } + } else if (platform === "darwin") { + try { + const output = execSync( + "ioreg -l | grep IOPlatformSerialNumber" + ).toString() + const parts = output.split("=") + const serial = parts[1]?.trim().replace(/"/g, "") ?? "" + return serial + } catch (error) { + console.error("Error retrieving serial number on macOS:", error) + throw new Error("Failed to retrieve HWID on macOS") + } + } else { + throw new Error("Unsupported platform for HWID retrieval") + } + } + + async displayQrCode(qrCodeUrl) { + try { + const qrCode = await QRCode.toDataURL(qrCodeUrl) + + const base64Data = qrCode.split(",")[1] + if (!base64Data) { + throw new Error("Invalid QR code data") + } + const img = Buffer.from(base64Data, "base64") + + const outputPath = this.path ?? "qrcode.png" + fs.writeFileSync(outputPath, img) + + const platform = os.platform() + let openCommand + + if (platform === "win32") { + openCommand = `start ${outputPath}` + } else if (platform === "darwin") { + openCommand = `open ${outputPath}` + } else if (platform === "linux") { + openCommand = `xdg-open ${outputPath}` + } else { + console.error("Unsupported platform for opening the QR code image") + return + } + + exec(openCommand, error => { + if (error) { + console.error("Failed to display the QR code image:", error) + } + }) + } catch (error) { + console.error("Failed to generate QR code:", error) + } + } + + getCheckSum() { + const md5Hash = crypto.createHash("md5") + const file = fs.readFileSync(process.argv.slice(2).join(""), { + encoding: "binary" + }) + md5Hash.update(file) + const digest = md5Hash.digest("hex") + return digest + } + + // Private functions (cannot be called from outside the class) + async sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + async __do_request(data) { + try { + const response = await fetch(this.url, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + // @ts-ignore - Why does it error? No fucking clue but it works + body: new URLSearchParams(data).toString() + }) + + if (!response.ok) { + console.error(`HTTP error! Status: ${response.status}`) + this.sleep(5000) + process.exit(0) + } + + const responseData = await response.json() + const dontRunExtra = ["log", "file", "2faenable", "2fadisable"] + if (dontRunExtra.includes(data.type)) { + return responseData + } + + const signature = response.headers.get("x-signature-ed25519") + const timestamp = response.headers.get("x-signature-timestamp") + if (!signature || !timestamp) { + console.log("Missing signature or timestamp in response headers") + this.sleep(5000) + process.exit(0) + } + + const server_time = new Date(Number(timestamp) * 1000).toUTCString() + const current_time = new Date().toUTCString() + + const buffer_seconds = 5 // Allowable time difference in seconds + const time_difference = + Math.abs( + new Date(server_time).getTime() - new Date(current_time).getTime() + ) / 1000 + + if (time_difference > buffer_seconds + 20) { + console.log( + `Time difference is too large: ${time_difference} seconds, try syncing your date and time settings.` + ) + this.sleep(5000) + process.exit(0) + } + + if ( + !verifyKey( + Buffer.from(JSON.stringify(responseData), "utf-8"), + signature, + timestamp, + this.public_key + ) + ) { + console.log( + "Signature checksum failed. Request was tampered with or session ended most likely." + ) + await this.sleep(3000) + process.exit(0) + } + + this.logEvent(JSON.stringify(responseData) + "\n") + + return responseData + } catch (error) { + console.error("Unexpected error:", error) + this.sleep(5000) + process.exit(0) + } + } + + logEvent(message) { + console.log(message) + if (!this.loggingEnabled) return + + const exeName = + process.argv[1].split("\\").pop() || process.argv[1].split("/").pop() + const logDirectory = `${os.homedir()}/AppData/Roaming/KeyAuth/debug/${exeName}` + + try { + if (!fs.existsSync(logDirectory)) { + fs.mkdirSync(logDirectory, { recursive: true }) + } + + const logFileName = `${new Date() + .toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric" + }) + .replace(/ /g, "_")}_logs.txt` + const logFilePath = `${logDirectory}/${logFileName}` + + // Redact sensitive fields + message = this.redactField(message, "sessionid") + message = this.redactField(message, "ownerid") + message = this.redactField(message, "app") + message = this.redactField(message, "version") + message = this.redactField(message, "fileid") + message = this.redactField(message, "webhooks") + message = this.redactField(message, "nonce") + + const logMessage = `[${new Date().toISOString()}] [${exeName}] ${message}\n` + fs.appendFileSync(logFilePath, logMessage, "utf-8") + } catch (error) { + if (error instanceof Error) { + console.error(`Error logging data: ${error.message}`) + } else { + console.error("Error logging data: Unknown error") + } + } + } + + redactField(content, field) { + const regex = new RegExp(`"${field}":\\s*".*?"`, "g") + return content.replace(regex, `"${field}": "[REDACTED]"`) + } + + __load_app_data(data) { + this.app_data = { + numUsers: data["numUsers"], + numKeys: data["numKeys"], + app_ver: data["version"], + customer_panel: data["customerPanelLink"], + onlineUsers: data["numOnlineUsers"] + } + } + + __load_user_data(data) { + this.user_data = { + username: data["username"], + ip: data["ip"], + hwid: data["hwid"] || "N/A", + expires: data["subscriptions"][0]["expiry"], + createdate: data["createdate"], + lastlogin: data["lastlogin"], + subscription: data["subscriptions"][0]["subscription"], + subscriptions: data["subscriptions"] + } + } +} diff --git a/typescript/.gitignore b/typescript/.gitignore new file mode 100644 index 0000000..b9c7e4a --- /dev/null +++ b/typescript/.gitignore @@ -0,0 +1,38 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +# Bun runtime (https://bun.sh/) +bun.lock +bun.lockb \ No newline at end of file diff --git a/typescript/package.json b/typescript/package.json new file mode 100644 index 0000000..cf11ed5 --- /dev/null +++ b/typescript/package.json @@ -0,0 +1,20 @@ +{ + "name": "keyauth-js-example", + "module": "src/index.ts", + "type": "module", + "scripts": { + "compile": "tsc" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/qrcode": "^1.5.5" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "discord-interactions": "^4.1.1", + "qrcode": "^1.5.4", + "tweetnacl": "^1.0.3" + } +} diff --git a/typescript/src/index.ts b/typescript/src/index.ts new file mode 100644 index 0000000..0487f5d --- /dev/null +++ b/typescript/src/index.ts @@ -0,0 +1,140 @@ +import KeyAuth from "./keyauth"; +import { createInterface } from "readline/promises"; + +const readline = createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false, +}); + +const KeyAuthApp = new KeyAuth({ + name: "", + ownerid: "", + version: "", +}); + +async function answer() { + try { + await KeyAuthApp.init(); + + console.log("[1] Login\n[2] Register\n[3] License\n[4] Upgrade"); + + const optionRaw = await readline.question("Select an option: "); + const option = parseInt(optionRaw); + + let username = "", + password = "", + license = ""; + + switch (option) { + case 1: + username = await readline.question("Username: "); + password = await readline.question("Password: "); + await KeyAuthApp.login(username, password); + dashboard(); + break; + + case 2: + username = await readline.question("Username: "); + password = await readline.question("Password: "); + license = await readline.question("License: "); + await KeyAuthApp.register(username, password, license); + dashboard(); + break; + + case 3: + license = await readline.question("License: "); + await KeyAuthApp.license(license); + dashboard(); + break; + + case 4: + username = await readline.question("Username: "); + license = await readline.question("License: "); + await KeyAuthApp.upgrade(username, license); + dashboard(); + break; + + default: + console.log("Invalid option selected."); + break; + } + } catch (error) { + if (error instanceof Error) { + console.error("An error occurred:", error.message); + } else { + console.error("An unknown error occurred:", error); + } + } +} + +answer(); + +async function dashboard() { + await KeyAuthApp.fetchStats(); + console.log("Application data:"); + console.log(" App Version: ", KeyAuthApp.app_data?.app_ver); + console.log(" Customer panel: ", KeyAuthApp.app_data?.customer_panel); + console.log(" Number of Keys: ", KeyAuthApp.app_data?.numKeys); + console.log(" Number of Users: ", KeyAuthApp.app_data?.numUsers); + console.log(" Online Users: ", KeyAuthApp.app_data?.onlineUsers); + + console.log("\nUser data:"); + console.log(" Username: ", KeyAuthApp.user_data?.username); + console.log(" IP Address: ", KeyAuthApp.user_data?.ip); + console.log(" Hardware-id: ", KeyAuthApp.user_data?.hwid); + + const subs = KeyAuthApp.user_data?.subscriptions || [] as Array<{ + subscription: string; + key: string; + expiry: string; + timeleft: number; + }>; + + for (let i = 0; i < subs.length; i++) { + const sub = subs[i] as { subscription: string, key: string, expiry: string, timeleft: number }; + const expiry = new Date(Number(sub.expiry) * 1000); + + console.log(`[${i + 1}/${subs.length}] | Subscription: ${sub.subscription} - Expiry: ${expiry.toLocaleString()}`); + } + + console.log( + `Created at: ${new Date( + (KeyAuthApp.user_data?.createdate || 0) * 1000 + ).toLocaleString()}` + ); + console.log( + `Last Login: ${new Date( + (KeyAuthApp.user_data?.lastlogin || 0) * 1000 + ).toLocaleString()}` + ); + console.log( + `Expires: ${new Date( + (KeyAuthApp.user_data?.expires || 0) * 1000 + ).toLocaleString()}` + ); + + console.log("\n2-factor authentication:"); + console.log("[1] Enable\n[2] Disable"); + + const optionRaw = await readline.question("Select an option: "); + const option = parseInt(optionRaw); + + switch (option) { + case 1: + await KeyAuthApp.enable2fa(); + break; + case 2: + await KeyAuthApp.disable2fa(); + break; + default: + console.log("Invalid option selected."); + break; + } + + console.log("Closing app in 10 seconds...") + await new Promise((resolve) => setTimeout(resolve, 10000)); + readline.close(); + await KeyAuthApp.logout(); + process.exit(0); +} \ No newline at end of file diff --git a/typescript/src/keyauth.ts b/typescript/src/keyauth.ts new file mode 100644 index 0000000..2e058a0 --- /dev/null +++ b/typescript/src/keyauth.ts @@ -0,0 +1,841 @@ +import { Buffer } from "buffer"; +import { createInterface } from "readline"; +import { execSync, exec } from "child_process"; +import { verifyKey } from "discord-interactions"; + +import os from "os"; +import fs from "fs"; +import crypto from "crypto"; +import * as QRCode from "qrcode"; + +const readline = createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false, +}) + +interface KeyAuthOptions { + name: string; + ownerid: string; + version: string; + hash_to_check?: string; + url?: string; + path?: string; +} + +interface KeyAuthUserData { + username: string; + ip: string; + hwid: string; + expires: number; + createdate: number; + lastlogin: number; + subscription: string; + subscriptions: string; +} + +interface KeyAuthAppData { + numUsers: number; + numKeys: number; + app_ver: string; + customer_panel: string; + onlineUsers: number; +} + +export default class KeyAuth { + private name: string; + private ownerid: string; + private version: string; + private hash_to_check?: string; + private url: string = "https://keyauth.win/api/1.3/"; + private path?: string; + private public_key: string = "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b"; + private loggingEnabled: boolean = true; + + private sessionid?: string; + private initialized: boolean = false; + + public user_data: KeyAuthUserData | null = null; + public app_data: KeyAuthAppData | null = null; + + constructor(options: KeyAuthOptions) { + this.name = options.name; + this.ownerid = options.ownerid; + this.version = options.version; + this.hash_to_check = options.hash_to_check; + this.url = options.url ?? "https://keyauth.win/api/1.3/"; + this.path = options.path; + + if (!this.name || !this.ownerid || !this.version) { + throw new Error("Name, ownerid, and version are required"); + } + } + + async init() { + if (this.sessionid && this.initialized) { + console.log("Application already initialized"); + return; + } + + let token: string = ""; + + if (this.path) { + try { + token = fs.readFileSync(this.path, "utf-8").trim(); + } catch (error) { + console.error(`Failed to read file at path ${this.path}:`, error); + this.sleep(5000); + process.exit(0); + } + } + + const post_data = { + "type": "init", + "name": this.name, + "ownerid": this.ownerid, + "version": this.version, + "hash": this.hash_to_check, + ...this.path && { + "token": token, + "thash": crypto.createHash("sha256").update(token).digest("hex"), + } + }; + + const response = await this.__do_request(post_data) as any; + + if (response === "KeyAuth_Invalid") { + console.log("This application does not exist"); + this.sleep(5000); + process.exit(0); + } + + if (response["message"] === "invalidver") { + if (response["download"]) { + console.log("Your application is outdated."); + exec(`start ${response["download"]}`, (error: any) => { + if (error) { + console.error("Failed to open the download link:", error); + } + }); + this.sleep(5000); + process.exit(0); + } else { + console.log("Your application is outdated and no download link was provided, contact the owner for the latest app version."); + this.sleep(5000); + process.exit(0); + } + } + + if (response["success"] === false) { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + + this.sessionid = response["sessionid"]; + this.initialized = true; + } + + async register(username: string, password: string, license: string, hwid?: string) { + this.checkinit(); + if (!hwid) hwid = this.get_hwid(); + + const post_data = { + "type": "register", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "username": username, + "pass": password, + "key": license, + "hwid": hwid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + console.log(response["message"]); + this.__load_user_data(response["info"]); + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async upgrade(username: string, license: string) { + this.checkinit(); + + const post_data = { + "type": "upgrade", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "username": username, + "key": license, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + console.log(response["message"]); + console.log("Restart the application to apply the changes."); + this.sleep(5000); + process.exit(0); + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async login(username: string, password: string, code?: string, hwid?: string) { + this.checkinit(); + if (!hwid) hwid = this.get_hwid(); + + const post_data = { + "type": "register", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "username": username, + "pass": password, + "hwid": hwid, + + ...code && { "code": code }, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + console.log(response["message"]); + this.__load_user_data(response["info"]); + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async license(license: string, code?: string, hwid?: string) { + this.checkinit(); + if (!hwid) hwid = this.get_hwid(); + + const post_data = { + "type": "license", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "key": license, + "hwid": hwid, + + ...code && { "code": code }, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + console.log(response["message"]); + this.__load_user_data(response["info"]); + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async var(name: string) { + this.checkinit(); + + const post_data = { + "type": "var", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "varid": name + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return response["message"]; + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async getvar(name: string) { + this.checkinit(); + + const post_data = { + "type": "getvar", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "var": name + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return response["message"]; + } else { + console.log("NOTE: This is commonly misunderstood. This is for user variables, not the normal variables.\nUse KeyAuthApp.var(\"{var_name}\") for normal variables") + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async setvar(name: string, value: string) { + this.checkinit(); + + const post_data = { + "type": "setvar", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "varid": name, + "data": value, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return true; + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async ban() { + this.checkinit(); + + const post_data = { + "type": "ban", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return true; + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async file(id: string) { + this.checkinit(); + + const post_data = { + "type": "file", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "fileid": id + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return Buffer.from(response["contents"], "hex"); + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async webhook(id: string, param: string, body?: string, conttype?: string) { + this.checkinit(); + + const post_data = { + "type": "webhook", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "webid": id, + "params": param, + "body": body, + "conttype": conttype, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return response["message"]; + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async check() { + this.checkinit(); + + const post_data = { + "type": "check", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return true; + } else { + return false; + } + } + + async checkblacklist() { + this.checkinit(); + const hwid = this.get_hwid(); + + const post_data = { + "type": "checkblacklist", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "hwid": hwid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return true; + } else { + return false; + } + } + + async log(message: string) { + this.checkinit(); + + const post_data = { + "type": "log", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "message": message, + "pcuser": os.userInfo().username, + }; + + await this.__do_request(post_data) as any; + } + + async fetchOnline() { + this.checkinit(); + + const post_data = { + "type": "fetchOnline", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + if (Number(response["users"]) === 0) { + return null; + } else { + return Number(response["users"]); + } + } else { + return false; + } + } + + async fetchStats() { + this.checkinit(); + + const post_data = { + "type": "fetchStats", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + this.__load_app_data(response["appinfo"]); + } + } + + async chatGet(channel: string) { + this.checkinit(); + + const post_data = { + "type": "chatget", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "channel": channel, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return response["messages"]; + } else { + return false; + } + } + + async chatSend(message: string, channel: string) { + this.checkinit(); + + const post_data = { + "type": "chatsend", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "channel": channel, + "message": message, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + return true; + } else { + return false; + } + } + + async changeUsername(username: string) { + this.checkinit(); + + const post_data = { + "type": "changeUsername", + "newUsername": username, + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + console.log("Username changed successfully"); + } else { + return false; + } + } + + async logout() { + this.checkinit(); + + const post_data = { + "type": "logout", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + console.log("Logged out successfully"); + this.sleep(5000); + process.exit(0); + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async enable2fa(code?: string) { + this.checkinit(); + + const post_data = { + "type": "2faenable", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + ...code && { "code": code }, + }; + + const response = await this.__do_request(post_data) as any; + + if (response["success"] === true) { + if (!code) { + console.log(response); + console.log("Your 2fa secret code is: " + response["2fa"]["secret_code"]); + + this.displayQrCode(response["2fa"]["QRCode"]); + console.log("Please enter the code you received in your authenticator app."); + + const userCode = await new Promise((resolve) => { + readline.question("Enter the code: ", (input: string) => { + resolve(input); + }); + }); + + await this.enable2fa(userCode); // Recursively call with the entered code + } else { + console.log("2FA enabled successfully"); + } + } else { + console.log(response["message"]); + this.sleep(5000); + process.exit(0); + } + } + + async disable2fa() { + this.checkinit(); + + const code = await new Promise((resolve) => { + readline.question("Enter the code: ", (input: string) => { + resolve(input); + }); + }); + + const post_data = { + "type": "2fadisable", + "name": this.name, + "ownerid": this.ownerid, + "sessionid": this.sessionid, + "code": code, + }; + + const response = await this.__do_request(post_data) as any; + + console.log(response["message"]); + await this.sleep(5000); + } + + async checkinit() { + if (!this.sessionid && !this.initialized) { + console.log("Application not initialized"); + this.sleep(5000); + process.exit(0); + } + } + + get_hwid(): string { + const platform = os.platform(); + + if (platform === "linux") { + try { + const hwid = fs.readFileSync("/etc/machine-id", "utf-8").trim(); + return hwid; + } catch (error) { + console.error("Error reading /etc/machine-id:", error); + throw new Error("Failed to retrieve HWID on Linux"); + } + } else if (platform === "win32") { + try { + const winUser = os.userInfo().username; + const sidOutput = execSync(`wmic useraccount where name='${winUser}' get sid`).toString().split("\n"); + const sid = sidOutput[1]?.trim(); + if (!sid) { + throw new Error("Failed to retrieve SID on Windows: SID is undefined"); + } + return sid; + } catch (error) { + console.error("Error retrieving SID on Windows:", error); + throw new Error("Failed to retrieve HWID on Windows"); + } + } else if (platform === "darwin") { + try { + const output = execSync("ioreg -l | grep IOPlatformSerialNumber").toString(); + const parts = output.split("="); + const serial = parts[1]?.trim().replace(/"/g, "") ?? ""; + return serial; + } catch (error) { + console.error("Error retrieving serial number on macOS:", error); + throw new Error("Failed to retrieve HWID on macOS"); + } + } else { + throw new Error("Unsupported platform for HWID retrieval"); + } + } + + async displayQrCode(qrCodeUrl: string): Promise { + try { + const qrCode = await QRCode.toDataURL(qrCodeUrl); + + const base64Data = qrCode.split(",")[1]; + if (!base64Data) { + throw new Error("Invalid QR code data"); + } + const img = Buffer.from(base64Data, "base64"); + + const outputPath = this.path ?? "qrcode.png"; + fs.writeFileSync(outputPath, img); + + const platform = os.platform(); + let openCommand: string; + + if (platform === "win32") { + openCommand = `start ${outputPath}`; + } else if (platform === "darwin") { + openCommand = `open ${outputPath}`; + } else if (platform === "linux") { + openCommand = `xdg-open ${outputPath}`; + } else { + console.error("Unsupported platform for opening the QR code image"); + return; + } + + exec(openCommand, (error) => { + if (error) { + console.error("Failed to display the QR code image:", error); + } + }); + } catch (error) { + console.error("Failed to generate QR code:", error); + } + } + + getCheckSum(): string { + const md5Hash = crypto.createHash('md5'); + const file = fs.readFileSync(process.argv.slice(2).join(''), { encoding: 'binary' }); + md5Hash.update(file); + const digest = md5Hash.digest('hex'); + return digest; + } + + // Private functions (cannot be called from outside the class) + private async sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + private async __do_request(data: any) { + try { + const response = await fetch(this.url, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + // @ts-ignore - Why does it error? No fucking clue but it works + body: new URLSearchParams(data).toString(), + }); + + if (!response.ok) { + console.error(`HTTP error! Status: ${response.status}`); + this.sleep(5000); + process.exit(0); + } + + const responseData = await response.json(); + const dontRunExtra = ["log", "file", "2faenable", "2fadisable"]; + if (dontRunExtra.includes(data.type)) { + return responseData; + } + + const signature = response.headers.get("x-signature-ed25519"); + const timestamp = response.headers.get("x-signature-timestamp"); + if (!signature || !timestamp) { + console.log("Missing signature or timestamp in response headers"); + this.sleep(5000); + process.exit(0); + } + + const server_time = new Date(Number(timestamp) * 1000).toUTCString(); + const current_time = new Date().toUTCString(); + + const buffer_seconds = 5; // Allowable time difference in seconds + const time_difference = Math.abs(new Date(server_time).getTime() - new Date(current_time).getTime()) / 1000; + + if (time_difference > buffer_seconds + 20) { + console.log(`Time difference is too large: ${time_difference} seconds, try syncing your date and time settings.`); + this.sleep(5000); + process.exit(0); + } + + if (!verifyKey(Buffer.from(JSON.stringify(responseData), 'utf-8'), signature, timestamp, this.public_key)) { + console.log("Signature checksum failed. Request was tampered with or session ended most likely."); + await this.sleep(3000); + process.exit(0); + } + + this.logEvent(JSON.stringify(responseData) + "\n"); + + return responseData; + } catch (error) { + console.error("Unexpected error:", error); + this.sleep(5000); + process.exit(0); + } + } + + private logEvent(message: string) { + console.log(message) + if (!this.loggingEnabled) return; + + const exeName = process.argv[1].split("\\").pop() || process.argv[1].split("/").pop(); + const logDirectory = `${os.homedir()}/AppData/Roaming/KeyAuth/debug/${exeName}`; + + try { + if (!fs.existsSync(logDirectory)) { + fs.mkdirSync(logDirectory, { recursive: true }); + } + + const logFileName = `${new Date().toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric" }).replace(/ /g, "_")}_logs.txt`; + const logFilePath = `${logDirectory}/${logFileName}`; + + // Redact sensitive fields + message = this.redactField(message, "sessionid"); + message = this.redactField(message, "ownerid"); + message = this.redactField(message, "app"); + message = this.redactField(message, "version"); + message = this.redactField(message, "fileid"); + message = this.redactField(message, "webhooks"); + message = this.redactField(message, "nonce"); + + const logMessage = `[${new Date().toISOString()}] [${exeName}] ${message}\n`; + fs.appendFileSync(logFilePath, logMessage, "utf-8"); + } catch (error) { + if (error instanceof Error) { + console.error(`Error logging data: ${error.message}`); + } else { + console.error("Error logging data: Unknown error"); + } + } + } + + private redactField(content: string, field: string): string { + const regex = new RegExp(`"${field}":\\s*".*?"`, "g"); + return content.replace(regex, `"${field}": "[REDACTED]"`); + } + + private __load_app_data(data: any) { + this.app_data = { + numUsers: data["numUsers"], + numKeys: data["numKeys"], + app_ver: data["version"], + customer_panel: data["customerPanelLink"], + onlineUsers: data["numOnlineUsers"], + } + } + + private __load_user_data(data: any) { + this.user_data = { + username: data["username"], + ip: data["ip"], + hwid: data["hwid"] || "N/A", + expires: data["subscriptions"][0]["expiry"], + createdate: data["createdate"], + lastlogin: data["lastlogin"], + subscription: data["subscriptions"][0]["subscription"], + subscriptions: data["subscriptions"], + } + } +} \ No newline at end of file diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json new file mode 100644 index 0000000..b69da1f --- /dev/null +++ b/typescript/tsconfig.json @@ -0,0 +1,113 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} From a6bb87331387ad179ede7e5be37164136cc4c98b Mon Sep 17 00:00:00 2001 From: Faintastic <110469682+faintastic@users.noreply.github.com> Date: Tue, 8 Apr 2025 21:02:39 +0000 Subject: [PATCH 2/2] fix: i'm stupid and forgot to name the file correctly! --- README => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md