mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
154 lines
5.4 KiB
C++
154 lines
5.4 KiB
C++
#include "KOReaderCredentialStore.h"
|
|
|
|
#include <Logging.h>
|
|
#include <MD5Builder.h>
|
|
#include <ObfuscationUtils.h>
|
|
|
|
namespace {
|
|
// Default sync server URL. crosspoint-sync speaks the full KOSync protocol, so
|
|
// pointing at any other kosync server (e.g. https://sync.koreader.rocks:443)
|
|
// still works via the custom server URL setting.
|
|
constexpr char DEFAULT_SERVER_URL[] = "https://sync.crosspointreader.com";
|
|
|
|
// Default before config version 2. Configs saved without a version stamp and an
|
|
// empty serverUrl were implicitly syncing here — they get pinned on upgrade.
|
|
constexpr char LEGACY_DEFAULT_SERVER_URL[] = "https://sync.koreader.rocks:443";
|
|
|
|
// Bumped when a change to defaults would alter behavior for existing configs.
|
|
constexpr uint8_t CONFIG_VERSION = 2;
|
|
} // namespace
|
|
|
|
void KOReaderCredentialStore::toJson(JsonDocument& doc) const {
|
|
doc["cfgVersion"] = CONFIG_VERSION;
|
|
doc["username"] = getUsername();
|
|
doc["password_obf"] = obfuscation::obfuscateToBase64(getPassword());
|
|
doc["serverUrl"] = getServerUrl();
|
|
doc["matchMethod"] = static_cast<uint8_t>(getMatchMethod());
|
|
doc["sendMetadata"] = getSendMetadata();
|
|
doc["syncBehavior"] = static_cast<uint8_t>(getSyncBehavior());
|
|
}
|
|
|
|
bool KOReaderCredentialStore::fromJson(JsonVariantConst doc) {
|
|
std::string user = doc["username"] | "";
|
|
|
|
bool needsResave = false;
|
|
std::string pass = extractPassword(doc, needsResave);
|
|
|
|
setCredentials(user, pass);
|
|
setServerUrl(doc["serverUrl"] | "");
|
|
|
|
// The default server changed in config v2 (sync.koreader.rocks -> crosspoint-sync).
|
|
// A pre-v2 config with credentials and no explicit URL was actively syncing
|
|
// against the old default — pin that URL so the upgrade doesn't switch servers
|
|
// out from under the user. Fresh setups get the new default.
|
|
const uint8_t cfgVersion = doc["cfgVersion"] | (uint8_t)1;
|
|
if (cfgVersion < CONFIG_VERSION) {
|
|
if (getServerUrl().empty() && hasCredentials()) {
|
|
LOG_DBG("KRS", "Pre-v2 config used the old default server; pinning %s", LEGACY_DEFAULT_SERVER_URL);
|
|
setServerUrl(LEGACY_DEFAULT_SERVER_URL);
|
|
}
|
|
needsResave = true; // stamp cfgVersion so this migration runs once
|
|
}
|
|
|
|
uint8_t method = doc["matchMethod"] | (uint8_t)0;
|
|
if (method <= static_cast<uint8_t>(DocumentMatchMethod::BINARY)) {
|
|
setMatchMethod(static_cast<DocumentMatchMethod>(method));
|
|
} else {
|
|
LOG_DBG("KRS", "Invalid matchMethod %u in JSON, resetting to FILENAME", method);
|
|
setMatchMethod(DocumentMatchMethod::FILENAME);
|
|
}
|
|
setSendMetadata(doc["sendMetadata"] | false);
|
|
|
|
const JsonVariantConst behaviorValue = doc["syncBehavior"];
|
|
const bool missingBehavior = behaviorValue.isNull();
|
|
uint8_t behavior = behaviorValue | static_cast<uint8_t>(KOReaderSyncBehavior::ASK_EVERY_TIME);
|
|
if (behavior <= static_cast<uint8_t>(KOReaderSyncBehavior::SMART)) {
|
|
setSyncBehavior(static_cast<KOReaderSyncBehavior>(behavior));
|
|
needsResave = needsResave || missingBehavior;
|
|
} else {
|
|
LOG_DBG("KRS", "Invalid syncBehavior %u in JSON, resetting to ASK_EVERY_TIME", behavior);
|
|
setSyncBehavior(KOReaderSyncBehavior::ASK_EVERY_TIME);
|
|
needsResave = true;
|
|
}
|
|
|
|
if (needsResave) {
|
|
LOG_DBG("KRS", "Resaving KOReader credentials to update format");
|
|
requestResave();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void KOReaderCredentialStore::setCredentials(const std::string& user, const std::string& pass) {
|
|
username = user;
|
|
password = pass;
|
|
LOG_DBG("KRS", "Set credentials for user: %s", user.c_str());
|
|
}
|
|
|
|
std::string KOReaderCredentialStore::getMd5Password() const {
|
|
if (password.empty()) {
|
|
return "";
|
|
}
|
|
|
|
// Calculate MD5 hash of password using ESP32's MD5Builder
|
|
MD5Builder md5;
|
|
md5.begin();
|
|
md5.add(password.c_str());
|
|
md5.calculate();
|
|
|
|
return md5.toString().c_str();
|
|
}
|
|
|
|
bool KOReaderCredentialStore::hasCredentials() const { return !username.empty() && !password.empty(); }
|
|
|
|
void KOReaderCredentialStore::clearCredentials() {
|
|
username.clear();
|
|
password.clear();
|
|
saveToFile();
|
|
LOG_DBG("KRS", "Cleared KOReader credentials");
|
|
}
|
|
|
|
void KOReaderCredentialStore::setServerUrl(const std::string& url) {
|
|
serverUrl = url;
|
|
LOG_DBG("KRS", "Set server URL: %s", url.empty() ? "(default)" : url.c_str());
|
|
}
|
|
|
|
std::string KOReaderCredentialStore::getBaseUrl() const {
|
|
std::string url;
|
|
if (serverUrl.empty()) {
|
|
url = DEFAULT_SERVER_URL;
|
|
} else if (serverUrl.find("://") == std::string::npos) {
|
|
// Normalize URL: add http:// if no protocol specified (local servers typically don't have SSL)
|
|
url = "http://" + serverUrl;
|
|
} else {
|
|
url = serverUrl;
|
|
}
|
|
|
|
// Strip trailing slashes to avoid double-slash in API paths
|
|
while (!url.empty() && url.back() == '/') {
|
|
url.pop_back();
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
bool KOReaderCredentialStore::usesCrossPointSyncServer() const { return getBaseUrl() == DEFAULT_SERVER_URL; }
|
|
|
|
void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) {
|
|
matchMethod = method;
|
|
LOG_DBG("KRS", "Set match method: %s", method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
|
}
|
|
|
|
void KOReaderCredentialStore::setSendMetadata(bool enabled) {
|
|
sendMetadata = enabled;
|
|
LOG_DBG("KRS", "Set send metadata: %s", enabled ? "true" : "false");
|
|
}
|
|
|
|
void KOReaderCredentialStore::setSyncBehavior(KOReaderSyncBehavior behavior) {
|
|
if (static_cast<uint8_t>(behavior) > static_cast<uint8_t>(KOReaderSyncBehavior::SMART)) {
|
|
behavior = KOReaderSyncBehavior::ASK_EVERY_TIME;
|
|
}
|
|
syncBehavior = behavior;
|
|
LOG_DBG("KRS", "Set sync behavior: %s", behavior == KOReaderSyncBehavior::SMART ? "Smart" : "Ask");
|
|
}
|