mirror of
https://github.com/ibratabian17/OpenParty.git
synced 2026-01-15 14:22:54 -03:00
Implement SHA256 hashing for user authentication tickets before storage. This prevents sensitive tokens from being stored in plain text, significantly improving security. Changes include: * Hashing tickets in `AccountRouteHandler` and `UbiservicesRouteHandler` during profile updates and login flows. * Introducing dedicated `updateUserTicket` methods in `AccountRepository` and `AccountService`. * Adjusting the `Account` model to handle tickets separately. * Adding a new `config` table to the database schema.
703 lines
28 KiB
JavaScript
703 lines
28 KiB
JavaScript
/**
|
|
* Account Route Handler for OpenParty
|
|
* Handles user account-related routes
|
|
*/
|
|
const crypto = require('crypto');
|
|
const axios = require('axios');
|
|
const RouteHandler = require('./RouteHandler'); // Assuming RouteHandler is in the same directory
|
|
const MostPlayedService = require('../../services/MostPlayedService');
|
|
const AccountService = require('../../services/AccountService'); // Import the AccountService
|
|
const { getDb } = require('../../database/sqlite');
|
|
const Logger = require('../../utils/logger');
|
|
const { v4: uuidv4 } = require('uuid'); // Import uuid for generating new profile IDs
|
|
|
|
class AccountRouteHandler extends RouteHandler {
|
|
/**
|
|
* Create a new account route handler
|
|
*/
|
|
constructor() {
|
|
super('AccountRouteHandler');
|
|
this.logger = new Logger('AccountRouteHandler');
|
|
|
|
// Bind handler methods to maintain 'this' context
|
|
this.handlePostProfiles = this.handlePostProfiles.bind(this);
|
|
this.handleGetProfiles = this.handleGetProfiles.bind(this);
|
|
this.handleMapEnded = this.handleMapEnded.bind(this);
|
|
this.handleDeleteFavoriteMap = this.handleDeleteFavoriteMap.bind(this);
|
|
this.handleGetProfileSessions = this.handleGetProfileSessions.bind(this);
|
|
this.handleFilterPlayers = this.handleFilterPlayers.bind(this);
|
|
|
|
// Initialize properties
|
|
this.ubiwsurl = "https://public-ubiservices.ubi.com";
|
|
this.prodwsurl = "https://prod.just-dance.com";
|
|
}
|
|
|
|
/**
|
|
* Initialize the routes
|
|
* @param {Express} app - The Express application instance
|
|
*/
|
|
initroute(app) {
|
|
this.logger.info(`Initializing routes...`);
|
|
|
|
// Register routes based on the 'old code'
|
|
this.registerPost(app, "/profile/v2/profiles", this.handlePostProfiles);
|
|
this.registerGet(app, "/profile/v2/profiles", this.handleGetProfiles);
|
|
this.registerPost(app, "/profile/v2/map-ended", this.handleMapEnded);
|
|
this.registerDelete(app, "/profile/v2/favorites/maps/:MapName", this.handleDeleteFavoriteMap);
|
|
this.registerGet(app, "/v3/profiles/sessions", this.handleGetProfileSessions);
|
|
this.registerPost(app, "/profile/v2/filter-players", this.handleFilterPlayers);
|
|
|
|
this.logger.info(`Routes initialized`);
|
|
}
|
|
|
|
/**
|
|
* Helper: Get the current week number
|
|
* @returns {number} The current week number
|
|
* @private
|
|
*/
|
|
getWeekNumber() {
|
|
const now = new Date();
|
|
const startOfWeek = new Date(now.getFullYear(), 0, 1);
|
|
const daysSinceStartOfWeek = Math.floor((now - startOfWeek) / (24 * 60 * 60 * 1000));
|
|
return Math.ceil((daysSinceStartOfWeek + 1) / 7);
|
|
}
|
|
|
|
/**
|
|
* Helper: Get game version from SkuId
|
|
* @param {Request} req - The request object
|
|
* @returns {string} The game version
|
|
* @private
|
|
*/
|
|
getGameVersion(req) {
|
|
const sku = req.header('X-SkuId') || "jd2019-pc-ww";
|
|
return sku.substring(0, 6) || "jd2019";
|
|
}
|
|
|
|
/**
|
|
* Helper: Find user by ticket
|
|
* @param {string} ticket - The user's ticket
|
|
* @returns {string|null} Profile ID if found, null otherwise
|
|
* @private
|
|
*/
|
|
findUserFromTicket(ticket) {
|
|
return AccountService.findUserFromTicket(ticket);
|
|
}
|
|
|
|
/**
|
|
* Helper: Find user by nickname
|
|
* @param {string} nickname - The user's nickname
|
|
* @returns {Object|undefined} User profile if found, undefined otherwise
|
|
* @private
|
|
*/
|
|
findUserFromNickname(nickname) {
|
|
return AccountService.findUserFromNickname(nickname);
|
|
}
|
|
|
|
/**
|
|
* Helper: Add a new user profile.
|
|
* @param {string} profileId - The unique ID for the user profile.
|
|
* @param {Object} userProfile - The user profile data to add.
|
|
* @private
|
|
*/
|
|
addUser(profileId, userProfile) {
|
|
this.logger.info(`Added User With UUID: `, profileId);
|
|
return AccountService.updateUser(profileId, userProfile);
|
|
}
|
|
|
|
/**
|
|
* Helper: Update or override existing user data.
|
|
* @param {string} profileId - The ID of the profile to update.
|
|
* @param {Object} userProfile - The data to merge into the existing profile.
|
|
* @private
|
|
*/
|
|
updateUser(profileId, userProfile) {
|
|
return AccountService.updateUser(profileId, userProfile);
|
|
}
|
|
|
|
/**
|
|
* Helper: Retrieve user data by profile ID.
|
|
* @param {string} profileId - The ID of the profile to retrieve.
|
|
* @returns {Object|null} The user profile data, or null if not found.
|
|
* @private
|
|
*/
|
|
getUserData(profileId) {
|
|
return AccountService.getUserData(profileId);
|
|
}
|
|
|
|
/**
|
|
* Helper: Read the last reset week from the database.
|
|
* @returns {Promise<number>} The last reset week number.
|
|
* @private
|
|
*/
|
|
async readLastResetWeek() {
|
|
const db = getDb();
|
|
return new Promise((resolve, reject) => {
|
|
db.get('SELECT value FROM config WHERE key = ?', ['last_reset_week'], (err, row) => {
|
|
if (err) {
|
|
this.logger.error(`Error reading last_reset_week from DB: ${err.message}`);
|
|
reject(err);
|
|
} else {
|
|
resolve(row ? parseInt(row.value, 10) : 0); // Default to 0 if not found
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper: Write the current week to the database.
|
|
* @param {number} weekNumber - The current week number.
|
|
* @returns {Promise<void>}
|
|
* @private
|
|
*/
|
|
async writeLastResetWeek(weekNumber) {
|
|
const db = getDb();
|
|
return new Promise((resolve, reject) => {
|
|
db.run('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)', ['last_reset_week', weekNumber.toString()], (err) => {
|
|
if (err) {
|
|
this.logger.error(`Error writing last_reset_week to DB: ${err.message}`);
|
|
reject(err);
|
|
} else {
|
|
this.logger.info(`Updated last_reset_week in DB to week ${weekNumber}`);
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper: Clear all entries from the main leaderboard table.
|
|
* @returns {Promise<void>} A promise that resolves when the table is cleared.
|
|
* @private
|
|
*/
|
|
async clearLeaderboard() {
|
|
const db = getDb();
|
|
return new Promise((resolve, reject) => {
|
|
db.run('DELETE FROM leaderboard', [], (err) => {
|
|
if (err) {
|
|
this.logger.error(`Error clearing leaderboard table:`, err.message);
|
|
reject(err);
|
|
} else {
|
|
this.logger.info(`Cleared leaderboard table.`);
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper: Read leaderboard data from SQLite.
|
|
* @param {boolean} isDotw - True if reading Dancer of the Week leaderboard.
|
|
* @returns {Promise<Object>} A promise that resolves to the leaderboard data.
|
|
* @private
|
|
*/
|
|
async readLeaderboard(isDotw = false) {
|
|
const db = getDb();
|
|
return new Promise((resolve, reject) => {
|
|
const tableName = isDotw ? 'dotw' : 'leaderboard';
|
|
db.all(`SELECT * FROM ${tableName}`, [], (err, rows) => {
|
|
if (err) {
|
|
this.logger.error(`Error reading ${tableName} from DB:`, err.message);
|
|
reject(err);
|
|
} else {
|
|
const data = {};
|
|
// For leaderboard, group by mapName
|
|
if (!isDotw) {
|
|
rows.forEach(row => {
|
|
if (!data[row.mapName]) {
|
|
data[row.mapName] = [];
|
|
}
|
|
data[row.mapName].push(row);
|
|
});
|
|
} else {
|
|
// For DOTW, return the week number of the first entry if available
|
|
// and all entries.
|
|
if (rows.length > 0) {
|
|
data.week = rows[0].weekNumber; // Assuming weekNumber is stored in the row
|
|
data.entries = rows;
|
|
} else {
|
|
data.week = null;
|
|
data.entries = [];
|
|
}
|
|
}
|
|
resolve(data);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper: Clear all entries from the dotw leaderboard table.
|
|
* @returns {Promise<void>} A promise that resolves when the table is cleared.
|
|
* @private
|
|
*/
|
|
async clearDotwLeaderboard() {
|
|
const db = getDb();
|
|
return new Promise((resolve, reject) => {
|
|
db.run('DELETE FROM dotw', [], (err) => {
|
|
if (err) {
|
|
this.logger.error(`Error clearing dotw table:`, err.message);
|
|
reject(err);
|
|
} else {
|
|
this.logger.info(`Cleared dotw table.`);
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper: Generate a leaderboard object from user data.
|
|
* This method is now primarily for transforming user data into a format suitable for leaderboard entries,
|
|
* not for directly interacting with the database.
|
|
* @param {Object} userDataList - All decrypted user profiles.
|
|
* @param {Request} req - The request object (for getGameVersion).
|
|
* @returns {Array} An array of leaderboard entries.
|
|
* @private
|
|
*/
|
|
generateLeaderboard(userDataList, req) {
|
|
const leaderboardEntries = [];
|
|
Object.entries(userDataList).forEach(([profileId, userProfile]) => {
|
|
if (userProfile.scores) {
|
|
Object.entries(userProfile.scores).forEach(([mapName, scoreData]) => {
|
|
leaderboardEntries.push({
|
|
mapName: mapName,
|
|
profileId: profileId,
|
|
username: userProfile.name, // Assuming 'name' is the username
|
|
score: scoreData.highest,
|
|
timestamp: scoreData.lastPlayed, // Using lastPlayed as timestamp
|
|
gameVersion: this.getGameVersion(req),
|
|
rank: userProfile.rank,
|
|
name: userProfile.name,
|
|
avatar: userProfile.avatar,
|
|
country: userProfile.country,
|
|
platformId: userProfile.platformId,
|
|
alias: userProfile.alias,
|
|
aliasGender: userProfile.aliasGender,
|
|
jdPoints: userProfile.jdPoints,
|
|
portraitBorder: userProfile.portraitBorder
|
|
});
|
|
});
|
|
}
|
|
});
|
|
this.logger.info('Leaderboard List Generated for processing.');
|
|
return leaderboardEntries;
|
|
}
|
|
|
|
/**
|
|
* Helper: Save leaderboard data to SQLite.
|
|
* This method will now handle inserting/updating multiple leaderboard entries.
|
|
* @param {Array} leaderboardEntries - An array of leaderboard entries to save.
|
|
* @param {boolean} isDotw - True if saving Dancer of the Week leaderboard.
|
|
* @returns {Promise<void>} A promise that resolves when data is saved.
|
|
* @private
|
|
*/
|
|
async saveLeaderboard(leaderboardEntries, isDotw = false) {
|
|
const db = getDb();
|
|
const tableName = isDotw ? 'dotw' : 'leaderboard';
|
|
|
|
return new Promise((resolve, reject) => {
|
|
db.serialize(() => {
|
|
db.run('BEGIN TRANSACTION;');
|
|
|
|
let stmt;
|
|
if (isDotw) {
|
|
stmt = db.prepare(`INSERT OR REPLACE INTO ${tableName} (mapName, profileId, username, score, timestamp, gameVersion, rank, name, avatar, country, platformId, alias, aliasGender, jdPoints, portraitBorder, weekNumber) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
} else {
|
|
stmt = db.prepare(`INSERT OR REPLACE INTO ${tableName} (
|
|
mapName, profileId, username, score, timestamp, name,
|
|
gameVersion, rank, avatar, country, platformId,
|
|
alias, aliasGender, jdPoints, portraitBorder
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
}
|
|
|
|
const promises = leaderboardEntries.map(entry => {
|
|
return new Promise((resolveRun, rejectRun) => {
|
|
if (isDotw) {
|
|
stmt.run(
|
|
entry.mapName,
|
|
entry.profileId,
|
|
entry.username,
|
|
entry.score,
|
|
entry.timestamp,
|
|
entry.gameVersion,
|
|
entry.rank,
|
|
entry.name,
|
|
entry.avatar,
|
|
entry.country,
|
|
entry.platformId,
|
|
entry.alias,
|
|
entry.aliasGender,
|
|
entry.jdPoints,
|
|
entry.portraitBorder,
|
|
this.getWeekNumber(),
|
|
(err) => {
|
|
if (err) rejectRun(err);
|
|
else resolveRun();
|
|
}
|
|
);
|
|
} else {
|
|
stmt.run(
|
|
entry.mapName,
|
|
entry.profileId,
|
|
entry.username,
|
|
entry.score,
|
|
entry.timestamp,
|
|
entry.name,
|
|
entry.gameVersion,
|
|
entry.rank,
|
|
entry.avatar,
|
|
entry.country,
|
|
entry.platformId,
|
|
entry.alias,
|
|
entry.aliasGender,
|
|
entry.jdPoints,
|
|
entry.portraitBorder,
|
|
(err) => {
|
|
if (err) rejectRun(err);
|
|
else resolveRun();
|
|
}
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
Promise.all(promises)
|
|
.then(() => {
|
|
stmt.finalize((err) => {
|
|
if (err) {
|
|
this.logger.error(`Error finalizing statement for ${tableName}:`, err.message);
|
|
db.run('ROLLBACK;');
|
|
reject(err);
|
|
} else {
|
|
db.run('COMMIT;', (commitErr) => {
|
|
if (commitErr) {
|
|
this.logger.error(`Error committing transaction for ${tableName}:`, commitErr.message);
|
|
reject(commitErr);
|
|
} else {
|
|
this.logger.info(`Saved ${tableName} data to DB.`);
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
})
|
|
.catch(error => {
|
|
this.logger.error(`Error during batch insert for ${tableName}:`, error.message);
|
|
db.run('ROLLBACK;');
|
|
reject(error);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle POST to /profile/v2/profiles
|
|
* @param {Request} req - The request object
|
|
* @param {Response} res - The response object
|
|
*/
|
|
async handlePostProfiles(req, res) {
|
|
const authHeader = req.header('Authorization');
|
|
const ticket = authHeader ? authHeader : null; // Keep the full header as ticket
|
|
|
|
if (!ticket) {
|
|
this.logger.warn(`POST /profile/v2/profiles: Missing Authorization header (ticket).`);
|
|
return res.status(400).send({ message: "Missing Authorization header (ticket)." });
|
|
}
|
|
|
|
const profileId = await AccountService.findUserFromTicket(ticket);
|
|
|
|
if (!profileId) {
|
|
this.logger.warn(`POST /profile/v2/profiles: Profile not found for provided ticket.`);
|
|
return res.status(400).send({ message: "Profile not found for provided ticket." });
|
|
}
|
|
|
|
let userData = await AccountService.getUserData(profileId);
|
|
|
|
if (userData) {
|
|
this.logger.info(`Updating existing profile ${profileId}`);
|
|
|
|
// Update only the fields present in the request body, preserving other fields
|
|
// Ensure the ticket is updated if present in the header
|
|
const updateData = { ...req.body };
|
|
const hashedTicket = crypto.createHash('sha256').update(ticket).digest('hex');
|
|
updateData.ticket = hashedTicket; // Always update ticket from header
|
|
const updatedProfile = await AccountService.updateUser(profileId, updateData);
|
|
|
|
return res.send({
|
|
__class: "UserProfile",
|
|
...updatedProfile.toPublicJSON()
|
|
});
|
|
} else {
|
|
// This case should ideally not be reached if profileId is always found via ticket
|
|
// However, if it is, it means a ticket was provided but no profile exists for it.
|
|
// As per previous instruction, we should not create a new profile here.
|
|
this.logger.error(`POST /profile/v2/profiles: Unexpected state - profileId found via ticket but no existing user data.`);
|
|
return res.status(400).send({ message: "Profile not found for provided ticket." });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle GET to /profile/v2/profiles
|
|
* @param {Request} req - The request object
|
|
* @param {Response} res - The response object
|
|
*/
|
|
async handleGetProfiles(req, res) {
|
|
const profileIdsParam = req.query.profileIds;
|
|
|
|
if (profileIdsParam) {
|
|
try {
|
|
const requestedProfileIds = profileIdsParam.split(',');
|
|
const profiles = [];
|
|
|
|
for (const reqProfileId of requestedProfileIds) {
|
|
const profile = await AccountService.getUserData(reqProfileId);
|
|
if (profile) {
|
|
profiles.push({
|
|
__class: "UserProfile",
|
|
...profile.toPublicJSON()
|
|
});
|
|
} else {
|
|
profiles.push({
|
|
profileId: reqProfileId,
|
|
isExisting: false
|
|
});
|
|
}
|
|
}
|
|
return res.send(profiles);
|
|
} catch (error) {
|
|
this.logger.error('Error processing profileIds:', error);
|
|
return res.status(400).send({ message: "Invalid profileIds format" });
|
|
}
|
|
}
|
|
|
|
// Fallback for single profile request or if profileIds is not present
|
|
const profileId = req.query.profileId || await this.findUserFromTicket(req.header('Authorization'));
|
|
|
|
if (!profileId) {
|
|
return res.status(400).send({ message: "Missing profileId" });
|
|
}
|
|
|
|
const userProfile = await AccountService.getUserData(profileId);
|
|
|
|
if (!userProfile) {
|
|
this.logger.info(`Profile ${profileId} not found`);
|
|
return res.status(404).send({ message: "Profile not found" });
|
|
}
|
|
|
|
return res.send({
|
|
__class: "UserProfile",
|
|
...userProfile.toPublicJSON()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle POST to /profile/v2/map-ended
|
|
* @param {Request} req - The request object
|
|
* @param {Response} res - The response object
|
|
*/
|
|
async handleMapEnded(req, res) {
|
|
const { mapName, score } = req.body;
|
|
const profileId = req.query.profileId || await this.findUserFromTicket(req.header('Authorization'));
|
|
|
|
if (!profileId) {
|
|
return res.status(400).send({ message: "Missing profileId" });
|
|
}
|
|
|
|
if (!mapName || !score) {
|
|
return res.status(400).send({ message: "Missing mapName or score" });
|
|
}
|
|
|
|
const userProfile = await AccountService.getUserData(profileId);
|
|
|
|
if (!userProfile) {
|
|
this.logger.info(`Profile ${profileId} not found`);
|
|
return res.status(404).send({ message: "Profile not found" });
|
|
}
|
|
|
|
// Perform weekly leaderboard reset check
|
|
const currentWeek = this.getWeekNumber();
|
|
const lastResetWeek = await this.readLastResetWeek();
|
|
|
|
if (currentWeek !== lastResetWeek) {
|
|
this.logger.info(`New week detected: ${currentWeek}. Resetting all leaderboards.`);
|
|
await this.clearLeaderboard(); // Clear main leaderboard
|
|
await this.clearDotwLeaderboard(); // Clear DOTW leaderboard
|
|
await this.writeLastResetWeek(currentWeek); // Update last reset week
|
|
}
|
|
|
|
// Update most played maps
|
|
await MostPlayedService.updateMostPlayed(mapName);
|
|
|
|
// Update user's score for this map
|
|
const currentScore = userProfile.scores?.[mapName]?.highest || 0;
|
|
const newHighest = Math.max(currentScore, score);
|
|
|
|
await AccountService.updateUserScore(profileId, mapName, {
|
|
highest: newHighest,
|
|
lastPlayed: new Date().toISOString(),
|
|
history: [
|
|
...(userProfile.scores?.[mapName]?.history || []),
|
|
{
|
|
score,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
].slice(-10) // Keep only last 10 scores
|
|
});
|
|
|
|
// Add to songsPlayed array if not already present
|
|
if (!userProfile.songsPlayed?.includes(mapName)) {
|
|
await AccountService.updateUser(profileId, {
|
|
songsPlayed: [...(userProfile.songsPlayed || []), mapName]
|
|
});
|
|
}
|
|
|
|
// Update leaderboards (main and DOTW)
|
|
const allAccounts = await AccountService.getAllAccounts();
|
|
const leaderboardEntries = this.generateLeaderboard(allAccounts, req);
|
|
await this.saveLeaderboard(leaderboardEntries); // Save to main leaderboard
|
|
|
|
// Save current map's score to DOTW
|
|
await this.saveLeaderboard([
|
|
{
|
|
mapName: mapName,
|
|
profileId: profileId,
|
|
username: userProfile.name,
|
|
score: newHighest,
|
|
timestamp: new Date().toISOString(),
|
|
gameVersion: this.getGameVersion(req),
|
|
rank: userProfile.rank,
|
|
name: userProfile.name,
|
|
avatar: userProfile.avatar,
|
|
country: userProfile.country,
|
|
platformId: userProfile.platformId,
|
|
alias: userProfile.alias,
|
|
aliasGender: userProfile.aliasGender,
|
|
jdPoints: userProfile.jdPoints,
|
|
portraitBorder: userProfile.portraitBorder
|
|
}
|
|
], true); // Save to DOTW leaderboard
|
|
|
|
return res.send({
|
|
__class: "MapEndResult",
|
|
isNewPersonalBest: score > currentScore,
|
|
personalBestScore: newHighest
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle DELETE to /profile/v2/favorites/maps/:MapName
|
|
* @param {Request} req - The request object
|
|
* @param {Response} res - The response object
|
|
*/
|
|
async handleDeleteFavoriteMap(req, res) {
|
|
const mapName = req.params.MapName;
|
|
const profileId = req.query.profileId || this.findUserFromTicket(req.header('Authorization'));
|
|
|
|
if (!profileId) {
|
|
return res.status(400).send({ message: "Missing profileId" });
|
|
}
|
|
|
|
if (!mapName) {
|
|
return res.status(400).send({ message: "Missing mapName" });
|
|
}
|
|
|
|
AccountService.removeMapFromFavorites(profileId, mapName);
|
|
|
|
return res.status(204).send();
|
|
}
|
|
|
|
/**
|
|
* Handle GET to /v3/profiles/sessions
|
|
* @param {Request} req - The request object
|
|
* @param {Response} res - The response object
|
|
*/
|
|
async handleGetProfileSessions(req, res) {
|
|
const profileId = req.query.profileId || this.findUserFromTicket(req.header('Authorization'));
|
|
|
|
if (!profileId) {
|
|
return res.status(400).send({ message: "Missing profileId" });
|
|
}
|
|
|
|
const userProfile = AccountService.getUserData(profileId);
|
|
|
|
if (!userProfile) {
|
|
this.logger.info(`Profile ${profileId} not found`);
|
|
return res.status(404).send({ message: "Profile not found" });
|
|
}
|
|
|
|
const clientIp = req.ip; // Get client IP
|
|
const authHeader = req.header('Authorization');
|
|
const ticket = authHeader ? authHeader.split(' ')[1] : null; // Extract ticket from "Ubi_v1 <ticket>"
|
|
|
|
// Determine platformType from X-SkuId
|
|
const skuId = req.header('X-SkuId') || '';
|
|
let platformType = 'pc'; // Default
|
|
if (skuId.includes('nx')) {
|
|
platformType = 'switch';
|
|
} else if (skuId.includes('ps4') || skuId.includes('orbis')) {
|
|
platformType = 'ps4';
|
|
} else if (skuId.includes('xboxone') || skuId.includes('durango')) {
|
|
platformType = 'xboxone';
|
|
} else if (skuId.includes('wiiu')) {
|
|
platformType = 'wiiu';
|
|
}
|
|
|
|
const serverTime = new Date().toISOString();
|
|
const expiration = new Date(Date.now() + 3600 * 1000).toISOString(); // Expires in 1 hour
|
|
|
|
return res.send({
|
|
platformType: platformType,
|
|
ticket: ticket,
|
|
twoFactorAuthenticationTicket: null,
|
|
profileId: userProfile.profileId,
|
|
userId: userProfile.userId, // Assuming userId is stored in Account model
|
|
nameOnPlatform: userProfile.name || userProfile.nickname,
|
|
environment: "Prod", // Static for now
|
|
expiration: expiration,
|
|
spaceId: "cd052712-ba1d-453a-89b9-08778888d380", // Static from example
|
|
clientIp: clientIp,
|
|
clientIpCountry: "US", // Static for now, requires IP lookup for dynamic
|
|
serverTime: serverTime,
|
|
sessionId: userProfile.profileId, // Keeping profileId as sessionId as per current implementation
|
|
sessionKey: "KJNTFMD24XOPTmpgGU3MXPuhAx3IMYSYG4YgyhPJ8rVkQDHzK1MmiOtHKrQiyL/HCOsJNCfX63oRAsyGe9CDiQ==", // Placeholder
|
|
rememberMeTicket: null
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle POST to /profile/v2/filter-players
|
|
* @param {Request} req - The request object
|
|
* @param {Response} res - The response object
|
|
*/
|
|
handleFilterPlayers(req, res) {
|
|
const { count, countryFilter, platform } = req.body;
|
|
const allAccounts = AccountService.getAllAccounts();
|
|
|
|
// Filter accounts based on criteria
|
|
const filteredAccounts = Object.values(allAccounts)
|
|
.filter(account => {
|
|
if (countryFilter && account.country !== countryFilter) {
|
|
return false;
|
|
}
|
|
if (platform && account.platformId !== platform) {
|
|
return false;
|
|
}
|
|
return true;
|
|
})
|
|
.slice(0, count || 10) // Limit to requested count or default 10
|
|
.map(account => ({
|
|
profileId: account.profileId,
|
|
name: account.name,
|
|
country: account.country,
|
|
platformId: account.platformId,
|
|
avatar: account.avatar
|
|
}));
|
|
|
|
return res.send({
|
|
__class: "FilterPlayersResponse",
|
|
players: filteredAccounts
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = new AccountRouteHandler(); |