Incorporate koreader registration
This commit is contained in:
@@ -102,14 +102,19 @@ STR_AUTHENTICATING: "Authenticating..."
|
|||||||
STR_AUTH_SUCCESS: "Successfully authenticated!"
|
STR_AUTH_SUCCESS: "Successfully authenticated!"
|
||||||
STR_KOREADER_AUTH: "KOReader Auth"
|
STR_KOREADER_AUTH: "KOReader Auth"
|
||||||
STR_SYNC_READY: "KOReader sync is ready to use"
|
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_DONE: "Done"
|
||||||
STR_CLEAR_CACHE_WARNING_1: "This will clear all cached book data."
|
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_2: "All reading progress will be lost!"
|
||||||
STR_CLEAR_CACHE_WARNING_3: "Books will need to be re-indexed"
|
STR_CLEAR_CACHE_WARNING_3: "Books will need to be re-indexed"
|
||||||
STR_CLEAR_CACHE_WARNING_4: "when opened again."
|
STR_CLEAR_CACHE_WARNING_4: "when opened again."
|
||||||
STR_CLEARING_CACHE: "Clearing cache..."
|
STR_CLEARING_CACHE: "Clearing cache..."
|
||||||
STR_CACHE_CLEARED: "Cache Cleared"
|
STR_CACHE_CLEARED: "Cache cleared"
|
||||||
STR_ITEMS_REMOVED: "items removed"
|
STR_ITEMS_REMOVED: "items removed"
|
||||||
STR_FAILED_LOWER: "failed"
|
STR_FAILED_LOWER: "failed"
|
||||||
STR_CLEAR_CACHE_FAILED: "Failed to clear cache"
|
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; }
|
bool isHttpsUrl(const std::string& url) { return url.rfind("https://", 0) == 0; }
|
||||||
} // namespace
|
} // 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() {
|
KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
if (!KOREADER_STORE.hasCredentials()) {
|
||||||
LOG_DBG("KOSync", "No credentials configured");
|
LOG_DBG("KOSync", "No credentials configured");
|
||||||
@@ -195,6 +256,10 @@ const char* KOReaderSyncClient::errorString(Error error) {
|
|||||||
return "JSON parse error";
|
return "JSON parse error";
|
||||||
case NOT_FOUND:
|
case NOT_FOUND:
|
||||||
return "No progress found";
|
return "No progress found";
|
||||||
|
case USER_EXISTS:
|
||||||
|
return "Username is already taken";
|
||||||
|
case REGISTRATION_DISABLED:
|
||||||
|
return "Registration is disabled on this server";
|
||||||
default:
|
default:
|
||||||
return "Unknown error";
|
return "Unknown error";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ struct KOReaderProgress {
|
|||||||
* Base URL: https://sync.koreader.rocks:443/
|
* Base URL: https://sync.koreader.rocks:443/
|
||||||
*
|
*
|
||||||
* API Endpoints:
|
* API Endpoints:
|
||||||
* GET /users/auth - Authenticate (validate credentials)
|
* POST /users/create - Register a new user
|
||||||
* GET /syncs/progress/:document - Get progress for a document
|
* GET /users/auth - Authenticate (validate credentials)
|
||||||
* PUT /syncs/progress - Update progress for a document
|
* GET /syncs/progress/:document - Get progress for a document
|
||||||
|
* PUT /syncs/progress - Update progress for a document
|
||||||
*
|
*
|
||||||
* Authentication:
|
* Authentication:
|
||||||
* x-auth-user: username
|
* x-auth-user: username
|
||||||
@@ -29,7 +30,24 @@ struct KOReaderProgress {
|
|||||||
*/
|
*/
|
||||||
class KOReaderSyncClient {
|
class KOReaderSyncClient {
|
||||||
public:
|
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).
|
* Authenticate with the sync server (validate credentials).
|
||||||
|
|||||||
@@ -24,12 +24,21 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
state = AUTHENTICATING;
|
if (mode == Mode::REGISTER) {
|
||||||
statusMessage = tr(STR_AUTHENTICATING);
|
state = REGISTERING;
|
||||||
|
statusMessage = tr(STR_REGISTERING);
|
||||||
|
} else {
|
||||||
|
state = AUTHENTICATING;
|
||||||
|
statusMessage = tr(STR_AUTHENTICATING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
|
|
||||||
performAuthentication();
|
if (mode == Mode::REGISTER) {
|
||||||
|
performRegistration();
|
||||||
|
} else {
|
||||||
|
performAuthentication();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderAuthActivity::performAuthentication() {
|
void KOReaderAuthActivity::performAuthentication() {
|
||||||
@@ -48,6 +57,25 @@ void KOReaderAuthActivity::performAuthentication() {
|
|||||||
requestUpdate();
|
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() {
|
void KOReaderAuthActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
|
||||||
@@ -83,13 +111,18 @@ void KOReaderAuthActivity::render(RenderLock&&) {
|
|||||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const auto top = (pageHeight - height) / 2;
|
const auto top = (pageHeight - height) / 2;
|
||||||
|
|
||||||
if (state == AUTHENTICATING) {
|
if (state == AUTHENTICATING || state == REGISTERING) {
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top, statusMessage.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top, statusMessage.c_str());
|
||||||
} else if (state == SUCCESS) {
|
} 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));
|
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) {
|
} 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());
|
renderer.drawCenteredText(UI_10_FONT_ID, top + height + 10, errorMessage.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +132,7 @@ void KOReaderAuthActivity::render(RenderLock&&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderAuthActivity::loop() {
|
void KOReaderAuthActivity::loop() {
|
||||||
if (state == SUCCESS || state == FAILED) {
|
if (state == SUCCESS || state == FAILED || state == USER_EXISTS) {
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back) ||
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back) ||
|
||||||
mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -5,27 +5,31 @@
|
|||||||
#include "activities/Activity.h"
|
#include "activities/Activity.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for testing KOReader credentials.
|
* Activity for authenticating or registering a KOReader sync account.
|
||||||
* Connects to WiFi and authenticates with the KOReader sync server.
|
* Connects to WiFi, then either logs in or creates a new account on the server.
|
||||||
*/
|
*/
|
||||||
class KOReaderAuthActivity final : public Activity {
|
class KOReaderAuthActivity final : public Activity {
|
||||||
public:
|
public:
|
||||||
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
enum class Mode { LOGIN, REGISTER };
|
||||||
: Activity("KOReaderAuth", renderer, mappedInput) {}
|
|
||||||
|
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, Mode mode = Mode::LOGIN)
|
||||||
|
: Activity("KOReaderAuth", renderer, mappedInput), mode(mode) {}
|
||||||
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void render(RenderLock&&) override;
|
void render(RenderLock&&) override;
|
||||||
bool preventAutoSleep() override { return state == CONNECTING || state == AUTHENTICATING; }
|
bool preventAutoSleep() override { return state == CONNECTING || state == AUTHENTICATING || state == REGISTERING; }
|
||||||
|
|
||||||
private:
|
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;
|
State state = WIFI_SELECTION;
|
||||||
std::string statusMessage;
|
std::string statusMessage;
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
|
|
||||||
void onWifiSelectionComplete(bool success);
|
void onWifiSelectionComplete(bool success);
|
||||||
void performAuthentication();
|
void performAuthentication();
|
||||||
|
void performRegistration();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int MENU_ITEMS = 5;
|
constexpr int MENU_ITEMS = 6;
|
||||||
const StrId menuNames[MENU_ITEMS] = {StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_SYNC_SERVER_URL,
|
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_DOCUMENT_MATCHING, StrId::STR_AUTHENTICATE, StrId::STR_REGISTER};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void KOReaderSettingsActivity::onEnter() {
|
void KOReaderSettingsActivity::onEnter() {
|
||||||
@@ -107,7 +107,16 @@ void KOReaderSettingsActivity::handleSelection() {
|
|||||||
// Can't authenticate without credentials - just show message briefly
|
// Can't authenticate without credentials - just show message briefly
|
||||||
return;
|
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));
|
: std::string(tr(STR_BINARY));
|
||||||
} else if (index == 4) {
|
} else if (index == 4) {
|
||||||
return KOREADER_STORE.hasCredentials() ? "" : std::string("[") + tr(STR_SET_CREDENTIALS_FIRST) + "]";
|
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));
|
return std::string(tr(STR_NOT_SET));
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user