mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-02 10:52:27 +00:00
Merge pull request #738 from PR0M3TH3AN/codex/implement-lockdown-mode-access-control
Enforce lockdown-only login restrictions
This commit is contained in:
4 files changed
+140
-14
No files matched your search
@@ -6,6 +6,7 @@ import {
|
||||
getWhitelistMode,
|
||||
setWhitelistMode as persistWhitelistMode,
|
||||
ADMIN_WHITELIST_MODE_STORAGE_KEY,
|
||||
isLockdownMode,
|
||||
} from "./config.js";
|
||||
import { loadAdminState, persistAdminState } from "./adminListStore.js";
|
||||
import { userLogger } from "./utils/logger.js";
|
||||
@@ -390,6 +391,10 @@ class AccessControl {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isLockdownActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.whitelist.has(normalized)) {
|
||||
return true;
|
||||
}
|
||||
@@ -404,6 +409,10 @@ class AccessControl {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
isLockdownActive() {
|
||||
return Boolean(isLockdownMode);
|
||||
}
|
||||
}
|
||||
|
||||
export const accessControl = new AccessControl();
|
||||
|
||||
@@ -350,6 +350,7 @@ class Application {
|
||||
userBlocks,
|
||||
relayManager,
|
||||
logger: devLogger,
|
||||
accessControl,
|
||||
});
|
||||
this.authEventUnsubscribes.push(
|
||||
this.authService.on("auth:login", (detail) => {
|
||||
@@ -1345,6 +1346,13 @@ class Application {
|
||||
await this.authService.login(savedPubKey, { persistActive: false });
|
||||
} catch (error) {
|
||||
devLogger.error("Auto-login failed:", error);
|
||||
if (error && error.code === "site-lockdown") {
|
||||
const message = this.describeLoginError(
|
||||
error,
|
||||
"Auto-login failed. Please sign in again once lockdown ends.",
|
||||
);
|
||||
this.showStatus(message, { autoHideMs: 12000 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1991,15 +1999,35 @@ class Application {
|
||||
this.showSuccess("Profile added. Select it when you're ready to switch.");
|
||||
} catch (error) {
|
||||
devLogger.error("Failed to add profile via NIP-07:", error);
|
||||
const message =
|
||||
error && typeof error.message === "string" && error.message.trim()
|
||||
? error.message.trim()
|
||||
: "Couldn't add that profile. Please try again.";
|
||||
const message = this.describeLoginError(
|
||||
error,
|
||||
"Couldn't add that profile. Please try again.",
|
||||
);
|
||||
this.showError(message);
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
describeLoginError(error, fallbackMessage = "Failed to login. Please try again.") {
|
||||
const code =
|
||||
error && typeof error.code === "string" && error.code.trim()
|
||||
? error.code.trim()
|
||||
: null;
|
||||
if (code === "site-lockdown") {
|
||||
return "This site is temporarily locked down. Only administrators may sign in right now.";
|
||||
}
|
||||
|
||||
if (error && typeof error.message === "string") {
|
||||
const trimmed = error.message.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
return fallbackMessage;
|
||||
}
|
||||
|
||||
canCurrentUserManageBlacklist() {
|
||||
const actorNpub = this.getCurrentUserNpub();
|
||||
if (!actorNpub) {
|
||||
@@ -2307,10 +2335,10 @@ class Application {
|
||||
}
|
||||
} catch (err) {
|
||||
devLogger.error("[NIP-07 login error]", err);
|
||||
const message =
|
||||
err && typeof err.message === "string" && err.message.trim()
|
||||
? err.message.trim()
|
||||
: "Failed to login with NIP-07. Please try again.";
|
||||
const message = this.describeLoginError(
|
||||
err,
|
||||
"Failed to login with NIP-07. Please try again.",
|
||||
);
|
||||
this.showError(message);
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
@@ -2873,10 +2901,10 @@ class Application {
|
||||
if (this.uploadModal?.cancelCustomSubmitCooldown) {
|
||||
this.uploadModal.cancelCustomSubmitCooldown();
|
||||
}
|
||||
const message =
|
||||
error && typeof error.message === "string" && error.message.trim()
|
||||
? error.message.trim()
|
||||
: "Login required to publish videos.";
|
||||
const message = this.describeLoginError(
|
||||
error,
|
||||
"Login required to publish videos.",
|
||||
);
|
||||
this.showError(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,17 @@ const FAST_PROFILE_TIMEOUT_MS = 2500;
|
||||
const BACKGROUND_PROFILE_TIMEOUT_MS = 6000;
|
||||
|
||||
export default class AuthService {
|
||||
constructor({ nostrClient, userBlocks, relayManager, logger } = {}) {
|
||||
constructor({
|
||||
nostrClient,
|
||||
userBlocks,
|
||||
relayManager,
|
||||
logger,
|
||||
accessControl,
|
||||
} = {}) {
|
||||
this.nostrClient = nostrClient || null;
|
||||
this.userBlocks = userBlocks || null;
|
||||
this.relayManager = relayManager || null;
|
||||
this.accessControl = accessControl || null;
|
||||
|
||||
if (typeof logger === "function") {
|
||||
this.logger = logger;
|
||||
@@ -318,6 +325,41 @@ export default class AuthService {
|
||||
|
||||
const identityChanged = previousPubkey !== nextPubkey;
|
||||
|
||||
const control = this.accessControl;
|
||||
const candidateNpub = this.safeEncodeNpub(nextPubkey) || null;
|
||||
let lockdownActive = false;
|
||||
if (control && typeof control.isLockdownActive === "function") {
|
||||
try {
|
||||
lockdownActive = control.isLockdownActive();
|
||||
} catch (error) {
|
||||
this.log("[AuthService] accessControl.isLockdownActive threw", error);
|
||||
lockdownActive = Boolean(lockdownActive);
|
||||
}
|
||||
}
|
||||
|
||||
if (lockdownActive) {
|
||||
let isAdminCandidate = false;
|
||||
const adminCheckValue =
|
||||
candidateNpub || (typeof trimmed === "string" && trimmed ? trimmed : null);
|
||||
if (adminCheckValue && typeof control?.isAdminEditor === "function") {
|
||||
try {
|
||||
isAdminCandidate = !!control.isAdminEditor(adminCheckValue);
|
||||
} catch (error) {
|
||||
this.log("[AuthService] accessControl.isAdminEditor threw", error);
|
||||
isAdminCandidate = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAdminCandidate) {
|
||||
const lockdownError = new Error(
|
||||
"This site is temporarily locked down. Only administrators may sign in right now.",
|
||||
);
|
||||
lockdownError.code = "site-lockdown";
|
||||
lockdownError.npub = candidateNpub || adminCheckValue;
|
||||
throw lockdownError;
|
||||
}
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
setPubkey(normalized);
|
||||
if (this.nostrClient && typeof this.nostrClient === "object") {
|
||||
@@ -330,7 +372,7 @@ export default class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
const npub = this.safeEncodeNpub(nextPubkey);
|
||||
const npub = candidateNpub;
|
||||
setCurrentUserNpub(npub);
|
||||
|
||||
let savedProfilesMutated = false;
|
||||
|
||||
@@ -172,6 +172,53 @@ await (async () => {
|
||||
|
||||
const detail = await service.login(SAMPLE_PUBKEY);
|
||||
|
||||
await detail.postLoginPromise;
|
||||
|
||||
assert.equal(detail.postLogin, postLogin);
|
||||
assert.deepEqual(detail.postLogin, postLogin);
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
resetState();
|
||||
|
||||
const adminCheckCalls = [];
|
||||
const service = new AuthService({
|
||||
accessControl: {
|
||||
isLockdownActive: () => true,
|
||||
isAdminEditor: (npub) => {
|
||||
adminCheckCalls.push(npub);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let error;
|
||||
try {
|
||||
await service.login(SAMPLE_PUBKEY);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
assert(error instanceof Error);
|
||||
assert.equal(error.code, "site-lockdown");
|
||||
assert.match(error.message, /locked down/i);
|
||||
assert.equal(adminCheckCalls.length, 1);
|
||||
assert.equal(typeof adminCheckCalls[0], "string");
|
||||
assert(adminCheckCalls[0].length > 0);
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
resetState();
|
||||
|
||||
const service = new AuthService({
|
||||
accessControl: {
|
||||
isLockdownActive: () => true,
|
||||
isAdminEditor: () => true,
|
||||
},
|
||||
});
|
||||
|
||||
const detail = await service.login(SAMPLE_PUBKEY);
|
||||
|
||||
assert.ok(detail);
|
||||
assert.equal(detail.pubkey, SAMPLE_PUBKEY);
|
||||
})();
|
||||
Reference in new issue
Block a user