Incorporate koreader registration
This commit is contained in:
@@ -102,14 +102,19 @@ STR_AUTHENTICATING: "Authenticating..."
|
||||
STR_AUTH_SUCCESS: "Successfully authenticated!"
|
||||
STR_KOREADER_AUTH: "KOReader Auth"
|
||||
STR_SYNC_READY: "KOReader sync is ready to use"
|
||||
STR_AUTH_FAILED: "Authentication Failed"
|
||||
STR_AUTH_FAILED: "Authentication failed"
|
||||
STR_REGISTER: "Register"
|
||||
STR_REGISTERING: "Registering..."
|
||||
STR_REGISTER_SUCCESS: "Account created successfully!"
|
||||
STR_REGISTER_FAILED: "Registration failed"
|
||||
STR_USERNAME_TAKEN: "Username already taken"
|
||||
STR_DONE: "Done"
|
||||
STR_CLEAR_CACHE_WARNING_1: "This will clear all cached book data."
|
||||
STR_CLEAR_CACHE_WARNING_2: "All reading progress will be lost!"
|
||||
STR_CLEAR_CACHE_WARNING_3: "Books will need to be re-indexed"
|
||||
STR_CLEAR_CACHE_WARNING_4: "when opened again."
|
||||
STR_CLEARING_CACHE: "Clearing cache..."
|
||||
STR_CACHE_CLEARED: "Cache Cleared"
|
||||
STR_CACHE_CLEARED: "Cache cleared"
|
||||
STR_ITEMS_REMOVED: "items removed"
|
||||
STR_FAILED_LOWER: "failed"
|
||||
STR_CLEAR_CACHE_FAILED: "Failed to clear cache"
|
||||
|
||||
@@ -28,6 +28,67 @@ void addAuthHeaders(HTTPClient& http) {
|
||||
bool isHttpsUrl(const std::string& url) { return url.rfind("https://", 0) == 0; }
|
||||
} // namespace
|
||||
|
||||
KOReaderSyncClient::Error KOReaderSyncClient::registerUser() {
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
LOG_DBG("KOSync", "No credentials configured");
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/users/create";
|
||||
LOG_DBG("KOSync", "Registering user: %s", url.c_str());
|
||||
|
||||
HTTPClient http;
|
||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||
WiFiClient plainClient;
|
||||
|
||||
if (isHttpsUrl(url)) {
|
||||
secureClient.reset(new WiFiClientSecure);
|
||||
secureClient->setInsecure();
|
||||
http.begin(*secureClient, url.c_str());
|
||||
} else {
|
||||
http.begin(plainClient, url.c_str());
|
||||
}
|
||||
|
||||
http.addHeader("Accept", "application/vnd.koreader.v1+json");
|
||||
http.addHeader("Content-Type", "application/json");
|
||||
|
||||
JsonDocument doc;
|
||||
doc["username"] = KOREADER_STORE.getUsername();
|
||||
doc["password"] = KOREADER_STORE.getMd5Password();
|
||||
std::string body;
|
||||
serializeJson(doc, body);
|
||||
|
||||
LOG_DBG("KOSync", "Register request body: <redacted credentials>");
|
||||
|
||||
const int httpCode = http.POST(body.c_str());
|
||||
const String responseBody = http.getString();
|
||||
http.end();
|
||||
|
||||
LOG_DBG("KOSync", "Register response: %d | body: %s", httpCode, responseBody.c_str());
|
||||
|
||||
if (httpCode == 201) {
|
||||
return OK;
|
||||
} else if (httpCode == 200) {
|
||||
// Some server implementations return 200 when the user already exists
|
||||
return USER_EXISTS;
|
||||
} else if (httpCode == 402) {
|
||||
// Both "user already exists" (error 2002) and "registration disabled" (error 2005)
|
||||
// return HTTP 402 on the original kosync server. Distinguish them by body text.
|
||||
String lowerBody = responseBody;
|
||||
lowerBody.toLowerCase();
|
||||
if (lowerBody.indexOf("already") >= 0) {
|
||||
return USER_EXISTS;
|
||||
}
|
||||
return REGISTRATION_DISABLED;
|
||||
} else if (httpCode == 409) {
|
||||
// korrosync returns 409 for existing users
|
||||
return USER_EXISTS;
|
||||
} else if (httpCode < 0) {
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
return SERVER_ERROR;
|
||||
}
|
||||
|
||||
KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
LOG_DBG("KOSync", "No credentials configured");
|
||||
@@ -195,6 +256,10 @@ const char* KOReaderSyncClient::errorString(Error error) {
|
||||
return "JSON parse error";
|
||||
case NOT_FOUND:
|
||||
return "No progress found";
|
||||
case USER_EXISTS:
|
||||
return "Username is already taken";
|
||||
case REGISTRATION_DISABLED:
|
||||
return "Registration is disabled on this server";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ struct KOReaderProgress {
|
||||
* Base URL: https://sync.koreader.rocks:443/
|
||||
*
|
||||
* API Endpoints:
|
||||
* GET /users/auth - Authenticate (validate credentials)
|
||||
* GET /syncs/progress/:document - Get progress for a document
|
||||
* PUT /syncs/progress - Update progress for a document
|
||||
* POST /users/create - Register a new user
|
||||
* GET /users/auth - Authenticate (validate credentials)
|
||||
* GET /syncs/progress/:document - Get progress for a document
|
||||
* PUT /syncs/progress - Update progress for a document
|
||||
*
|
||||
* Authentication:
|
||||
* x-auth-user: username
|
||||
@@ -29,7 +30,24 @@ struct KOReaderProgress {
|
||||
*/
|
||||
class KOReaderSyncClient {
|
||||
public:
|
||||
enum Error { OK = 0, NO_CREDENTIALS, NETWORK_ERROR, AUTH_FAILED, SERVER_ERROR, JSON_ERROR, NOT_FOUND };
|
||||
enum Error {
|
||||
OK = 0,
|
||||
NO_CREDENTIALS,
|
||||
NETWORK_ERROR,
|
||||
AUTH_FAILED,
|
||||
SERVER_ERROR,
|
||||
JSON_ERROR,
|
||||
NOT_FOUND,
|
||||
USER_EXISTS,
|
||||
REGISTRATION_DISABLED
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a new user account with the sync server.
|
||||
* Uses credentials already stored in KOReaderCredentialStore.
|
||||
* @return OK on success, USER_EXISTS if taken, REGISTRATION_DISABLED if server disallows it
|
||||
*/
|
||||
static Error registerUser();
|
||||
|
||||
/**
|
||||
* Authenticate with the sync server (validate credentials).
|
||||
|
||||
@@ -24,12 +24,21 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
||||
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = AUTHENTICATING;
|
||||
statusMessage = tr(STR_AUTHENTICATING);
|
||||
if (mode == Mode::REGISTER) {
|
||||
state = REGISTERING;
|
||||
statusMessage = tr(STR_REGISTERING);
|
||||
} else {
|
||||
state = AUTHENTICATING;
|
||||
statusMessage = tr(STR_AUTHENTICATING);
|
||||
}
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
performAuthentication();
|
||||
if (mode == Mode::REGISTER) {
|
||||
performRegistration();
|
||||
} else {
|
||||
performAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::performAuthentication() {
|
||||
@@ -48,6 +57,25 @@ void KOReaderAuthActivity::performAuthentication() {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::performRegistration() {
|
||||
const auto result = KOReaderSyncClient::registerUser();
|
||||
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
if (result == KOReaderSyncClient::OK) {
|
||||
state = SUCCESS;
|
||||
statusMessage = tr(STR_REGISTER_SUCCESS);
|
||||
} else if (result == KOReaderSyncClient::USER_EXISTS) {
|
||||
state = USER_EXISTS;
|
||||
errorMessage = KOReaderSyncClient::errorString(result);
|
||||
} else {
|
||||
state = FAILED;
|
||||
errorMessage = KOReaderSyncClient::errorString(result);
|
||||
}
|
||||
}
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
|
||||
@@ -83,13 +111,18 @@ void KOReaderAuthActivity::render(RenderLock&&) {
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height) / 2;
|
||||
|
||||
if (state == AUTHENTICATING) {
|
||||
if (state == AUTHENTICATING || state == REGISTERING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, statusMessage.c_str());
|
||||
} else if (state == SUCCESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_AUTH_SUCCESS), true, EpdFontFamily::BOLD);
|
||||
const char* successMsg = (mode == Mode::REGISTER) ? tr(STR_REGISTER_SUCCESS) : tr(STR_AUTH_SUCCESS);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, successMsg, true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + height + 10, tr(STR_SYNC_READY));
|
||||
} else if (state == USER_EXISTS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_USERNAME_TAKEN), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + height + 10, errorMessage.c_str());
|
||||
} else if (state == FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_AUTH_FAILED), true, EpdFontFamily::BOLD);
|
||||
const char* failedMsg = (mode == Mode::REGISTER) ? tr(STR_REGISTER_FAILED) : tr(STR_AUTH_FAILED);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, failedMsg, true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + height + 10, errorMessage.c_str());
|
||||
}
|
||||
|
||||
@@ -99,7 +132,7 @@ void KOReaderAuthActivity::render(RenderLock&&) {
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::loop() {
|
||||
if (state == SUCCESS || state == FAILED) {
|
||||
if (state == SUCCESS || state == FAILED || state == USER_EXISTS) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
finish();
|
||||
|
||||
@@ -5,27 +5,31 @@
|
||||
#include "activities/Activity.h"
|
||||
|
||||
/**
|
||||
* Activity for testing KOReader credentials.
|
||||
* Connects to WiFi and authenticates with the KOReader sync server.
|
||||
* Activity for authenticating or registering a KOReader sync account.
|
||||
* Connects to WiFi, then either logs in or creates a new account on the server.
|
||||
*/
|
||||
class KOReaderAuthActivity final : public Activity {
|
||||
public:
|
||||
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("KOReaderAuth", renderer, mappedInput) {}
|
||||
enum class Mode { LOGIN, REGISTER };
|
||||
|
||||
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, Mode mode = Mode::LOGIN)
|
||||
: Activity("KOReaderAuth", renderer, mappedInput), mode(mode) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
bool preventAutoSleep() override { return state == CONNECTING || state == AUTHENTICATING; }
|
||||
bool preventAutoSleep() override { return state == CONNECTING || state == AUTHENTICATING || state == REGISTERING; }
|
||||
|
||||
private:
|
||||
enum State { WIFI_SELECTION, CONNECTING, AUTHENTICATING, SUCCESS, FAILED };
|
||||
enum State { WIFI_SELECTION, CONNECTING, AUTHENTICATING, REGISTERING, SUCCESS, FAILED, USER_EXISTS };
|
||||
|
||||
Mode mode;
|
||||
State state = WIFI_SELECTION;
|
||||
std::string statusMessage;
|
||||
std::string errorMessage;
|
||||
|
||||
void onWifiSelectionComplete(bool success);
|
||||
void performAuthentication();
|
||||
void performRegistration();
|
||||
};
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
constexpr int MENU_ITEMS = 5;
|
||||
const StrId menuNames[MENU_ITEMS] = {StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_SYNC_SERVER_URL,
|
||||
StrId::STR_DOCUMENT_MATCHING, StrId::STR_AUTHENTICATE};
|
||||
constexpr int MENU_ITEMS = 6;
|
||||
const StrId menuNames[MENU_ITEMS] = {StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_SYNC_SERVER_URL,
|
||||
StrId::STR_DOCUMENT_MATCHING, StrId::STR_AUTHENTICATE, StrId::STR_REGISTER};
|
||||
} // namespace
|
||||
|
||||
void KOReaderSettingsActivity::onEnter() {
|
||||
@@ -107,7 +107,16 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
// Can't authenticate without credentials - just show message briefly
|
||||
return;
|
||||
}
|
||||
startActivityForResult(std::make_unique<KOReaderAuthActivity>(renderer, mappedInput), [](const ActivityResult&) {});
|
||||
startActivityForResult(std::make_unique<KOReaderAuthActivity>(renderer, mappedInput, KOReaderAuthActivity::Mode::LOGIN),
|
||||
[](const ActivityResult&) {});
|
||||
} else if (selectedIndex == 5) {
|
||||
// Register
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
return;
|
||||
}
|
||||
startActivityForResult(
|
||||
std::make_unique<KOReaderAuthActivity>(renderer, mappedInput, KOReaderAuthActivity::Mode::REGISTER),
|
||||
[](const ActivityResult&) {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +150,8 @@ void KOReaderSettingsActivity::render(RenderLock&&) {
|
||||
: std::string(tr(STR_BINARY));
|
||||
} else if (index == 4) {
|
||||
return KOREADER_STORE.hasCredentials() ? "" : std::string("[") + tr(STR_SET_CREDENTIALS_FIRST) + "]";
|
||||
} else if (index == 5) {
|
||||
return KOREADER_STORE.hasCredentials() ? "" : std::string("[") + tr(STR_SET_CREDENTIALS_FIRST) + "]";
|
||||
}
|
||||
return std::string(tr(STR_NOT_SET));
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user