partial logon support
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good
This commit is contained in:
parent
40e57f3262
commit
5b09333f0d
@ -23,11 +23,26 @@
|
|||||||
#define REPERTORY_INCLUDE_UI_HANDLERS_HPP_
|
#define REPERTORY_INCLUDE_UI_HANDLERS_HPP_
|
||||||
|
|
||||||
#include "events/consumers/console_consumer.hpp"
|
#include "events/consumers/console_consumer.hpp"
|
||||||
|
#include "utils/common.hpp"
|
||||||
|
|
||||||
namespace repertory::ui {
|
namespace repertory::ui {
|
||||||
class mgmt_app_config;
|
class mgmt_app_config;
|
||||||
|
|
||||||
class handlers final {
|
class handlers final {
|
||||||
|
private:
|
||||||
|
static constexpr const auto nonce_length{128U};
|
||||||
|
static constexpr const auto nonce_timeout{15U};
|
||||||
|
|
||||||
|
struct nonce_data final {
|
||||||
|
std::chrono::system_clock::time_point creation{
|
||||||
|
std::chrono::system_clock::now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string nonce{
|
||||||
|
utils::generate_random_string(nonce_length),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
handlers(mgmt_app_config *config, httplib::Server *server);
|
handlers(mgmt_app_config *config, httplib::Server *server);
|
||||||
|
|
||||||
@ -49,34 +64,50 @@ private:
|
|||||||
console_consumer console;
|
console_consumer console;
|
||||||
mutable std::mutex mtx_;
|
mutable std::mutex mtx_;
|
||||||
mutable std::unordered_map<std::string, std::recursive_mutex> mtx_lookup_;
|
mutable std::unordered_map<std::string, std::recursive_mutex> mtx_lookup_;
|
||||||
|
std::mutex nonce_mtx_;
|
||||||
|
std::unordered_map<std::string, nonce_data> nonce_lookup_;
|
||||||
|
std::condition_variable nonce_notify_;
|
||||||
|
std::unique_ptr<std::thread> nonce_thread_;
|
||||||
|
stop_type stop_requested{false};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] auto data_directory_exists(provider_type prov,
|
[[nodiscard]] auto data_directory_exists(provider_type prov,
|
||||||
std::string_view name) const -> bool;
|
std::string_view name) const -> bool;
|
||||||
|
|
||||||
void handle_get_mount(auto &&req, auto &&res) const;
|
void handle_get_mount(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_get_mount_list(auto &&res) const;
|
void handle_get_mount_list(httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_get_mount_location(auto &&req, auto &&res) const;
|
void handle_get_mount_location(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_get_mount_status(auto &&req, auto &&res) const;
|
void handle_get_mount_status(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_get_settings(auto &&res) const;
|
void handle_get_nonce(httplib::Response &res);
|
||||||
|
|
||||||
void handle_post_add_mount(auto &&req, auto &&res) const;
|
void handle_get_settings(httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_post_mount(auto &&req, auto &&res) const;
|
void handle_post_add_mount(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_put_set_value_by_name(auto &&req, auto &&res) const;
|
void handle_post_mount(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
void handle_put_settings(auto &&req, auto &&res) const;
|
void handle_put_set_value_by_name(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
|
void handle_put_settings(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const;
|
||||||
|
|
||||||
auto launch_process(provider_type prov, std::string_view name,
|
auto launch_process(provider_type prov, std::string_view name,
|
||||||
std::vector<std::string> args,
|
std::vector<std::string> args,
|
||||||
bool background = false) const
|
bool background = false) const
|
||||||
-> std::vector<std::string>;
|
-> std::vector<std::string>;
|
||||||
|
|
||||||
|
void removed_expired_nonces();
|
||||||
|
|
||||||
void set_key_value(provider_type prov, std::string_view name,
|
void set_key_value(provider_type prov, std::string_view name,
|
||||||
std::string_view key, std::string_view value) const;
|
std::string_view key, std::string_view value) const;
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
#include "app_config.hpp"
|
#include "app_config.hpp"
|
||||||
#include "events/event_system.hpp"
|
#include "events/event_system.hpp"
|
||||||
#include "rpc/common.hpp"
|
|
||||||
#include "types/repertory.hpp"
|
#include "types/repertory.hpp"
|
||||||
#include "ui/mgmt_app_config.hpp"
|
#include "ui/mgmt_app_config.hpp"
|
||||||
#include "utils/collection.hpp"
|
#include "utils/collection.hpp"
|
||||||
@ -60,7 +59,8 @@ namespace {
|
|||||||
reinterpret_cast<const unsigned char *>(
|
reinterpret_cast<const unsigned char *>(
|
||||||
&decoded.at(crypto_aead_xchacha20poly1305_IETF_NPUBBYTES)),
|
&decoded.at(crypto_aead_xchacha20poly1305_IETF_NPUBBYTES)),
|
||||||
decoded.size() - crypto_aead_xchacha20poly1305_IETF_NPUBBYTES,
|
decoded.size() - crypto_aead_xchacha20poly1305_IETF_NPUBBYTES,
|
||||||
reinterpret_cast<const unsigned char *>(REPERTORY.data()), 9U,
|
reinterpret_cast<const unsigned char *>(REPERTORY.data()),
|
||||||
|
REPERTORY.length(),
|
||||||
reinterpret_cast<const unsigned char *>(decoded.data()),
|
reinterpret_cast<const unsigned char *>(decoded.data()),
|
||||||
reinterpret_cast<const unsigned char *>(key.data()));
|
reinterpret_cast<const unsigned char *>(key.data()));
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
@ -109,15 +109,27 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
|||||||
REPERTORY_USES_FUNCTION_NAME();
|
REPERTORY_USES_FUNCTION_NAME();
|
||||||
|
|
||||||
server_->set_pre_routing_handler(
|
server_->set_pre_routing_handler(
|
||||||
[this](auto &&req, auto &&res) -> httplib::Server::HandlerResponse {
|
[this](const httplib::Request &req,
|
||||||
if (rpc::check_authorization(*config_, req)) {
|
auto &&res) -> httplib::Server::HandlerResponse {
|
||||||
|
if (req.path == "/api/v1/nonce" || req.path == "/ui" ||
|
||||||
|
req.path.starts_with("/ui/")) {
|
||||||
return httplib::Server::HandlerResponse::Unhandled;
|
return httplib::Server::HandlerResponse::Unhandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto auth =
|
||||||
|
decrypt(req.get_param_value("auth"), config_->get_api_password());
|
||||||
|
if (utils::string::begins_with(
|
||||||
|
auth, fmt::format("{}_", config_->get_api_user()))) {
|
||||||
|
auto nonce = auth.substr(config_->get_api_user().length() + 1U);
|
||||||
|
|
||||||
|
mutex_lock lock(nonce_mtx_);
|
||||||
|
if (nonce_lookup_.contains(nonce)) {
|
||||||
|
nonce_lookup_.erase(nonce);
|
||||||
|
return httplib::Server::HandlerResponse::Unhandled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.status = http_error_codes::unauthorized;
|
res.status = http_error_codes::unauthorized;
|
||||||
res.set_header(
|
|
||||||
"WWW-Authenticate",
|
|
||||||
R"(Basic realm="Repertory Management Portal", charset="UTF-8")");
|
|
||||||
return httplib::Server::HandlerResponse::Handled;
|
return httplib::Server::HandlerResponse::Handled;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,13 +167,14 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
|||||||
handle_get_mount_list(res);
|
handle_get_mount_list(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
server->Get("/api/v1/mount_status",
|
server->Get("/api/v1/mount_status", [this](auto &&req, auto &&res) {
|
||||||
[this](const httplib::Request &req, auto &&res) {
|
|
||||||
handle_get_mount_status(req, res);
|
handle_get_mount_status(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
server->Get("/api/v1/settings",
|
server->Get("/api/v1/nonce",
|
||||||
[this](const httplib::Request & /* req */, auto &&res) {
|
[this](auto && /* req */, auto &&res) { handle_get_nonce(res); });
|
||||||
|
|
||||||
|
server->Get("/api/v1/settings", [this](auto && /* req */, auto &&res) {
|
||||||
handle_get_settings(res);
|
handle_get_settings(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -225,6 +238,9 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
|||||||
|
|
||||||
event_system::instance().start();
|
event_system::instance().start();
|
||||||
|
|
||||||
|
nonce_thread_ =
|
||||||
|
std::make_unique<std::thread>([this]() { removed_expired_nonces(); });
|
||||||
|
|
||||||
server_->listen("127.0.0.1", config_->get_api_port());
|
server_->listen("127.0.0.1", config_->get_api_port());
|
||||||
if (this_server != nullptr) {
|
if (this_server != nullptr) {
|
||||||
this_server = nullptr;
|
this_server = nullptr;
|
||||||
@ -232,7 +248,20 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers::~handlers() { event_system::instance().stop(); }
|
handlers::~handlers() {
|
||||||
|
if (nonce_thread_) {
|
||||||
|
stop_requested = true;
|
||||||
|
|
||||||
|
unique_mutex_lock lock(nonce_mtx_);
|
||||||
|
nonce_notify_.notify_all();
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
nonce_thread_->join();
|
||||||
|
nonce_thread_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
event_system::instance().stop();
|
||||||
|
}
|
||||||
|
|
||||||
auto handlers::data_directory_exists(provider_type prov,
|
auto handlers::data_directory_exists(provider_type prov,
|
||||||
std::string_view name) const -> bool {
|
std::string_view name) const -> bool {
|
||||||
@ -253,7 +282,8 @@ auto handlers::data_directory_exists(provider_type prov,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_get_mount(auto &&req, auto &&res) const {
|
void handlers::handle_get_mount(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
REPERTORY_USES_FUNCTION_NAME();
|
REPERTORY_USES_FUNCTION_NAME();
|
||||||
|
|
||||||
auto prov = provider_type_from_string(req.get_param_value("type"));
|
auto prov = provider_type_from_string(req.get_param_value("type"));
|
||||||
@ -282,7 +312,7 @@ void handlers::handle_get_mount(auto &&req, auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_get_mount_list(auto &&res) const {
|
void handlers::handle_get_mount_list(httplib::Response &res) const {
|
||||||
auto data_dir = utils::file::directory{app_config::get_root_data_directory()};
|
auto data_dir = utils::file::directory{app_config::get_root_data_directory()};
|
||||||
|
|
||||||
nlohmann::json result;
|
nlohmann::json result;
|
||||||
@ -311,7 +341,8 @@ void handlers::handle_get_mount_list(auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_get_mount_location(auto &&req, auto &&res) const {
|
void handlers::handle_get_mount_location(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
auto name = req.get_param_value("name");
|
auto name = req.get_param_value("name");
|
||||||
auto prov = provider_type_from_string(req.get_param_value("type"));
|
auto prov = provider_type_from_string(req.get_param_value("type"));
|
||||||
|
|
||||||
@ -329,7 +360,8 @@ void handlers::handle_get_mount_location(auto &&req, auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_get_mount_status(auto &&req, auto &&res) const {
|
void handlers::handle_get_mount_status(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
REPERTORY_USES_FUNCTION_NAME();
|
REPERTORY_USES_FUNCTION_NAME();
|
||||||
|
|
||||||
auto name = req.get_param_value("name");
|
auto name = req.get_param_value("name");
|
||||||
@ -379,7 +411,18 @@ void handlers::handle_get_mount_status(auto &&req, auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_get_settings(auto &&res) const {
|
void handlers::handle_get_nonce(httplib::Response &res) {
|
||||||
|
mutex_lock lock(nonce_mtx_);
|
||||||
|
|
||||||
|
nonce_data nonce{};
|
||||||
|
nonce_lookup_[nonce.nonce] = nonce;
|
||||||
|
|
||||||
|
nlohmann::json data({{"nonce", nonce.nonce}});
|
||||||
|
res.set_content(data.dump(), "application/json");
|
||||||
|
res.status = http_error_codes::ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlers::handle_get_settings(httplib::Response &res) const {
|
||||||
auto settings = config_->to_json();
|
auto settings = config_->to_json();
|
||||||
settings[JSON_API_PASSWORD] = "";
|
settings[JSON_API_PASSWORD] = "";
|
||||||
settings.erase(JSON_MOUNT_LOCATIONS);
|
settings.erase(JSON_MOUNT_LOCATIONS);
|
||||||
@ -387,7 +430,8 @@ void handlers::handle_get_settings(auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_post_add_mount(auto &&req, auto &&res) const {
|
void handlers::handle_post_add_mount(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
auto name = req.get_param_value("name");
|
auto name = req.get_param_value("name");
|
||||||
auto prov = provider_type_from_string(req.get_param_value("type"));
|
auto prov = provider_type_from_string(req.get_param_value("type"));
|
||||||
if (data_directory_exists(prov, name)) {
|
if (data_directory_exists(prov, name)) {
|
||||||
@ -431,7 +475,8 @@ void handlers::handle_post_add_mount(auto &&req, auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_post_mount(auto &&req, auto &&res) const {
|
void handlers::handle_post_mount(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
auto name = req.get_param_value("name");
|
auto name = req.get_param_value("name");
|
||||||
auto prov = provider_type_from_string(req.get_param_value("type"));
|
auto prov = provider_type_from_string(req.get_param_value("type"));
|
||||||
|
|
||||||
@ -462,7 +507,8 @@ void handlers::handle_post_mount(auto &&req, auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_put_set_value_by_name(auto &&req, auto &&res) const {
|
void handlers::handle_put_set_value_by_name(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
auto name = req.get_param_value("name");
|
auto name = req.get_param_value("name");
|
||||||
auto prov = provider_type_from_string(req.get_param_value("type"));
|
auto prov = provider_type_from_string(req.get_param_value("type"));
|
||||||
|
|
||||||
@ -483,7 +529,8 @@ void handlers::handle_put_set_value_by_name(auto &&req, auto &&res) const {
|
|||||||
res.status = http_error_codes::ok;
|
res.status = http_error_codes::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlers::handle_put_settings(auto &&req, auto &&res) const {
|
void handlers::handle_put_settings(const httplib::Request &req,
|
||||||
|
httplib::Response &res) const {
|
||||||
nlohmann::json data = nlohmann::json::parse(req.get_param_value("data"));
|
nlohmann::json data = nlohmann::json::parse(req.get_param_value("data"));
|
||||||
|
|
||||||
if (data.contains(JSON_API_PASSWORD)) {
|
if (data.contains(JSON_API_PASSWORD)) {
|
||||||
@ -620,6 +667,38 @@ auto handlers::launch_process(provider_type prov, std::string_view name,
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handlers::removed_expired_nonces() {
|
||||||
|
unique_mutex_lock lock(nonce_mtx_);
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
while (not stop_requested) {
|
||||||
|
lock.lock();
|
||||||
|
auto nonces = nonce_lookup_;
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
for (const auto &[key, value] : nonces) {
|
||||||
|
if (std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
std::chrono::system_clock::now() - value.creation)
|
||||||
|
.count() >= nonce_timeout) {
|
||||||
|
lock.lock();
|
||||||
|
nonce_lookup_.erase(key);
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stop_requested) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
if (stop_requested) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nonce_notify_.wait_for(lock, std::chrono::seconds(1U));
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handlers::set_key_value(provider_type prov, std::string_view name,
|
void handlers::set_key_value(provider_type prov, std::string_view name,
|
||||||
std::string_view key,
|
std::string_view key,
|
||||||
std::string_view value) const {
|
std::string_view value) const {
|
||||||
|
@ -246,8 +246,8 @@ bool validateSettings(
|
|||||||
|
|
||||||
Future<Map<String, dynamic>> convertAllToString(
|
Future<Map<String, dynamic>> convertAllToString(
|
||||||
Map<String, dynamic> settings,
|
Map<String, dynamic> settings,
|
||||||
|
SecureKey key,
|
||||||
) async {
|
) async {
|
||||||
String? password;
|
|
||||||
Future<Map<String, dynamic>> convert(Map<String, dynamic> settings) async {
|
Future<Map<String, dynamic>> convert(Map<String, dynamic> settings) async {
|
||||||
for (var entry in settings.entries) {
|
for (var entry in settings.entries) {
|
||||||
if (entry.value is Map<String, dynamic>) {
|
if (entry.value is Map<String, dynamic>) {
|
||||||
@ -262,14 +262,7 @@ Future<Map<String, dynamic>> convertAllToString(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password == null) {
|
settings[entry.key] = encryptValue(entry.value, key);
|
||||||
password = await promptPassword();
|
|
||||||
if (password == null) {
|
|
||||||
throw NullPasswordException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings[entry.key] = encryptValue(entry.value, password!);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +279,7 @@ Future<Map<String, dynamic>> convertAllToString(
|
|||||||
return convert(settings);
|
return convert(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
String encryptValue(String value, String password) {
|
String encryptValue(String value, SecureKey key) {
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -296,17 +289,12 @@ String encryptValue(String value, String password) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
final keyHash = sodium.crypto.genericHash(
|
|
||||||
outLen: sodium.crypto.aeadXChaCha20Poly1305IETF.keyBytes,
|
|
||||||
message: Uint8List.fromList(password.toCharArray()),
|
|
||||||
);
|
|
||||||
|
|
||||||
final crypto = sodium.crypto.aeadXChaCha20Poly1305IETF;
|
final crypto = sodium.crypto.aeadXChaCha20Poly1305IETF;
|
||||||
|
|
||||||
final nonce = sodium.secureRandom(crypto.nonceBytes).extractBytes();
|
final nonce = sodium.secureRandom(crypto.nonceBytes).extractBytes();
|
||||||
final data = crypto.encrypt(
|
final data = crypto.encrypt(
|
||||||
additionalData: Uint8List.fromList('repertory'.toCharArray()),
|
additionalData: Uint8List.fromList('repertory'.toCharArray()),
|
||||||
key: SecureKey.fromList(sodium, keyHash),
|
key: key,
|
||||||
message: Uint8List.fromList(value.toCharArray()),
|
message: Uint8List.fromList(value.toCharArray()),
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
);
|
);
|
||||||
|
@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/mount.dart';
|
import 'package:repertory/models/mount.dart';
|
||||||
import 'package:repertory/models/mount_list.dart';
|
import 'package:repertory/models/mount_list.dart';
|
||||||
import 'package:repertory/screens/add_mount_screen.dart';
|
import 'package:repertory/screens/add_mount_screen.dart';
|
||||||
|
import 'package:repertory/screens/auth_screen.dart';
|
||||||
import 'package:repertory/screens/edit_mount_screen.dart';
|
import 'package:repertory/screens/edit_mount_screen.dart';
|
||||||
import 'package:repertory/screens/edit_settings_screen.dart';
|
import 'package:repertory/screens/edit_settings_screen.dart';
|
||||||
import 'package:repertory/screens/home_screen.dart';
|
import 'package:repertory/screens/home_screen.dart';
|
||||||
@ -17,8 +19,15 @@ void main() async {
|
|||||||
debugPrint('$e');
|
debugPrint('$e');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final auth = Auth();
|
||||||
runApp(
|
runApp(
|
||||||
ChangeNotifierProvider(create: (_) => MountList(), child: const MyApp()),
|
MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider(create: (_) => auth),
|
||||||
|
ChangeNotifierProvider(create: (_) => MountList(auth)),
|
||||||
|
],
|
||||||
|
child: const MyApp(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,12 +64,18 @@ class _MyAppState extends State<MyApp> {
|
|||||||
title: constants.appTitle,
|
title: constants.appTitle,
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
routes: {
|
routes: {
|
||||||
'/': (context) => const HomeScreen(title: constants.appTitle),
|
'/':
|
||||||
'/add':
|
|
||||||
(context) => const AddMountScreen(title: constants.addMountTitle),
|
|
||||||
'/settings':
|
|
||||||
(context) =>
|
(context) =>
|
||||||
const EditSettingsScreen(title: constants.appSettingsTitle),
|
const AuthCheck(child: HomeScreen(title: constants.appTitle)),
|
||||||
|
'/add':
|
||||||
|
(context) => const AuthCheck(
|
||||||
|
child: AddMountScreen(title: constants.addMountTitle),
|
||||||
|
),
|
||||||
|
'/auth': (context) => const AuthScreen(title: constants.appTitle),
|
||||||
|
'/settings':
|
||||||
|
(context) => const AuthCheck(
|
||||||
|
child: EditSettingsScreen(title: constants.appSettingsTitle),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
onGenerateRoute: (settings) {
|
onGenerateRoute: (settings) {
|
||||||
if (settings.name != '/edit') {
|
if (settings.name != '/edit') {
|
||||||
@ -70,10 +85,12 @@ class _MyAppState extends State<MyApp> {
|
|||||||
final mount = settings.arguments as Mount;
|
final mount = settings.arguments as Mount;
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return EditMountScreen(
|
return AuthCheck(
|
||||||
|
child: EditMountScreen(
|
||||||
mount: mount,
|
mount: mount,
|
||||||
title:
|
title:
|
||||||
'${mount.provider} [${formatMountName(mount.type, mount.name)}] Settings',
|
'${mount.provider} [${formatMountName(mount.type, mount.name)}] Settings',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -81,3 +98,22 @@ class _MyAppState extends State<MyApp> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AuthCheck extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const AuthCheck({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<Auth>(
|
||||||
|
builder: (context, auth, __) {
|
||||||
|
if (!auth.authenticated) {
|
||||||
|
Navigator.of(context).pushReplacementNamed('/auth');
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
55
web/repertory/lib/models/auth.dart
Normal file
55
web/repertory/lib/models/auth.dart
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:repertory/constants.dart' as constants;
|
||||||
|
import 'package:repertory/helpers.dart';
|
||||||
|
import 'package:sodium_libs/sodium_libs.dart';
|
||||||
|
|
||||||
|
class Auth with ChangeNotifier {
|
||||||
|
bool _authenticated = false;
|
||||||
|
SecureKey? _key;
|
||||||
|
String _user = "";
|
||||||
|
|
||||||
|
bool get authenticated => _authenticated;
|
||||||
|
SecureKey get key => _key!;
|
||||||
|
|
||||||
|
Future<void> authenticate(String user, String password) async {
|
||||||
|
final sodium = constants.sodium;
|
||||||
|
if (sodium == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final keyHash = sodium.crypto.genericHash(
|
||||||
|
outLen: sodium.crypto.aeadXChaCha20Poly1305IETF.keyBytes,
|
||||||
|
message: Uint8List.fromList(password.toCharArray()),
|
||||||
|
);
|
||||||
|
|
||||||
|
_authenticated = true;
|
||||||
|
_key = SecureKey.fromList(sodium, keyHash);
|
||||||
|
_user = user;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> createAuth() async {
|
||||||
|
try {
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(Uri.encodeFull('${getBaseUri()}/api/v1/nonce')),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
final nonce = jsonDecode(response.body)["nonce"];
|
||||||
|
debugPrint('nonce: $nonce');
|
||||||
|
return encryptValue('${_user}_$nonce', key);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('$e');
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
@ -3,16 +3,18 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/mount_list.dart';
|
import 'package:repertory/models/mount_list.dart';
|
||||||
import 'package:repertory/types/mount_config.dart';
|
import 'package:repertory/types/mount_config.dart';
|
||||||
|
|
||||||
class Mount with ChangeNotifier {
|
class Mount with ChangeNotifier {
|
||||||
|
final Auth _auth;
|
||||||
final MountConfig mountConfig;
|
final MountConfig mountConfig;
|
||||||
final MountList? _mountList;
|
final MountList? _mountList;
|
||||||
bool _isMounting = false;
|
bool _isMounting = false;
|
||||||
bool _isRefreshing = false;
|
bool _isRefreshing = false;
|
||||||
|
|
||||||
Mount(this.mountConfig, this._mountList, {isAdd = false}) {
|
Mount(this._auth, this.mountConfig, this._mountList, {isAdd = false}) {
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -29,9 +31,12 @@ class Mount with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> _fetch() async {
|
Future<void> _fetch() async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await _auth.createAuth();
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull('${getBaseUri()}/api/v1/mount?name=$name&type=$type'),
|
Uri.encodeFull(
|
||||||
|
'${getBaseUri()}/api/v1/mount?auth=$auth&name=$name&type=$type',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -57,10 +62,11 @@ class Mount with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> _fetchStatus() async {
|
Future<void> _fetchStatus() async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await _auth.createAuth();
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull(
|
Uri.encodeFull(
|
||||||
'${getBaseUri()}/api/v1/mount_status?name=$name&type=$type',
|
'${getBaseUri()}/api/v1/mount_status?auth=$auth&name=$name&type=$type',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -87,10 +93,11 @@ class Mount with ChangeNotifier {
|
|||||||
|
|
||||||
Future<String?> getMountLocation() async {
|
Future<String?> getMountLocation() async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await _auth.createAuth();
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull(
|
Uri.encodeFull(
|
||||||
'${getBaseUri()}/api/v1/mount_location?name=$name&type=$type',
|
'${getBaseUri()}/api/v1/mount_location?auth=$auth&name=$name&type=$type',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -120,10 +127,11 @@ class Mount with ChangeNotifier {
|
|||||||
await Future.delayed(Duration(seconds: 1));
|
await Future.delayed(Duration(seconds: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final auth = await _auth.createAuth();
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull(
|
Uri.encodeFull(
|
||||||
'${getBaseUri()}/api/v1/mount?unmount=$unmount&name=$name&type=$type&location=$location',
|
'${getBaseUri()}/api/v1/mount?auth=$auth&unmount=$unmount&name=$name&type=$type&location=$location',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -167,10 +175,11 @@ class Mount with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> setValue(String key, String value) async {
|
Future<void> setValue(String key, String value) async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await _auth.createAuth();
|
||||||
final response = await http.put(
|
final response = await http.put(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull(
|
Uri.encodeFull(
|
||||||
'${getBaseUri()}/api/v1/set_value_by_name?name=$name&type=$type&key=$key&value=$value',
|
'${getBaseUri()}/api/v1/set_value_by_name?auth=$auth&name=$name&type=$type&key=$key&value=$value',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -6,16 +6,21 @@ import 'package:flutter/material.dart' show ModalRoute;
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/mount.dart';
|
import 'package:repertory/models/mount.dart';
|
||||||
import 'package:repertory/types/mount_config.dart';
|
import 'package:repertory/types/mount_config.dart';
|
||||||
|
|
||||||
class MountList with ChangeNotifier {
|
class MountList with ChangeNotifier {
|
||||||
MountList() {
|
final Auth _auth;
|
||||||
|
|
||||||
|
MountList(this._auth) {
|
||||||
_fetch();
|
_fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Mount> _mountList = [];
|
List<Mount> _mountList = [];
|
||||||
|
|
||||||
|
Auth get auth => _auth;
|
||||||
|
|
||||||
UnmodifiableListView<Mount> get items =>
|
UnmodifiableListView<Mount> get items =>
|
||||||
UnmodifiableListView<Mount>(_mountList);
|
UnmodifiableListView<Mount>(_mountList);
|
||||||
|
|
||||||
@ -46,8 +51,9 @@ class MountList with ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> _fetch() async {
|
Future<void> _fetch() async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await _auth.createAuth();
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse('${getBaseUri()}/api/v1/mount_list'),
|
Uri.parse('${getBaseUri()}/api/v1/mount_list?auth=$auth'),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 404) {
|
if (response.statusCode == 404) {
|
||||||
@ -64,7 +70,10 @@ class MountList with ChangeNotifier {
|
|||||||
jsonDecode(response.body).forEach((type, value) {
|
jsonDecode(response.body).forEach((type, value) {
|
||||||
nextList.addAll(
|
nextList.addAll(
|
||||||
value
|
value
|
||||||
.map((name) => Mount(MountConfig(type: type, name: name), this))
|
.map(
|
||||||
|
(name) =>
|
||||||
|
Mount(_auth, MountConfig(type: type, name: name), this),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -107,11 +116,15 @@ class MountList with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final map = await convertAllToString(jsonDecode(jsonEncode(mountConfig)));
|
final auth = await _auth.createAuth();
|
||||||
|
final map = await convertAllToString(
|
||||||
|
jsonDecode(jsonEncode(mountConfig)),
|
||||||
|
_auth.key,
|
||||||
|
);
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull(
|
Uri.encodeFull(
|
||||||
'${getBaseUri()}/api/v1/add_mount?name=$name&type=$type&config=${jsonEncode(map)}',
|
'${getBaseUri()}/api/v1/add_mount?auth=$auth&name=$name&type=$type&config=${jsonEncode(map)}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/mount.dart';
|
import 'package:repertory/models/mount.dart';
|
||||||
import 'package:repertory/models/mount_list.dart';
|
import 'package:repertory/models/mount_list.dart';
|
||||||
import 'package:repertory/types/mount_config.dart';
|
import 'package:repertory/types/mount_config.dart';
|
||||||
@ -49,7 +50,9 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(constants.padding),
|
padding: const EdgeInsets.all(constants.padding),
|
||||||
child: Column(
|
child: Consumer<Auth>(
|
||||||
|
builder: (context, auth, _) {
|
||||||
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
@ -68,7 +71,9 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
DropdownButton<String>(
|
DropdownButton<String>(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
value: _mountType,
|
value: _mountType,
|
||||||
onChanged: (mountType) => _handleChange(mountType ?? ''),
|
onChanged:
|
||||||
|
(mountType) =>
|
||||||
|
_handleChange(auth, mountType ?? ''),
|
||||||
items:
|
items:
|
||||||
constants.providerTypeList
|
constants.providerTypeList
|
||||||
.map<DropdownMenuItem<String>>((item) {
|
.map<DropdownMenuItem<String>>((item) {
|
||||||
@ -104,7 +109,7 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.deny(RegExp(r'\s')),
|
FilteringTextInputFormatter.deny(RegExp(r'\s')),
|
||||||
],
|
],
|
||||||
onChanged: (_) => _handleChange(_mountType),
|
onChanged: (_) => _handleChange(auth, _mountType),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -135,7 +140,10 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
final mountList = Provider.of<MountList>(context);
|
final mountList = Provider.of<MountList>(context);
|
||||||
|
|
||||||
List<String> failed = [];
|
List<String> failed = [];
|
||||||
if (!validateSettings(_settings[_mountType]!, failed)) {
|
if (!validateSettings(
|
||||||
|
_settings[_mountType]!,
|
||||||
|
failed,
|
||||||
|
)) {
|
||||||
for (var key in failed) {
|
for (var key in failed) {
|
||||||
displayErrorMessage(
|
displayErrorMessage(
|
||||||
context,
|
context,
|
||||||
@ -145,7 +153,9 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mountList.hasConfigName(_mountNameController.text)) {
|
if (mountList.hasConfigName(
|
||||||
|
_mountNameController.text,
|
||||||
|
)) {
|
||||||
return displayErrorMessage(
|
return displayErrorMessage(
|
||||||
context,
|
context,
|
||||||
"Configuration name '${_mountNameController.text}' already exists",
|
"Configuration name '${_mountNameController.text}' already exists",
|
||||||
@ -184,12 +194,14 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleChange(String mountType) {
|
void _handleChange(Auth auth, String mountType) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final changed = _mountType != mountType;
|
final changed = _mountType != mountType;
|
||||||
|
|
||||||
@ -204,6 +216,7 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
(_mountNameController.text.isEmpty)
|
(_mountNameController.text.isEmpty)
|
||||||
? null
|
? null
|
||||||
: Mount(
|
: Mount(
|
||||||
|
auth,
|
||||||
MountConfig(
|
MountConfig(
|
||||||
name: _mountNameController.text,
|
name: _mountNameController.text,
|
||||||
settings: _settings[mountType],
|
settings: _settings[mountType],
|
||||||
|
94
web/repertory/lib/screens/auth_screen.dart
Normal file
94
web/repertory/lib/screens/auth_screen.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:repertory/constants.dart' as constants;
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
|
|
||||||
|
class AuthScreen extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
const AuthScreen({super.key, required this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AuthScreen> createState() => _AuthScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AuthScreenState extends State<AuthScreen> {
|
||||||
|
bool _enabled = true;
|
||||||
|
final _passwordController = TextEditingController();
|
||||||
|
final _userController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
|
title: Text(widget.title),
|
||||||
|
),
|
||||||
|
body: Consumer<Auth>(
|
||||||
|
builder: (context, auth, _) {
|
||||||
|
if (auth.authenticated) {
|
||||||
|
Navigator.of(context).pushReplacementNamed('/');
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(constants.padding),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Logon to Repertory Portal',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: constants.padding),
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(labelText: 'Username'),
|
||||||
|
controller: _userController,
|
||||||
|
),
|
||||||
|
const SizedBox(height: constants.padding),
|
||||||
|
TextField(
|
||||||
|
obscureText: true,
|
||||||
|
decoration: InputDecoration(labelText: 'Password'),
|
||||||
|
controller: _passwordController,
|
||||||
|
),
|
||||||
|
const SizedBox(height: constants.padding),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
_enabled
|
||||||
|
? () async {
|
||||||
|
setState(() => _enabled = false);
|
||||||
|
await auth.authenticate(
|
||||||
|
_userController.text,
|
||||||
|
_passwordController.text,
|
||||||
|
);
|
||||||
|
setState(() => _enabled = true);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text('Login'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback fn) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.setState(fn);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,9 @@ import 'dart:convert' show jsonDecode, jsonEncode;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/widgets/ui_settings.dart';
|
import 'package:repertory/widgets/ui_settings.dart';
|
||||||
|
|
||||||
class EditSettingsScreen extends StatefulWidget {
|
class EditSettingsScreen extends StatefulWidget {
|
||||||
@ -41,8 +43,9 @@ class _EditSettingsScreenState extends State<EditSettingsScreen> {
|
|||||||
|
|
||||||
Future<Map<String, dynamic>> _grabSettings() async {
|
Future<Map<String, dynamic>> _grabSettings() async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await Provider.of<Auth>(context, listen: false).createAuth();
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
Uri.parse('${getBaseUri()}/api/v1/settings'),
|
Uri.parse('${getBaseUri()}/api/v1/settings?auth=$auth'),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
|
@ -7,6 +7,7 @@ import 'package:repertory/helpers.dart'
|
|||||||
getChanged,
|
getChanged,
|
||||||
getSettingDescription,
|
getSettingDescription,
|
||||||
getSettingValidators;
|
getSettingValidators;
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/mount.dart';
|
import 'package:repertory/models/mount.dart';
|
||||||
import 'package:repertory/models/mount_list.dart';
|
import 'package:repertory/models/mount_list.dart';
|
||||||
import 'package:repertory/settings.dart';
|
import 'package:repertory/settings.dart';
|
||||||
@ -622,7 +623,8 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
widget.settings,
|
widget.settings,
|
||||||
);
|
);
|
||||||
if (settings.isNotEmpty) {
|
if (settings.isNotEmpty) {
|
||||||
convertAllToString(settings).then((map) {
|
final authProvider = Provider.of<Auth>(context, listen: false);
|
||||||
|
convertAllToString(settings, authProvider.key).then((map) {
|
||||||
map.forEach((key, value) {
|
map.forEach((key, value) {
|
||||||
if (value is Map<String, dynamic>) {
|
if (value is Map<String, dynamic>) {
|
||||||
value.forEach((subKey, subValue) {
|
value.forEach((subKey, subValue) {
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:convert' show jsonEncode;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/helpers.dart'
|
import 'package:repertory/helpers.dart'
|
||||||
show
|
show
|
||||||
convertAllToString,
|
convertAllToString,
|
||||||
@ -11,6 +12,7 @@ import 'package:repertory/helpers.dart'
|
|||||||
getSettingDescription,
|
getSettingDescription,
|
||||||
getSettingValidators,
|
getSettingValidators,
|
||||||
trimNotEmptyValidator;
|
trimNotEmptyValidator;
|
||||||
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/settings.dart';
|
import 'package:repertory/settings.dart';
|
||||||
import 'package:settings_ui/settings_ui.dart';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
|
|
||||||
@ -103,13 +105,15 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
final settings = getChanged(widget.origSettings, widget.settings);
|
final settings = getChanged(widget.origSettings, widget.settings);
|
||||||
if (settings.isNotEmpty) {
|
if (settings.isNotEmpty) {
|
||||||
convertAllToString(settings)
|
final authProvider = Provider.of<Auth>(context, listen: false);
|
||||||
|
convertAllToString(settings, authProvider.key)
|
||||||
.then((map) async {
|
.then((map) async {
|
||||||
try {
|
try {
|
||||||
|
final auth = await authProvider.createAuth();
|
||||||
final response = await http.put(
|
final response = await http.put(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
Uri.encodeFull(
|
Uri.encodeFull(
|
||||||
'${getBaseUri()}/api/v1/settings?data=${jsonEncode(map)}',
|
'${getBaseUri()}/api/v1/settings?auth=$auth&data=${jsonEncode(map)}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user