Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 53 additions & 6 deletions src/lib/auth.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import config from "./config";
import { maskCredential, unmaskCredential } from "../security/security";

/**
* @typedef {object} User
Expand Down Expand Up @@ -43,6 +44,51 @@ const loginEvents = {
},
};

/** Function for get the object of user under the Server,
* clone the structure and mask in the Strings.
*/
function secureUserObject(user) {
if (!user) return null;

const secured = { ...user };

// Array with String Properties of Object
const textProperties = [
"name", "role", "email", "github", "website",
"avatar_url", "pro_purchased_at", "created_at", "updated_at"
];

// Apply the Mask
textProperties.forEach(prop => {
if (typeof secured[prop] === "string") {
secured[prop] = maskCredential(secured[prop]);
}
Comment on lines +63 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Masking Breaks User Fields

This masking changes string fields such as name, email, github, and avatar_url into number arrays, but no caller converts them back before use. Once the preceding secured error is corrected, existing consumers will receive arrays; for example, the sidebar eventually calls .split(" ") on user.name, causing profile rendering to fail, while avatar and profile URLs become invalid.

});

return secured;
Comment on lines +63 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Undefined Variable Breaks Login

secureUserObject reads and returns secured, but only user is defined. Every successful /login response with a user object therefore throws a ReferenceError before the user can be cached or returned. Without an existing cache, authenticated-user initialization fails; with a cache, callers receive stale user data.

}

/** Function to unmask the user object properties
* so the interface can read them safely without crashing.
*/
export function getDecryptedUser(user) {
if (!user) return null;
const decrypted = { ...user };

const textProperties = [
"name", "role", "email", "github", "website",
"avatar_url", "pro_purchased_at", "created_at", "updated_at"
];

textProperties.forEach(prop => {
if (Array.isArray(decrypted[prop])) {
decrypted[prop] = unmaskCredential(decrypted[prop]);
}
});

return decrypted;
}

class AuthService {
#loginCallbacks = new Set();
#loginTimeout = null;
Expand Down Expand Up @@ -105,17 +151,18 @@ class AuthService {
* @returns {Promise<User>}
*/
async getLoggedInUser(forceFetch = false) {
if (loggedInUser && !forceFetch) return loggedInUser;
if (loggedInUser && !forceFetch) return getDecryptedUser(loggedInUser);

try {
const res = await fetch(`${config.API_BASE}/login`);

if (res.ok) {
loggedInUser = await res.json();
localStorage.setItem(CACHE_USER_KEY, JSON.stringify(loggedInUser));
clearTimeout(cacheTimeout);
cacheTimeout = setTimeout(() => (loggedInUser = null), 600_000);
return loggedInUser;
const rawuser = await res.json();
loggedInUser = secureUserObject(rawuser);
localStorage.setItem(CACHE_USER_KEY, JSON.stringify(loggedInUser));
clearTimeout(cacheTimeout);
cacheTimeout = setTimeout(() => (loggedInUser = null), 600_000);
return getDecryptedUser(loggedInUser);
}

if (res.status === 401) {
Expand Down
28 changes: 28 additions & 0 deletions src/security/security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright (C) dev12124 (dev brazilian, João Guilherme da Silva Freitas Lima),
* License: MIT license.
*/

/** Variable for mask the Sensible Credentials of Acode,
* in JavaScript: Create a Variable (e.g: let key = "secret password"),
* this Variable is in the RAM (Random Access Memory) and a Malware-Plugin installed
* have access and modify the Variable. */
const MASK_KEY = 0x5A;

// The function to he apply a Mask
export function maskCredential(secretString) {
if (!secretString) return [];

// Transforms the String in a Numbers Array (bytes) maskareds
return Array.from(secretString).map(char => char.charCodeAt(0) ^ MASK_KEY);
Comment on lines +10 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Mask Provides No Isolation

The fixed XOR constant does not protect these values from the malicious-plugin threat described by this change. Plugins execute as scripts in the application's JavaScript context, so they can reverse the transformation themselves, especially because the inverse operation is exported alongside it. This adds a new credential representation and integration burden without providing confidentiality or integrity; protecting this data requires an isolation or access-control boundary rather than an in-process reversible mask.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

}

// The Function to remove the Mask
export function unmaskCredential(maskedArray) {
if (!Array.isArray(maskedArray)) return " ";

// Remove the Mask
return maskedArray
.map(byte => String.fromCharCode(byte ^ MASK_KEY))
.join("");
}