Implements createUser() endpoint to allow account creation via the KOSync protocol. Changes default sync server from sync.koreader.rocks to sync.crosspointreader.com with migration logic to preserve existing users' server settings. Extends sync protocol to include position data (spine index, page numbers, xpath) that crosspoint-sync supports while remaining compatible with standard kosync servers.
152 lines
5.3 KiB
C++
152 lines
5.3 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", "Resaved KOReader credentials to update format");
|
|
saveToFile();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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");
|
|
}
|