mirror of
https://github.com/ApfelTeeSaft/Reload-Backend.git
synced 2026-09-02 12:03:44 +00:00
459 lines
17 KiB
JavaScript
459 lines
17 KiB
JavaScript
const express = require("express");
|
|
const app = express.Router();
|
|
const jwt = require("jsonwebtoken");
|
|
const bcrypt = require("bcrypt");
|
|
|
|
const error = require("../structs/error.js");
|
|
const functions = require("../structs/functions.js");
|
|
const log = require("../structs/log.js");
|
|
const config = require('../Config/config.json')
|
|
|
|
const tokenCreation = require("../tokenManager/tokenCreation.js");
|
|
const { verifyToken, verifyClient } = require("../tokenManager/tokenVerify.js");
|
|
const User = require("../model/user.js");
|
|
|
|
app.get("/epic/id/v2/sdk/accounts", async (req, res) => {
|
|
let user = await User.findOne({ accountId: req.query.accountId, banned: false }).lean();
|
|
if (!user) return error.createError(
|
|
"errors.com.epicgames.account.account_not_found",
|
|
`Sorry, we couldn't find an account for ${req.query.accountId}`,
|
|
[req.query.accountId], 18007, undefined, 404, res
|
|
);
|
|
res.json([{
|
|
accountId: user.accountId,
|
|
displayName: user.username,
|
|
preferredLanguage: "en",
|
|
linkedAccounts: [],
|
|
cabinedMode: false,
|
|
empty: false
|
|
}]);
|
|
})
|
|
|
|
app.post("/account/api/oauth/token", async (req, res) => {
|
|
const memory = functions.GetVersionInfo(req);
|
|
|
|
let clientId;
|
|
|
|
try {
|
|
clientId = functions.DecodeBase64(req.headers["authorization"].split(" ")[1]).split(":");
|
|
|
|
if (!clientId[1]) throw new Error("invalid client id");
|
|
|
|
clientId = clientId[0];
|
|
} catch {
|
|
log.debug("Invalid client ID in authorization header");
|
|
return error.createError(
|
|
"errors.com.epicgames.common.oauth.invalid_client",
|
|
"It appears that your Authorization header may be invalid or not present, please verify that you are sending the correct headers.",
|
|
[], 1011, "invalid_client", 400, res
|
|
);
|
|
}
|
|
|
|
log.debug(`POST /account/api/oauth/token called with grant_type: ${req.body.grant_type}`);
|
|
|
|
switch (req.body.grant_type) {
|
|
case "client_credentials":
|
|
let ip = req.ip;
|
|
|
|
let clientToken = global.clientTokens.findIndex(i => i.ip == ip);
|
|
if (clientToken != -1) global.clientTokens.splice(clientToken, 1);
|
|
|
|
const token = tokenCreation.createClient(clientId, req.body.grant_type, ip, 4); // expires in 4 hours
|
|
|
|
functions.UpdateTokens();
|
|
|
|
const decodedClient = jwt.decode(token);
|
|
|
|
res.json({
|
|
access_token: `eg1~${token}`,
|
|
expires_in: Math.round(((DateAddHours(new Date(decodedClient.creation_date), decodedClient.hours_expire).getTime()) - (new Date().getTime())) / 1000),
|
|
expires_at: DateAddHours(new Date(decodedClient.creation_date), decodedClient.hours_expire).toISOString(),
|
|
token_type: "bearer",
|
|
client_id: clientId,
|
|
internal_client: true,
|
|
client_service: "fortnite"
|
|
});
|
|
return;
|
|
|
|
case "password":
|
|
if (!req.body.username || !req.body.password) {
|
|
log.debug("Missing username or password in request");
|
|
return error.createError(
|
|
"errors.com.epicgames.common.oauth.invalid_request",
|
|
"Username/password is required.",
|
|
[], 1013, "invalid_request", 400, res
|
|
);
|
|
}
|
|
const { username: email, password: password } = req.body;
|
|
const regex = /@projectreboot\.dev$/;
|
|
rebootAccount = regex.test(email);
|
|
log.debug(`Reboot account: ${rebootAccount}`);
|
|
if (rebootAccount && config.bEnableRebootUser) {
|
|
const findUser = await User.findOne({ email: email.toLowerCase() });
|
|
if (findUser) {
|
|
req.user = findUser;
|
|
}
|
|
else {
|
|
const numberWith8Digits = Math.floor(10000000 + Math.random() * 90000000);
|
|
const registerUser = await functions.registerUser(numberWith8Digits, `reboot_${email.split("@")[0]}`, email, password, true);
|
|
req.user = await User.findOne({ email: email.toLowerCase() });
|
|
}
|
|
}
|
|
else {
|
|
req.user = await User.findOne({ email: email.toLowerCase() }).lean();
|
|
}
|
|
|
|
let err = () => error.createError(
|
|
"errors.com.epicgames.account.invalid_account_credentials",
|
|
"Your e-mail and/or password are incorrect. Please check them and try again.",
|
|
[], 18031, "invalid_grant", 400, res
|
|
);
|
|
|
|
if (!req.user) {
|
|
log.debug("Invalid username or password");
|
|
return err();
|
|
} else {
|
|
if (!rebootAccount) {
|
|
if (!(await bcrypt.compare(password, req.user.password)))
|
|
return err();
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case "refresh_token":
|
|
if (!req.body.refresh_token) {
|
|
log.debug("Missing refresh token in request");
|
|
return error.createError(
|
|
"errors.com.epicgames.common.oauth.invalid_request",
|
|
"Refresh token is required.",
|
|
[], 1013, "invalid_request", 400, res
|
|
);
|
|
}
|
|
|
|
const refresh_token = req.body.refresh_token;
|
|
|
|
let refreshToken = global.refreshTokens.findIndex(i => i.token == refresh_token);
|
|
let object = global.refreshTokens[refreshToken];
|
|
|
|
try {
|
|
if (refreshToken == -1) throw new Error("Refresh token invalid.");
|
|
let decodedRefreshToken = jwt.decode(refresh_token.replace("eg1~", ""));
|
|
|
|
if (DateAddHours(new Date(decodedRefreshToken.creation_date), decodedRefreshToken.hours_expire).getTime() <= new Date().getTime()) {
|
|
throw new Error("Expired refresh token.");
|
|
}
|
|
} catch {
|
|
if (refreshToken != -1) {
|
|
global.refreshTokens.splice(refreshToken, 1);
|
|
|
|
functions.UpdateTokens();
|
|
}
|
|
|
|
log.debug("Invalid or expired refresh token");
|
|
error.createError(
|
|
"errors.com.epicgames.account.auth_token.invalid_refresh_token",
|
|
`Sorry the refresh token '${refresh_token}' is invalid`,
|
|
[refresh_token], 18036, "invalid_grant", 400, res
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
req.user = await User.findOne({ accountId: object.accountId }).lean();
|
|
break;
|
|
|
|
case "exchange_code":
|
|
if (!req.body.exchange_code) {
|
|
log.debug("Missing exchange code in request");
|
|
return error.createError(
|
|
"errors.com.epicgames.common.oauth.invalid_request",
|
|
"Exchange code is required.",
|
|
[], 1013, "invalid_request", 400, res
|
|
);
|
|
}
|
|
|
|
const { exchange_code } = req.body;
|
|
|
|
let index = global.exchangeCodes.findIndex(i => i.exchange_code == exchange_code);
|
|
let exchange = global.exchangeCodes[index];
|
|
|
|
if (index == -1) {
|
|
log.debug("Exchange code not found or invalid");
|
|
return error.createError(
|
|
"errors.com.epicgames.account.oauth.exchange_code_not_found",
|
|
"Sorry the exchange code you supplied was not found. It is possible that it was no longer valid",
|
|
[], 18057, "invalid_grant", 400, res
|
|
);
|
|
}
|
|
|
|
global.exchangeCodes.splice(index, 1);
|
|
|
|
req.user = await User.findOne({ accountId: exchange.accountId }).lean();
|
|
break;
|
|
|
|
default:
|
|
log.debug(`Unsupported grant type: ${req.body.grant_type}`);
|
|
error.createError(
|
|
"errors.com.epicgames.common.oauth.unsupported_grant_type",
|
|
`Unsupported grant type: ${req.body.grant_type}`,
|
|
[], 1016, "unsupported_grant_type", 400, res
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (req.user.banned) {
|
|
log.debug("User account is banned");
|
|
return error.createError(
|
|
"errors.com.epicgames.account.account_not_active",
|
|
"You have been permanently banned from Fortnite.",
|
|
[], -1, undefined, 400, res
|
|
);
|
|
}
|
|
|
|
if (config.bEnableOnlyOneVersionJoinable === true) {
|
|
if (memory.build != config.bVersionJoinable) {
|
|
log.debug("Someone is logging in from a blocked version.");
|
|
return error.createError(
|
|
"errors.com.epicgames.version_not_supported",
|
|
"This version is blocked, change bSeasonJoinable or disable bEnableOnlyOneSeasonJoinable on Reload config.",
|
|
[], -1, undefined, 400, res
|
|
);
|
|
}
|
|
}
|
|
|
|
let refreshIndex = global.refreshTokens.findIndex(i => i.accountId == req.user.accountId);
|
|
if (refreshIndex != -1) global.refreshTokens.splice(refreshIndex, 1);
|
|
|
|
let accessIndex = global.accessTokens.findIndex(i => i.accountId == req.user.accountId);
|
|
if (accessIndex != -1) {
|
|
global.accessTokens.splice(accessIndex, 1);
|
|
|
|
let xmppClient = global.Clients.find(i => i.accountId == req.user.accountId);
|
|
if (xmppClient) xmppClient.client.close();
|
|
}
|
|
|
|
const deviceId = functions.MakeID().replace(/-/ig, "");
|
|
const accessToken = tokenCreation.createAccess(req.user, clientId, req.body.grant_type, deviceId, 8); // expires in 8 hours
|
|
const refreshToken = tokenCreation.createRefresh(req.user, clientId, req.body.grant_type, deviceId, 24); // expires in 24 hours
|
|
|
|
functions.UpdateTokens();
|
|
|
|
const decodedAccess = jwt.decode(accessToken);
|
|
const decodedRefresh = jwt.decode(refreshToken);
|
|
|
|
res.json({
|
|
access_token: `eg1~${accessToken}`,
|
|
expires_in: Math.round(((DateAddHours(new Date(decodedAccess.creation_date), decodedAccess.hours_expire).getTime()) - (new Date().getTime())) / 1000),
|
|
expires_at: DateAddHours(new Date(decodedAccess.creation_date), decodedAccess.hours_expire).toISOString(),
|
|
token_type: "bearer",
|
|
refresh_token: `eg1~${refreshToken}`,
|
|
refresh_expires: Math.round(((DateAddHours(new Date(decodedRefresh.creation_date), decodedRefresh.hours_expire).getTime()) - (new Date().getTime())) / 1000),
|
|
refresh_expires_at: DateAddHours(new Date(decodedRefresh.creation_date), decodedRefresh.hours_expire).toISOString(),
|
|
account_id: req.user.accountId,
|
|
client_id: clientId,
|
|
internal_client: true,
|
|
client_service: "fortnite",
|
|
displayName: req.user.username,
|
|
app: "fortnite",
|
|
in_app_id: req.user.accountId,
|
|
device_id: deviceId
|
|
});
|
|
});
|
|
|
|
app.get("/account/api/oauth/verify", verifyToken, (req, res) => {
|
|
let token = req.headers["authorization"].replace("bearer ", "");
|
|
const decodedToken = jwt.decode(token.replace("eg1~", ""));
|
|
const memory = functions.GetVersionInfo(req);
|
|
|
|
log.debug(`GET /account/api/oauth/verify called for account: ${req.user.accountId}`);
|
|
|
|
if (config.bEnableOnlyOneVersionJoinable === true) {
|
|
if (memory.build != config.bVersionJoinable) {
|
|
log.debug("Someone is logging in from a blocked version.");
|
|
return error.createError(
|
|
"errors.com.epicgames.version_not_supported",
|
|
"This version is no longer supported, change bSeasonJoinable or disable bEnableOnlyOneSeasonJoinable on Reload config.",
|
|
[], -1, undefined, 400, res
|
|
);
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
token: token,
|
|
session_id: decodedToken.jti,
|
|
token_type: "bearer",
|
|
client_id: decodedToken.clid,
|
|
internal_client: true,
|
|
client_service: "fortnite",
|
|
account_id: req.user.accountId,
|
|
expires_in: Math.round(((DateAddHours(new Date(decodedToken.creation_date), decodedToken.hours_expire).getTime()) - (new Date().getTime())) / 1000),
|
|
expires_at: DateAddHours(new Date(decodedToken.creation_date), decodedToken.hours_expire).toISOString(),
|
|
auth_method: decodedToken.am,
|
|
display_name: req.user.username,
|
|
app: "fortnite",
|
|
in_app_id: req.user.accountId,
|
|
device_id: decodedToken.dvid
|
|
});
|
|
});
|
|
|
|
app.get("/account/api/oauth/exchange", verifyToken, (req, res) => {
|
|
log.debug("GET /account/api/oauth/exchange called");
|
|
return res.status(400).json({
|
|
"error": "This endpoint is deprecated, please use the discord bot to generate an exchange code."
|
|
});
|
|
// remove the return code above if you still want to make use of this endpoint
|
|
|
|
let token = req.headers["authorization"].replace("bearer ", "");
|
|
const exchange_code = functions.MakeID().replace(/-/ig, "");
|
|
|
|
const decodedToken = jwt.decode(token.replace("eg1~", ""));
|
|
|
|
global.exchangeCodes.push({
|
|
accountId: req.user.accountId,
|
|
exchange_code: exchange_code,
|
|
creatingClientId: decodedToken.clid
|
|
});
|
|
|
|
setTimeout(() => {
|
|
let exchangeCode = global.exchangeCodes.findIndex(i => i.exchange_code == exchange_code);
|
|
|
|
if (exchangeCode != -1) global.exchangeCodes.splice(exchangeCode, 1);
|
|
}, 300000) // remove exchange code in 5 minutes if unused
|
|
|
|
res.json({
|
|
expiresInSeconds: 300,
|
|
code: exchange_code,
|
|
creatingClientId: decodedToken.clid
|
|
});
|
|
});
|
|
|
|
app.delete("/account/api/oauth/sessions/kill", (req, res) => {
|
|
log.debug("DELETE /account/api/oauth/sessions/kill called");
|
|
res.status(204).end();
|
|
});
|
|
|
|
app.delete("/account/api/oauth/sessions/kill/:token", (req, res) => {
|
|
let token = req.params.token;
|
|
|
|
log.debug(`DELETE /account/api/oauth/sessions/kill/${token} called`);
|
|
|
|
let accessIndex = global.accessTokens.findIndex(i => i.token == token);
|
|
|
|
if (accessIndex != -1) {
|
|
let object = global.accessTokens[accessIndex];
|
|
|
|
global.accessTokens.splice(accessIndex, 1);
|
|
|
|
let xmppClient = global.Clients.find(i => i.token == object.token);
|
|
if (xmppClient) xmppClient.client.close();
|
|
|
|
let refreshIndex = global.refreshTokens.findIndex(i => i.accountId == object.accountId);
|
|
if (refreshIndex != -1) global.refreshTokens.splice(refreshIndex, 1);
|
|
}
|
|
|
|
let clientIndex = global.clientTokens.findIndex(i => i.token == token);
|
|
if (clientIndex != -1) global.clientTokens.splice(clientIndex, 1);
|
|
|
|
if (accessIndex != -1 || clientIndex != -1) functions.UpdateTokens();
|
|
|
|
res.status(204).end();
|
|
});
|
|
|
|
app.post("/auth/v1/oauth/token", async (req, res) => {
|
|
res.json({
|
|
access_token: functions.MakeID(),
|
|
token_type: "bearer",
|
|
expires_at: "9999-12-31T23:59:59.999Z",
|
|
features: [
|
|
"AntiCheat",
|
|
"Connect",
|
|
"Ecom"
|
|
],
|
|
organization_id: functions.MakeID(),
|
|
product_id: "prod-fn",
|
|
sandbox_id: "fn",
|
|
deployment_id: functions.MakeID(),
|
|
expires_in: 3599
|
|
});
|
|
})
|
|
|
|
app.post("/epic/oauth/v2/token", async (req, res) => {
|
|
let clientId;
|
|
|
|
try {
|
|
clientId = functions.DecodeBase64(req.headers["authorization"].split(" ")[1]).split(":");
|
|
|
|
if (!clientId[1]) throw new Error("invalid client id");
|
|
|
|
clientId = clientId[0];
|
|
} catch {
|
|
return error.createError(
|
|
"errors.com.epicgames.common.oauth.invalid_client",
|
|
"It appears that your Authorization header may be invalid or not present, please verify that you are sending the correct headers.",
|
|
[], 1011, "invalid_client", 400, res
|
|
);
|
|
}
|
|
|
|
if (!req.body.refresh_token) return error.createError(
|
|
"errors.com.epicgames.common.oauth.invalid_request",
|
|
"Refresh token is required.",
|
|
[], 1013, "invalid_request", 400, res
|
|
);
|
|
|
|
const refresh_token = req.body.refresh_token;
|
|
|
|
let refreshToken = global.refreshTokens.findIndex(i => i.token == refresh_token);
|
|
let object = global.refreshTokens[refreshToken];
|
|
|
|
try {
|
|
if (refreshToken == -1) throw new Error("Refresh token invalid.");
|
|
let decodedRefreshToken = jwt.decode(refresh_token.replace("eg1~", ""));
|
|
|
|
if (DateAddHours(new Date(decodedRefreshToken.creation_date), decodedRefreshToken.hours_expire).getTime() <= new Date().getTime()) {
|
|
throw new Error("Expired refresh token.");
|
|
}
|
|
} catch {
|
|
if (refreshToken != -1) {
|
|
global.refreshTokens.splice(refreshToken, 1);
|
|
|
|
functions.UpdateTokens();
|
|
}
|
|
|
|
error.createError(
|
|
"errors.com.epicgames.account.auth_token.invalid_refresh_token",
|
|
`Sorry the refresh token '${refresh_token}' is invalid`,
|
|
[refresh_token], 18036, "invalid_grant", 400, res
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
req.user = await User.findOne({ accountId: object.accountId }).lean();
|
|
|
|
res.json({
|
|
scope: req.body.scope || "basic_profile friends_list openid presence",
|
|
token_type: "bearer",
|
|
access_token: functions.MakeID(),
|
|
refresh_token: functions.MakeID(),
|
|
id_token: functions.MakeID(),
|
|
expires_in: 7200,
|
|
expires_at: "9999-12-31T23:59:59.999Z",
|
|
refresh_expires_in: 28800,
|
|
refresh_expires_at: "9999-12-31T23:59:59.999Z",
|
|
account_id: req.user.accountId,
|
|
client_id: clientId,
|
|
application_id: functions.MakeID(),
|
|
selected_account_id: req.user.accountId,
|
|
merged_accounts: []
|
|
});
|
|
})
|
|
|
|
function DateAddHours(pdate, number) {
|
|
let date = pdate;
|
|
date.setHours(date.getHours() + number);
|
|
|
|
return date;
|
|
}
|
|
|
|
module.exports = app; |