mirror of
https://github.com/ibratabian17/OpenParty.git
synced 2026-01-15 14:22:54 -03:00
feat: Hash user authentication tickets for enhanced security
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.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
* Account Route Handler for OpenParty
|
* Account Route Handler for OpenParty
|
||||||
* Handles user account-related routes
|
* Handles user account-related routes
|
||||||
*/
|
*/
|
||||||
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const RouteHandler = require('./RouteHandler'); // Assuming RouteHandler is in the same directory
|
const RouteHandler = require('./RouteHandler'); // Assuming RouteHandler is in the same directory
|
||||||
const MostPlayedService = require('../../services/MostPlayedService');
|
const MostPlayedService = require('../../services/MostPlayedService');
|
||||||
@@ -418,7 +419,8 @@ class AccountRouteHandler extends RouteHandler {
|
|||||||
// Update only the fields present in the request body, preserving other fields
|
// Update only the fields present in the request body, preserving other fields
|
||||||
// Ensure the ticket is updated if present in the header
|
// Ensure the ticket is updated if present in the header
|
||||||
const updateData = { ...req.body };
|
const updateData = { ...req.body };
|
||||||
updateData.ticket = ticket; // Always update ticket from header
|
const hashedTicket = crypto.createHash('sha256').update(ticket).digest('hex');
|
||||||
|
updateData.ticket = hashedTicket; // Always update ticket from header
|
||||||
const updatedProfile = await AccountService.updateUser(profileId, updateData);
|
const updatedProfile = await AccountService.updateUser(profileId, updateData);
|
||||||
|
|
||||||
return res.send({
|
return res.send({
|
||||||
@@ -698,4 +700,4 @@ class AccountRouteHandler extends RouteHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new AccountRouteHandler();
|
module.exports = new AccountRouteHandler();
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
* Ubiservices Route Handler for OpenParty
|
* Ubiservices Route Handler for OpenParty
|
||||||
* Handles Ubisoft services related routes
|
* Handles Ubisoft services related routes
|
||||||
*/
|
*/
|
||||||
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const RouteHandler = require('./RouteHandler'); // Assuming RouteHandler is in the same directory
|
const RouteHandler = require('./RouteHandler'); // Assuming RouteHandler is in the same directory
|
||||||
@@ -158,7 +159,8 @@ class UbiservicesRouteHandler extends RouteHandler {
|
|||||||
|
|
||||||
// Update user mappings
|
// Update user mappings
|
||||||
AccountService.addUserId(response.data.profileId, response.data.userId);
|
AccountService.addUserId(response.data.profileId, response.data.userId);
|
||||||
AccountService.updateUserTicket(response.data.profileId, `Ubi_v1 ${response.data.ticket}`);
|
const hashedTicket = crypto.createHash('sha256').update(`Ubi_v1 ${response.data.ticket}`).digest('hex');
|
||||||
|
AccountService.updateUserTicket(response.data.profileId, hashedTicket);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("[ACC] Error fetching from Ubisoft services", error.message);
|
console.log("[ACC] Error fetching from Ubisoft services", error.message);
|
||||||
|
|
||||||
@@ -496,4 +498,4 @@ class UbiservicesRouteHandler extends RouteHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Export an instance of the route handler
|
// Export an instance of the route handler
|
||||||
module.exports = new UbiservicesRouteHandler();
|
module.exports = new UbiservicesRouteHandler();
|
||||||
@@ -140,11 +140,21 @@ class DatabaseManager {
|
|||||||
if (err) {
|
if (err) {
|
||||||
this.logger.error('Error creating user_profiles table:', err.message);
|
this.logger.error('Error creating user_profiles table:', err.message);
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
|
||||||
this.logger.info('All tables created. Resolving initialize promise.');
|
|
||||||
resolve(this._db);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create config table
|
||||||
|
this._db.run(`CREATE TABLE IF NOT EXISTS config (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT
|
||||||
|
)`, (err) => {
|
||||||
|
if (err) {
|
||||||
|
this.logger.error('Error creating config table:', err.message);
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
this.logger.info('All tables created. Resolving initialize promise.');
|
||||||
|
resolve(this._db);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class Account {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const simpleOverwriteKeys = [
|
const simpleOverwriteKeys = [
|
||||||
'profileId', 'userId', 'username', 'nickname', 'name', 'email', 'password', 'ticket',
|
'profileId', 'userId', 'username', 'nickname', 'name', 'email', 'password',
|
||||||
'avatar', 'country', 'platformId', 'alias', 'aliasGender', 'jdPoints',
|
'avatar', 'country', 'platformId', 'alias', 'aliasGender', 'jdPoints',
|
||||||
'portraitBorder', 'rank', 'skin', 'diamondPoints', 'wdfRank', 'stars',
|
'portraitBorder', 'rank', 'skin', 'diamondPoints', 'wdfRank', 'stars',
|
||||||
'unlocks', 'language', 'firstPartyEnv'
|
'unlocks', 'language', 'firstPartyEnv'
|
||||||
@@ -212,49 +212,9 @@ class Account {
|
|||||||
* @returns {Object} Plain object representation
|
* @returns {Object} Plain object representation
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
const data = { ...this };
|
||||||
profileId: this.profileId,
|
delete data.ticket;
|
||||||
userId: this.userId,
|
return data;
|
||||||
username: this.username,
|
|
||||||
nickname: this.nickname,
|
|
||||||
name: this.name,
|
|
||||||
email: this.email,
|
|
||||||
password: this.password,
|
|
||||||
ticket: this.ticket,
|
|
||||||
avatar: this.avatar,
|
|
||||||
country: this.country,
|
|
||||||
platformId: this.platformId,
|
|
||||||
alias: this.alias,
|
|
||||||
aliasGender: this.aliasGender,
|
|
||||||
jdPoints: this.jdPoints,
|
|
||||||
portraitBorder: this.portraitBorder,
|
|
||||||
rank: this.rank,
|
|
||||||
scores: this.scores,
|
|
||||||
favorites: this.favorites,
|
|
||||||
songsPlayed: this.songsPlayed,
|
|
||||||
progression: this.progression,
|
|
||||||
history: this.history,
|
|
||||||
// New fields
|
|
||||||
skin: this.skin,
|
|
||||||
diamondPoints: this.diamondPoints,
|
|
||||||
unlockedAvatars: this.unlockedAvatars,
|
|
||||||
unlockedSkins: this.unlockedSkins,
|
|
||||||
unlockedAliases: this.unlockedAliases,
|
|
||||||
unlockedPortraitBorders: this.unlockedPortraitBorders,
|
|
||||||
wdfRank: this.wdfRank,
|
|
||||||
stars: this.stars,
|
|
||||||
unlocks: this.unlocks,
|
|
||||||
populations: this.populations,
|
|
||||||
inProgressAliases: this.inProgressAliases,
|
|
||||||
language: this.language,
|
|
||||||
firstPartyEnv: this.firstPartyEnv,
|
|
||||||
syncVersions: this.syncVersions,
|
|
||||||
otherPids: this.otherPids,
|
|
||||||
stats: this.stats,
|
|
||||||
mapHistory: this.mapHistory,
|
|
||||||
createdAt: this.createdAt,
|
|
||||||
updatedAt: this.updatedAt
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,46 +222,7 @@ class Account {
|
|||||||
* @returns {Object} Sanitized plain object representation
|
* @returns {Object} Sanitized plain object representation
|
||||||
*/
|
*/
|
||||||
toPublicJSON() {
|
toPublicJSON() {
|
||||||
const publicData = {
|
const publicData = this.toJSON();
|
||||||
profileId: this.profileId,
|
|
||||||
userId: this.userId,
|
|
||||||
username: this.username,
|
|
||||||
nickname: this.nickname,
|
|
||||||
name: this.name,
|
|
||||||
avatar: this.avatar,
|
|
||||||
country: this.country,
|
|
||||||
platformId: this.platformId,
|
|
||||||
alias: this.alias,
|
|
||||||
aliasGender: this.aliasGender,
|
|
||||||
jdPoints: this.jdPoints,
|
|
||||||
portraitBorder: this.portraitBorder,
|
|
||||||
rank: this.rank,
|
|
||||||
scores: this.scores,
|
|
||||||
favorites: this.favorites,
|
|
||||||
songsPlayed: this.songsPlayed,
|
|
||||||
progression: this.progression,
|
|
||||||
history: this.history,
|
|
||||||
// New fields
|
|
||||||
skin: this.skin,
|
|
||||||
diamondPoints: this.diamondPoints,
|
|
||||||
unlockedAvatars: this.unlockedAvatars,
|
|
||||||
unlockedSkins: this.unlockedSkins,
|
|
||||||
unlockedAliases: this.unlockedAliases,
|
|
||||||
unlockedPortraitBorders: this.unlockedPortraitBorders,
|
|
||||||
wdfRank: this.wdfRank,
|
|
||||||
stars: this.stars,
|
|
||||||
unlocks: this.unlocks,
|
|
||||||
populations: this.populations,
|
|
||||||
inProgressAliases: this.inProgressAliases,
|
|
||||||
language: this.language,
|
|
||||||
firstPartyEnv: this.firstPartyEnv,
|
|
||||||
syncVersions: this.syncVersions,
|
|
||||||
otherPids: this.otherPids,
|
|
||||||
stats: this.stats,
|
|
||||||
mapHistory: this.mapHistory,
|
|
||||||
createdAt: this.createdAt,
|
|
||||||
updatedAt: this.updatedAt
|
|
||||||
};
|
|
||||||
// Explicitly remove sensitive fields if they were somehow added
|
// Explicitly remove sensitive fields if they were somehow added
|
||||||
delete publicData.email;
|
delete publicData.email;
|
||||||
delete publicData.password;
|
delete publicData.password;
|
||||||
@@ -310,4 +231,4 @@ class Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Account;
|
module.exports = Account;
|
||||||
@@ -38,7 +38,6 @@ class AccountRepository {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
email: row.email,
|
email: row.email,
|
||||||
password: row.password, // Should be handled securely if stored
|
password: row.password, // Should be handled securely if stored
|
||||||
ticket: row.ticket,
|
|
||||||
alias: row.alias,
|
alias: row.alias,
|
||||||
aliasGender: row.aliasGender,
|
aliasGender: row.aliasGender,
|
||||||
avatar: row.avatar,
|
avatar: row.avatar,
|
||||||
@@ -119,14 +118,14 @@ class AccountRepository {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.run(`INSERT OR REPLACE INTO user_profiles (
|
db.run(`INSERT OR REPLACE INTO user_profiles (
|
||||||
profileId, userId, username, nickname, name, email, password, ticket,
|
profileId, userId, username, nickname, name, email, password,
|
||||||
alias, aliasGender, avatar, country, platformId, jdPoints, portraitBorder, rank,
|
alias, aliasGender, avatar, country, platformId, jdPoints, portraitBorder, rank,
|
||||||
scores, songsPlayed, favorites, progression, history,
|
scores, songsPlayed, favorites, progression, history,
|
||||||
skin, diamondPoints, unlockedAvatars, unlockedSkins, unlockedAliases, unlockedPortraitBorders,
|
skin, diamondPoints, unlockedAvatars, unlockedSkins, unlockedAliases, unlockedPortraitBorders,
|
||||||
wdfRank, stars, unlocks, populations, inProgressAliases, language, firstPartyEnv,
|
wdfRank, stars, unlocks, populations, inProgressAliases, language, firstPartyEnv,
|
||||||
syncVersions, otherPids, stats, mapHistory,
|
syncVersions, otherPids, stats, mapHistory,
|
||||||
createdAt, updatedAt
|
createdAt, updatedAt
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
accountData.profileId,
|
accountData.profileId,
|
||||||
accountData.userId,
|
accountData.userId,
|
||||||
@@ -135,7 +134,6 @@ class AccountRepository {
|
|||||||
accountData.name,
|
accountData.name,
|
||||||
accountData.email,
|
accountData.email,
|
||||||
accountData.password, // Ensure this is handled securely (e.g., hashed) if stored
|
accountData.password, // Ensure this is handled securely (e.g., hashed) if stored
|
||||||
accountData.ticket,
|
|
||||||
accountData.alias,
|
accountData.alias,
|
||||||
accountData.aliasGender,
|
accountData.aliasGender,
|
||||||
accountData.avatar,
|
accountData.avatar,
|
||||||
@@ -206,7 +204,6 @@ class AccountRepository {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
email: row.email,
|
email: row.email,
|
||||||
password: row.password,
|
password: row.password,
|
||||||
ticket: row.ticket,
|
|
||||||
alias: row.alias,
|
alias: row.alias,
|
||||||
aliasGender: row.aliasGender,
|
aliasGender: row.aliasGender,
|
||||||
avatar: row.avatar,
|
avatar: row.avatar,
|
||||||
@@ -276,7 +273,6 @@ class AccountRepository {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
email: row.email,
|
email: row.email,
|
||||||
password: row.password,
|
password: row.password,
|
||||||
ticket: row.ticket,
|
|
||||||
alias: row.alias,
|
alias: row.alias,
|
||||||
aliasGender: row.aliasGender,
|
aliasGender: row.aliasGender,
|
||||||
avatar: row.avatar,
|
avatar: row.avatar,
|
||||||
@@ -346,7 +342,6 @@ class AccountRepository {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
email: row.email,
|
email: row.email,
|
||||||
password: row.password,
|
password: row.password,
|
||||||
ticket: row.ticket,
|
|
||||||
alias: row.alias,
|
alias: row.alias,
|
||||||
aliasGender: row.aliasGender,
|
aliasGender: row.aliasGender,
|
||||||
avatar: row.avatar,
|
avatar: row.avatar,
|
||||||
@@ -433,4 +428,4 @@ class AccountRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new AccountRepository(); // Export a singleton instance
|
module.exports = new AccountRepository(); // Export a singleton instance
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Service for handling account-related business logic
|
* Service for handling account-related business logic
|
||||||
*/
|
*/
|
||||||
|
const crypto = require('crypto');
|
||||||
const Account = require('../models/Account');
|
const Account = require('../models/Account');
|
||||||
const AccountRepository = require('../repositories/AccountRepository');
|
const AccountRepository = require('../repositories/AccountRepository');
|
||||||
const Logger = require('../utils/logger');
|
const Logger = require('../utils/logger');
|
||||||
@@ -27,7 +28,8 @@ class AccountService {
|
|||||||
*/
|
*/
|
||||||
async findUserFromTicket(ticket) {
|
async findUserFromTicket(ticket) {
|
||||||
this.logger.info(`Finding user from ticket`);
|
this.logger.info(`Finding user from ticket`);
|
||||||
const account = await AccountRepository.findByTicket(ticket);
|
const hashedTicket = crypto.createHash('sha256').update(ticket).digest('hex');
|
||||||
|
const account = await AccountRepository.findByTicket(hashedTicket);
|
||||||
return account ? account.profileId : null;
|
return account ? account.profileId : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +75,8 @@ class AccountService {
|
|||||||
account = new Account({ profileId });
|
account = new Account({ profileId });
|
||||||
}
|
}
|
||||||
|
|
||||||
account.update({ ticket });
|
const hashedTicket = crypto.createHash('sha256').update(ticket).digest('hex');
|
||||||
|
account.update({ ticket: hashedTicket });
|
||||||
return AccountRepository.save(account);
|
return AccountRepository.save(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user