Merge pull request #1242 from PR0M3TH3AN/codex/add-access-control-check-in-login

Enforce access-control check in AuthService.login
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-15 14:58:38 -05:00
commit 8d4792e662
2 files changed
+139 -6

No files matched your search

+29
View File
@@ -500,6 +500,35 @@ export default class AuthService {
}
}
if (control && typeof control.canAccess === "function") {
let canAccess = true;
try {
canAccess = control.canAccess(candidateNpub || nextPubkey);
} catch (error) {
this.log("[AuthService] accessControl.canAccess threw", error);
canAccess = Boolean(canAccess);
}
if (!canAccess) {
let isBlocked = false;
if (typeof control.isBlacklisted === "function") {
try {
isBlocked = control.isBlacklisted(candidateNpub || nextPubkey);
} catch (error) {
this.log("[AuthService] accessControl.isBlacklisted threw", error);
isBlocked = false;
}
}
const accessError = new Error(
isBlocked
? "Your account has been blocked on this platform."
: "Access restricted to admins and moderators users only.",
);
throw accessError;
}
}
if (normalized) {
setPubkey(normalized);
if (this.nostrClient && typeof this.nostrClient === "object") {
+110 -6
View File
@@ -287,6 +287,11 @@ export default class LoginModalController {
this.nextRequestLoginOptionsResolver = null;
this.pendingTask = null;
this.modalPrepared = false;
// Track generated keys so we only create one keypair per modal session.
this.generatedKeypair = null;
// Track modal close state to reset generated keys when the modal closes.
this.modalCloseObserver = null;
this.modalCloseIntervalId = null;
this.initializeRemoteSignerStatus();
this.initialized = false;
@@ -310,6 +315,8 @@ export default class LoginModalController {
this.template = this.resolveTemplate();
this.renderProviders();
// Start tracking modal close events to reset per-session key generation state.
this.initializeModalCloseTracking();
if (typeof this.lastRemoteSignerStatus !== "undefined") {
this.applyRemoteSignerStatus(this.lastRemoteSignerStatus);
@@ -319,6 +326,57 @@ export default class LoginModalController {
this.initialized = true;
}
initializeModalCloseTracking() {
const modal = this.modalElement;
if (!(modal instanceof HTMLElement)) {
return;
}
const handleClose = () => {
if (!this.isModalOpen()) {
// Reset generated keys once the modal is closed to allow a new keypair later.
this.resetGeneratedKeypair();
}
};
// Prefer a MutationObserver, fall back to polling if needed.
if (typeof MutationObserver === "function") {
if (this.modalCloseObserver) {
return;
}
try {
const observer = new MutationObserver(handleClose);
observer.observe(modal, {
attributes: true,
attributeFilter: ["data-open", "class"],
});
this.modalCloseObserver = observer;
return;
} catch (error) {
devLogger.warn(
"[LoginModalController] Failed to observe login modal close state:",
error,
);
}
}
if (
this.modalCloseIntervalId ||
!this.window ||
typeof this.window.setInterval !== "function"
) {
return;
}
// Poll for modal close state as a fallback when observers are unavailable.
this.modalCloseIntervalId = this.window.setInterval(handleClose, 500);
}
resetGeneratedKeypair() {
// Clear cached keypair so the next modal session generates fresh keys.
this.generatedKeypair = null;
}
ensureModalPrepared() {
if (!(this.modalElement instanceof HTMLElement)) {
return null;
@@ -339,6 +397,8 @@ export default class LoginModalController {
}
this.modalPrepared = true;
// Ensure close tracking starts after modal preparation updates the element.
this.initializeModalCloseTracking();
return this.modalElement instanceof HTMLElement ? this.modalElement : null;
}
@@ -1636,12 +1696,26 @@ export default class LoginModalController {
return null;
}
// Generate keys
const sk = tools.generateSecretKey();
const pk = tools.getPublicKey(sk);
const npub = tools.nip19?.npubEncode ? tools.nip19.npubEncode(pk) : pk;
const nsec = tools.nip19?.nsecEncode ? tools.nip19.nsecEncode(sk) : "";
const hexSk = nsec ? nsec : tools.nip19?.bytesToHex ? tools.nip19.bytesToHex(sk) : ""; // Fallback if nsecEncode missing? unlikely with recent tools
// Generate keys only once per modal session to avoid duplicates.
if (!this.generatedKeypair) {
const sk = tools.generateSecretKey();
const pk = tools.getPublicKey(sk);
const npub = tools.nip19?.npubEncode ? tools.nip19.npubEncode(pk) : pk;
const nsec = tools.nip19?.nsecEncode ? tools.nip19.nsecEncode(sk) : "";
const hexSk = nsec
? nsec
: tools.nip19?.bytesToHex
? tools.nip19.bytesToHex(sk)
: "";
this.generatedKeypair = {
npub,
nsec,
hexSk,
pubkey: pk,
};
}
const { npub, nsec, hexSk } = this.generatedKeypair;
// Display keys
if (npubEl) npubEl.textContent = npub;
@@ -1695,6 +1769,36 @@ export default class LoginModalController {
if (loginBtn) {
loginBtn.addEventListener("click", () => {
// Block generated accounts if whitelist-only access is enforced.
const accessControl = this.services?.authService?.accessControl;
if (accessControl && typeof accessControl.canAccess === "function") {
let canAccess = true;
try {
canAccess = accessControl.canAccess(npub);
} catch (error) {
devLogger.warn(
"[LoginModalController] accessControl.canAccess threw:",
error,
);
canAccess = Boolean(canAccess);
}
if (!canAccess && typeof accessControl.whitelistMode === "function") {
const isWhitelistMode = accessControl.whitelistMode();
if (isWhitelistMode) {
// Surface the access error via the login error callback/notification.
const accessError = new Error(
"Access restricted to admins and moderators users only.",
);
safeInvoke(this.callbacks.onLoginError, {
message: accessError.message,
error: accessError,
});
return;
}
}
}
// Return credentials compatible with nsec provider
finish({ secret: nsec || hexSk, persist: false });
});