Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement account manager #66

Merged
merged 2 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ if (WIN32)
${VERSION_RC_PATH}
)
set(WIN32_MANIFEST_PATH ${CMAKE_SOURCE_DIR}/app.manifest)

set(BUILD_SAST_LINK_SHARED OFF)
endif()

# find Slint
Expand Down
159 changes: 122 additions & 37 deletions src/Controller/Core/AccountManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@

AccountManager::AccountManager(slint::ComponentHandle<UiEntryName> uiEntry, UiBridge& bridge)
: GlobalAgent(uiEntry)
, bridge(bridge) {
, bridge(bridge)
, renewAccessTokenTimer(evento::executor()->getIoContext()) {
auto& self = *this;
self->on_request_login([this] { return requestLogin(); });
self->on_request_logout([this] { return requestLogout(); });
// TODO

loadConfig();
}

Expand All @@ -32,54 +31,118 @@
return loginState;
}

void AccountManager::requestLogin() {
if (isLogin()) {
return;
}
void AccountManager::performLogin() {
evento::executor()->asyncExecute(
[]() -> evento::Task<Result<LoginResEntity>> {
spdlog::info("Start login");
auto codeResult = co_await sast_link::login();
if (!codeResult) {
spdlog::error("Login failed: {}\n", codeResult.error());
spdlog::error("Login failed: {}", codeResult.error());
co_return Err(Error(Error::Unknown, "Login failed"));
}

spdlog::info("Login success");
auto loginResult = co_await evento::networkClient()->loginViaSastLink(
codeResult.value());
if (loginResult.isErr()) {
spdlog::error("Login failed: {}\n", loginResult.unwrapErr().what());
spdlog::error("Login failed: {}", loginResult.unwrapErr().what());
co_return Err(Error(Error::Unknown, "Login failed"));
}

spdlog::info("Login success");
co_return loginResult.unwrap();
}(),
[weak_this = weak_from_this(), &self = *this, this](Result<LoginResEntity> result) {
[&self = *this](Result<LoginResEntity> result) {
if (result.isErr()) {
self.setLoginState(false);
self.bridge.getMessageManager().showMessage(result.unwrapErr().what(),
MessageType::Error);
return;
}
auto data = result.unwrap();

self.userInfo = data.userInfo;

self.setKeychainRefreshToken(data.refreshToken);
self.setNetworkAccessToken(data.accessToken);
self.scheduleRenewAccessToken();

self.setLoginState(true);
spdlog::info("login success");
});
}

void AccountManager::performRefreshToken() {
auto& self = *this;
evento::executor()->asyncExecute(
[refreshToken = getKeychainRefreshToken()]() -> evento::Task<Result<std::monostate>> {
auto result = co_await evento::networkClient()->refreshAccessToken(
refreshToken.value_or("NONE"));
if (result.isErr()) {
spdlog::error("Failed to refresh token: {}", result.unwrapErr().what());
co_return Err(Error(Error::Network, "Failed to refresh token"));
}
co_return Ok(std::monostate{});
}(),
[weak_this = weak_from_this(), &self](Result<std::monostate> result) {
if (auto alive = weak_this.lock()) {
if (result.isErr()) {
// TODO: onLoginFail();
self.setLoginState(false);
return;
}
auto data = result.unwrap();

self.userInfo = data.userInfo;

setKeychainRefreshToken(data.refreshToken);
setNetworkAccessToken(data.accessToken);
scheduleRenewAccessToken();
scheduleRenewRefreshToken();
spdlog::info("refresh token success");
}
});
}

slint::invoke_from_event_loop([&self]() { self.setLoginState(true); });
spdlog::info("login success");
void AccountManager::performGetUserInfo() {
auto& self = *this;
evento::executor()->asyncExecute(
[]() -> evento::Task<Result<UserInfoEntity>> {
auto result = co_await evento::networkClient()->getUserInfo();
if (result.isErr()) {
spdlog::error("Failed to get user info: {}", result.unwrapErr().what());
co_return Err(Error(Error::Unknown, "Failed to get user info"));
}
co_return result.unwrap();
}(),
[weak_this = weak_from_this(), &self](Result<UserInfoEntity> result) {
if (auto alive = weak_this.lock()) {
if (result.isErr()) {
self.setLoginState(false);
return;
}
self.userInfo = result.unwrap();
spdlog::info("get user info success");
}
});
}

void AccountManager::requestLogin() {
if (isLogin()) {
return;
}
// If the token is expired, login again
if (expiredTime - 15min >= std::chrono::system_clock::now()) {
performRefreshToken();
performGetUserInfo();
scheduleRenewAccessToken();
setLoginState(true);
return;
}
performLogin();
}

void AccountManager::requestLogout() {
// TODO: net logout
if (!isLogin()) {
return;
}
auto& self = *this;
// if (logoutSuccess) {
self->set_is_login(false);
// }
self.userInfo = UserInfoEntity();
setKeychainRefreshToken("");
setNetworkAccessToken("");

renewAccessTokenTimer.cancel();

setLoginState(false);
}

UserInfoEntity AccountManager::getUserInfo() {
Expand All @@ -88,39 +151,60 @@

void AccountManager::loadConfig() {
setLoginState(false);
// TODO: load last time
auto [year, month, day] = evento::account.expire.date;
auto [hour, minute, second, _] = evento::account.expire.time;
std::tm t = {year, month, day, hour, minute, second};

expiredTime = std::chrono::system_clock::from_time_t(std::mktime(&t));
userInfo.id = evento::account.userId;
}

void AccountManager::saveConfig() {
// TODO: save
auto expire = std::chrono::system_clock::to_time_t(expiredTime);
auto expireTm = *std::localtime(&expire);
evento::account.expire
= toml::date_time{toml::date{expireTm.tm_year + 1900, expireTm.tm_mon + 1, expireTm.tm_mday},
toml::time{expireTm.tm_hour, expireTm.tm_min, expireTm.tm_sec}};
evento::account.userId = userInfo.id;
}

void AccountManager::setKeychainRefreshToken(const std::string& refreshToken) const {
keychain::Error err;
keychain::setPassword(package, service, userInfo.id, refreshToken, err);

if (err.code != 0) {
spdlog::error("Failed to save refresh token: {}\n", err.message);
spdlog::error("Failed to save refresh token: {}", err.message);
}
}

std::string AccountManager::getKeychainRefreshToken() const {
std::optional<std::string> AccountManager::getKeychainRefreshToken() const {

Check warning on line 180 in src/Controller/Core/AccountManager.cc

View workflow job for this annotation

GitHub Actions / review

method 'getKeychainRefreshToken' can be made static [readability-convert-member-functions-to-static]
Mairon1206 marked this conversation as resolved.
Show resolved Hide resolved
keychain::Error err;
auto refreshToken = keychain::getPassword(package, service, userInfo.id, err);
Mairon1206 marked this conversation as resolved.
Show resolved Hide resolved

if (err.code != 0) {
spdlog::error("Failed to save refresh token: {}\n", err.message);
spdlog::error("Failed to save refresh token: {}", err.message);
}

if (refreshToken.empty()) {
std::nullopt;
}

return refreshToken;
}

void AccountManager::scheduleRenewAccessToken() {
// TODO
}

void AccountManager::scheduleRenewRefreshToken() {
// TODO
auto& self = *this;
renewAccessTokenTimer.expires_after(55min); // 设置定时器为30分钟
renewAccessTokenTimer.async_wait(
[weak_this = weak_from_this(), &self](const boost::system::error_code& ec) {
if (ec) {
spdlog::error("Failed to renew access token: {}", ec.message());
return;
}
if (auto alive = weak_this.lock()) {
self.performRefreshToken();
}
});
}

void AccountManager::setNetworkAccessToken(std::string accessToken) {
Expand All @@ -133,6 +217,7 @@
loginState = newState;
self->set_is_login(newState);
}
onStateChanged();
}

void AccountManager::onStateChanged() {
Expand Down
17 changes: 10 additions & 7 deletions src/Controller/Core/AccountManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <Controller/Core/UiBase.h>
#include <Infrastructure/Network/ResponseStruct.h>
#include <chrono>
#include <memory>

EVENTO_UI_START

Expand All @@ -16,9 +15,12 @@

bool loginState = false;
UserInfoEntity userInfo;
// refreshToken(expired in 7d) saved to keychain

std::chrono::system_clock::time_point lastRefreshTokenRenewTime;
std::chrono::system_clock::time_point expiredTime;
net::steady_timer renewAccessTokenTimer;

static const inline std::string package = "org.sast.evento";
static const inline std::string service = "refresh-token";

public:
AccountManager(slint::ComponentHandle<UiEntryName> uiEntry, UiBridge& bridge);
Expand All @@ -35,18 +37,19 @@
void loadConfig();
void saveConfig();

static const inline std::string package = "org.sast.evento";
static const inline std::string service = "refresh-token";
void setKeychainRefreshToken(const std::string& refreshToken) const;
std::string getKeychainRefreshToken() const;
std::optional<std::string> getKeychainRefreshToken() const;

Check warning on line 41 in src/Controller/Core/AccountManager.h

View workflow job for this annotation

GitHub Actions / review

function 'getKeychainRefreshToken' should be marked [[nodiscard]] [modernize-use-nodiscard]
Mairon1206 marked this conversation as resolved.
Show resolved Hide resolved
Mairon1206 marked this conversation as resolved.
Show resolved Hide resolved

void scheduleRenewAccessToken();
void scheduleRenewRefreshToken();

static void setNetworkAccessToken(std::string accessToken);

void setLoginState(bool newState);
void onStateChanged();

void performLogin();
void performRefreshToken();
void performGetUserInfo();
};

EVENTO_UI_END
Loading