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_
|
||||
|
||||
#include "events/consumers/console_consumer.hpp"
|
||||
#include "utils/common.hpp"
|
||||
|
||||
namespace repertory::ui {
|
||||
class mgmt_app_config;
|
||||
|
||||
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:
|
||||
handlers(mgmt_app_config *config, httplib::Server *server);
|
||||
|
||||
@ -49,34 +64,50 @@ private:
|
||||
console_consumer console;
|
||||
mutable std::mutex mtx_;
|
||||
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:
|
||||
[[nodiscard]] auto data_directory_exists(provider_type prov,
|
||||
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,
|
||||
std::vector<std::string> args,
|
||||
bool background = false) const
|
||||
-> std::vector<std::string>;
|
||||
|
||||
void removed_expired_nonces();
|
||||
|
||||
void set_key_value(provider_type prov, std::string_view name,
|
||||
std::string_view key, std::string_view value) const;
|
||||
};
|
||||
|
@ -23,7 +23,6 @@
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "rpc/common.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "ui/mgmt_app_config.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
@ -60,7 +59,8 @@ namespace {
|
||||
reinterpret_cast<const unsigned char *>(
|
||||
&decoded.at(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 *>(key.data()));
|
||||
if (res != 0) {
|
||||
@ -109,15 +109,27 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
server_->set_pre_routing_handler(
|
||||
[this](auto &&req, auto &&res) -> httplib::Server::HandlerResponse {
|
||||
if (rpc::check_authorization(*config_, req)) {
|
||||
[this](const httplib::Request &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;
|
||||
}
|
||||
|
||||
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.set_header(
|
||||
"WWW-Authenticate",
|
||||
R"(Basic realm="Repertory Management Portal", charset="UTF-8")");
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
});
|
||||
|
||||
@ -155,13 +167,14 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
||||
handle_get_mount_list(res);
|
||||
});
|
||||
|
||||
server->Get("/api/v1/mount_status",
|
||||
[this](const httplib::Request &req, auto &&res) {
|
||||
server->Get("/api/v1/mount_status", [this](auto &&req, auto &&res) {
|
||||
handle_get_mount_status(req, res);
|
||||
});
|
||||
|
||||
server->Get("/api/v1/settings",
|
||||
[this](const httplib::Request & /* req */, auto &&res) {
|
||||
server->Get("/api/v1/nonce",
|
||||
[this](auto && /* req */, auto &&res) { handle_get_nonce(res); });
|
||||
|
||||
server->Get("/api/v1/settings", [this](auto && /* req */, auto &&res) {
|
||||
handle_get_settings(res);
|
||||
});
|
||||
|
||||
@ -225,6 +238,9 @@ handlers::handlers(mgmt_app_config *config, httplib::Server *server)
|
||||
|
||||
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());
|
||||
if (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,
|
||||
std::string_view name) const -> bool {
|
||||
@ -253,7 +282,8 @@ auto handlers::data_directory_exists(provider_type prov,
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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()};
|
||||
|
||||
nlohmann::json result;
|
||||
@ -311,7 +341,8 @@ void handlers::handle_get_mount_list(auto &&res) const {
|
||||
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 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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
settings[JSON_API_PASSWORD] = "";
|
||||
settings.erase(JSON_MOUNT_LOCATIONS);
|
||||
@ -387,7 +430,8 @@ void handlers::handle_get_settings(auto &&res) const {
|
||||
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 prov = provider_type_from_string(req.get_param_value("type"));
|
||||
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;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
if (data.contains(JSON_API_PASSWORD)) {
|
||||
@ -620,6 +667,38 @@ auto handlers::launch_process(provider_type prov, std::string_view name,
|
||||
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,
|
||||
std::string_view key,
|
||||
std::string_view value) const {
|
||||
|
@ -246,8 +246,8 @@ bool validateSettings(
|
||||
|
||||
Future<Map<String, dynamic>> convertAllToString(
|
||||
Map<String, dynamic> settings,
|
||||
SecureKey key,
|
||||
) async {
|
||||
String? password;
|
||||
Future<Map<String, dynamic>> convert(Map<String, dynamic> settings) async {
|
||||
for (var entry in settings.entries) {
|
||||
if (entry.value is Map<String, dynamic>) {
|
||||
@ -262,14 +262,7 @@ Future<Map<String, dynamic>> convertAllToString(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (password == null) {
|
||||
password = await promptPassword();
|
||||
if (password == null) {
|
||||
throw NullPasswordException();
|
||||
}
|
||||
}
|
||||
|
||||
settings[entry.key] = encryptValue(entry.value, password!);
|
||||
settings[entry.key] = encryptValue(entry.value, key);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -286,7 +279,7 @@ Future<Map<String, dynamic>> convertAllToString(
|
||||
return convert(settings);
|
||||
}
|
||||
|
||||
String encryptValue(String value, String password) {
|
||||
String encryptValue(String value, SecureKey key) {
|
||||
if (value.isEmpty) {
|
||||
return value;
|
||||
}
|
||||
@ -296,17 +289,12 @@ String encryptValue(String value, String password) {
|
||||
return value;
|
||||
}
|
||||
|
||||
final keyHash = sodium.crypto.genericHash(
|
||||
outLen: sodium.crypto.aeadXChaCha20Poly1305IETF.keyBytes,
|
||||
message: Uint8List.fromList(password.toCharArray()),
|
||||
);
|
||||
|
||||
final crypto = sodium.crypto.aeadXChaCha20Poly1305IETF;
|
||||
|
||||
final nonce = sodium.secureRandom(crypto.nonceBytes).extractBytes();
|
||||
final data = crypto.encrypt(
|
||||
additionalData: Uint8List.fromList('repertory'.toCharArray()),
|
||||
key: SecureKey.fromList(sodium, keyHash),
|
||||
key: key,
|
||||
message: Uint8List.fromList(value.toCharArray()),
|
||||
nonce: nonce,
|
||||
);
|
||||
|
@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:repertory/constants.dart' as constants;
|
||||
import 'package:repertory/helpers.dart';
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/models/mount.dart';
|
||||
import 'package:repertory/models/mount_list.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_settings_screen.dart';
|
||||
import 'package:repertory/screens/home_screen.dart';
|
||||
@ -17,8 +19,15 @@ void main() async {
|
||||
debugPrint('$e');
|
||||
}
|
||||
|
||||
final auth = Auth();
|
||||
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,
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => const HomeScreen(title: constants.appTitle),
|
||||
'/add':
|
||||
(context) => const AddMountScreen(title: constants.addMountTitle),
|
||||
'/settings':
|
||||
'/':
|
||||
(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) {
|
||||
if (settings.name != '/edit') {
|
||||
@ -70,10 +85,12 @@ class _MyAppState extends State<MyApp> {
|
||||
final mount = settings.arguments as Mount;
|
||||
return MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return EditMountScreen(
|
||||
return AuthCheck(
|
||||
child: EditMountScreen(
|
||||
mount: mount,
|
||||
title:
|
||||
'${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:http/http.dart' as http;
|
||||
import 'package:repertory/helpers.dart';
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/models/mount_list.dart';
|
||||
import 'package:repertory/types/mount_config.dart';
|
||||
|
||||
class Mount with ChangeNotifier {
|
||||
final Auth _auth;
|
||||
final MountConfig mountConfig;
|
||||
final MountList? _mountList;
|
||||
bool _isMounting = false;
|
||||
bool _isRefreshing = false;
|
||||
|
||||
Mount(this.mountConfig, this._mountList, {isAdd = false}) {
|
||||
Mount(this._auth, this.mountConfig, this._mountList, {isAdd = false}) {
|
||||
if (isAdd) {
|
||||
return;
|
||||
}
|
||||
@ -29,9 +31,12 @@ class Mount with ChangeNotifier {
|
||||
|
||||
Future<void> _fetch() async {
|
||||
try {
|
||||
final auth = await _auth.createAuth();
|
||||
final response = await http.get(
|
||||
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 {
|
||||
try {
|
||||
final auth = await _auth.createAuth();
|
||||
final response = await http.get(
|
||||
Uri.parse(
|
||||
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 {
|
||||
try {
|
||||
final auth = await _auth.createAuth();
|
||||
final response = await http.get(
|
||||
Uri.parse(
|
||||
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));
|
||||
}
|
||||
|
||||
final auth = await _auth.createAuth();
|
||||
final response = await http.post(
|
||||
Uri.parse(
|
||||
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 {
|
||||
try {
|
||||
final auth = await _auth.createAuth();
|
||||
final response = await http.put(
|
||||
Uri.parse(
|
||||
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:repertory/constants.dart' as constants;
|
||||
import 'package:repertory/helpers.dart';
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/models/mount.dart';
|
||||
import 'package:repertory/types/mount_config.dart';
|
||||
|
||||
class MountList with ChangeNotifier {
|
||||
MountList() {
|
||||
final Auth _auth;
|
||||
|
||||
MountList(this._auth) {
|
||||
_fetch();
|
||||
}
|
||||
|
||||
List<Mount> _mountList = [];
|
||||
|
||||
Auth get auth => _auth;
|
||||
|
||||
UnmodifiableListView<Mount> get items =>
|
||||
UnmodifiableListView<Mount>(_mountList);
|
||||
|
||||
@ -46,8 +51,9 @@ class MountList with ChangeNotifier {
|
||||
|
||||
Future<void> _fetch() async {
|
||||
try {
|
||||
final auth = await _auth.createAuth();
|
||||
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) {
|
||||
@ -64,7 +70,10 @@ class MountList with ChangeNotifier {
|
||||
jsonDecode(response.body).forEach((type, value) {
|
||||
nextList.addAll(
|
||||
value
|
||||
.map((name) => Mount(MountConfig(type: type, name: name), this))
|
||||
.map(
|
||||
(name) =>
|
||||
Mount(_auth, MountConfig(type: type, name: name), this),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
@ -107,11 +116,15 @@ class MountList with ChangeNotifier {
|
||||
}
|
||||
|
||||
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(
|
||||
Uri.parse(
|
||||
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:repertory/constants.dart' as constants;
|
||||
import 'package:repertory/helpers.dart';
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/models/mount.dart';
|
||||
import 'package:repertory/models/mount_list.dart';
|
||||
import 'package:repertory/types/mount_config.dart';
|
||||
@ -49,7 +50,9 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(constants.padding),
|
||||
child: Column(
|
||||
child: Consumer<Auth>(
|
||||
builder: (context, auth, _) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
@ -68,7 +71,9 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
||||
DropdownButton<String>(
|
||||
autofocus: true,
|
||||
value: _mountType,
|
||||
onChanged: (mountType) => _handleChange(mountType ?? ''),
|
||||
onChanged:
|
||||
(mountType) =>
|
||||
_handleChange(auth, mountType ?? ''),
|
||||
items:
|
||||
constants.providerTypeList
|
||||
.map<DropdownMenuItem<String>>((item) {
|
||||
@ -104,7 +109,7 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
||||
inputFormatters: [
|
||||
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);
|
||||
|
||||
List<String> failed = [];
|
||||
if (!validateSettings(_settings[_mountType]!, failed)) {
|
||||
if (!validateSettings(
|
||||
_settings[_mountType]!,
|
||||
failed,
|
||||
)) {
|
||||
for (var key in failed) {
|
||||
displayErrorMessage(
|
||||
context,
|
||||
@ -145,7 +153,9 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mountList.hasConfigName(_mountNameController.text)) {
|
||||
if (mountList.hasConfigName(
|
||||
_mountNameController.text,
|
||||
)) {
|
||||
return displayErrorMessage(
|
||||
context,
|
||||
"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(() {
|
||||
final changed = _mountType != mountType;
|
||||
|
||||
@ -204,6 +216,7 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
||||
(_mountNameController.text.isEmpty)
|
||||
? null
|
||||
: Mount(
|
||||
auth,
|
||||
MountConfig(
|
||||
name: _mountNameController.text,
|
||||
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:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:repertory/helpers.dart';
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/widgets/ui_settings.dart';
|
||||
|
||||
class EditSettingsScreen extends StatefulWidget {
|
||||
@ -41,8 +43,9 @@ class _EditSettingsScreenState extends State<EditSettingsScreen> {
|
||||
|
||||
Future<Map<String, dynamic>> _grabSettings() async {
|
||||
try {
|
||||
final auth = await Provider.of<Auth>(context, listen: false).createAuth();
|
||||
final response = await http.get(
|
||||
Uri.parse('${getBaseUri()}/api/v1/settings'),
|
||||
Uri.parse('${getBaseUri()}/api/v1/settings?auth=$auth'),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
|
@ -7,6 +7,7 @@ import 'package:repertory/helpers.dart'
|
||||
getChanged,
|
||||
getSettingDescription,
|
||||
getSettingValidators;
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/models/mount.dart';
|
||||
import 'package:repertory/models/mount_list.dart';
|
||||
import 'package:repertory/settings.dart';
|
||||
@ -622,7 +623,8 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
||||
widget.settings,
|
||||
);
|
||||
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) {
|
||||
if (value is Map<String, dynamic>) {
|
||||
value.forEach((subKey, subValue) {
|
||||
|
@ -2,6 +2,7 @@ import 'dart:convert' show jsonEncode;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:repertory/helpers.dart'
|
||||
show
|
||||
convertAllToString,
|
||||
@ -11,6 +12,7 @@ import 'package:repertory/helpers.dart'
|
||||
getSettingDescription,
|
||||
getSettingValidators,
|
||||
trimNotEmptyValidator;
|
||||
import 'package:repertory/models/auth.dart';
|
||||
import 'package:repertory/settings.dart';
|
||||
import 'package:settings_ui/settings_ui.dart';
|
||||
|
||||
@ -103,13 +105,15 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
||||
void dispose() {
|
||||
final settings = getChanged(widget.origSettings, widget.settings);
|
||||
if (settings.isNotEmpty) {
|
||||
convertAllToString(settings)
|
||||
final authProvider = Provider.of<Auth>(context, listen: false);
|
||||
convertAllToString(settings, authProvider.key)
|
||||
.then((map) async {
|
||||
try {
|
||||
final auth = await authProvider.createAuth();
|
||||
final response = await http.put(
|
||||
Uri.parse(
|
||||
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