Implement authorization
Read username from user config, check existence of user and then check password.
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
#include "path.c"
|
||||
|
||||
typedef struct {
|
||||
bool is_dir;
|
||||
SV name;
|
||||
|
||||
@@ -11,7 +11,7 @@ static Noh_String build_path_(bool url, Noh_String_View *first, ...) {
|
||||
Noh_String_View elem = *elem_;
|
||||
while (elem.count > 0) {
|
||||
Noh_String_View component = noh_sv_chop_by_delim(&elem, '/');
|
||||
if (noh_sv_eq(component, sv(".."))) continue;
|
||||
if (noh_sv_eq(component, noh_sv_from_cstr(".."))) continue;
|
||||
|
||||
if (component.count > 0) {
|
||||
if (start) start = false; else noh_string_append_cstr(&result, "/");
|
||||
|
||||
92
src/server.c
92
src/server.c
@@ -5,25 +5,82 @@
|
||||
#define NOH_IMPLEMENTATION
|
||||
#include "noh.h"
|
||||
|
||||
#define NO_AUTH
|
||||
Noh_Arena temp;
|
||||
|
||||
#include "path.c"
|
||||
#include "users.c"
|
||||
|
||||
#define SV Noh_String_View
|
||||
#define sv noh_sv_from_cstr
|
||||
|
||||
/* To disable Basic auth for testing:
|
||||
#define NO_AUTH
|
||||
This will ignore any auth headers and always log in with username demo.
|
||||
*/
|
||||
|
||||
#define SV Noh_String_View
|
||||
#define sv noh_sv_from_cstr
|
||||
|
||||
Noh_Arena temp;
|
||||
|
||||
#include "id_store.c"
|
||||
#include "library.c"
|
||||
|
||||
typedef struct {
|
||||
SV base_path;
|
||||
SV library_name;
|
||||
} Settings;
|
||||
|
||||
// Authorizes the username from the Basic authorization info in the provided connection.
|
||||
// The username data is guaranteed to live as long as the connection lives.
|
||||
static bool authorize_user(struct MHD_Connection *connection, SV *username, Settings *settings) {
|
||||
#ifdef NO_AUTH
|
||||
(void)connection;
|
||||
|
||||
*username = sv("demo");
|
||||
return false;
|
||||
#else
|
||||
bool result = true;
|
||||
|
||||
struct MHD_BasicAuthInfo *auth = MHD_basic_auth_get_username_password3(connection);
|
||||
if (!auth) return false;
|
||||
|
||||
SV provided_username = { .elems = auth->username, .count = auth->username_len };
|
||||
SV provided_password = { .elems = auth->password, .count = auth->password_len };
|
||||
|
||||
// Read the users file.
|
||||
noh_arena_save(&temp);
|
||||
Noh_String_View file_name = noh_sv_from_cstr("users.conf");
|
||||
Noh_String users_file_path = build_path(false, &settings->base_path, &file_name);
|
||||
noh_string_append_null(&users_file_path);
|
||||
|
||||
Noh_String hashed = {0};
|
||||
Users users = {0};
|
||||
Noh_String users_file = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users_file, &users)) noh_return_defer(false);
|
||||
|
||||
// Find the user in the users list.
|
||||
bool found_user = false;
|
||||
User *user = NULL;
|
||||
for (size_t i = 0; i < users.count && !found_user; i++) {
|
||||
user = &users.elems[i];
|
||||
|
||||
if (noh_sv_eq(provided_username, user->username)) found_user = true;
|
||||
}
|
||||
|
||||
if (!found_user) noh_return_defer(false);
|
||||
|
||||
// Hash the provided password.
|
||||
hashed = hash_password(provided_username, provided_password);
|
||||
|
||||
// Check the hash.
|
||||
if (!noh_sv_eq(noh_sv_from_string(hashed), user->hash)) noh_return_defer(false);
|
||||
|
||||
*username = provided_username;
|
||||
|
||||
defer:
|
||||
noh_string_free(&hashed);
|
||||
noh_da_free(&users);
|
||||
noh_string_free(&users_file_path);
|
||||
noh_string_free(&users_file);
|
||||
noh_arena_reset(&temp);
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
static enum MHD_Result send_response(struct MHD_Connection *connection, int status, SV message, bool xml) {
|
||||
struct MHD_Response *response = MHD_create_response_from_buffer(
|
||||
message.count, (void*)message.elems, MHD_RESPMEM_MUST_COPY);
|
||||
@@ -47,6 +104,8 @@ static enum MHD_Result send_epub_file(struct MHD_Connection *connection, int fil
|
||||
return ret;
|
||||
}
|
||||
|
||||
#include "id_store.c"
|
||||
#include "library.c"
|
||||
#include "opds_handler.c"
|
||||
#include "get_handler.c"
|
||||
|
||||
@@ -65,20 +124,7 @@ static enum MHD_Result request_handler(
|
||||
noh_log(NOH_INFO, "Request: %s %s %s", version, method, _url);
|
||||
|
||||
SV username;
|
||||
#ifdef NO_AUTH
|
||||
username = sv("demo");
|
||||
#else
|
||||
struct MHD_BasicAuthInfo *auth = MHD_basic_auth_get_username_password3(connection);
|
||||
if (!auth) return send_response(connection, 401, sv(""), false);
|
||||
|
||||
username = (SV){ .elems = auth->username, .count = auth->username_len };
|
||||
SV password = { .elems = auth->password, .count = auth->password_len };
|
||||
|
||||
// TODO: Read username from user config.
|
||||
if (!noh_sv_eq(password, sv("jaja"))) return send_response(connection, 401, sv(""), false);
|
||||
#endif
|
||||
|
||||
noh_log(NOH_INFO, "Authenticated as "Nsv_Fmt, Nsv_Arg(username));
|
||||
if (!authorize_user(connection, &username, &settings)) return send_response(connection, 401, sv(""), false);
|
||||
|
||||
if (strcmp(method, "GET") != 0) return send_response(connection, 405, sv(""), false);
|
||||
|
||||
|
||||
129
src/userctl.c
129
src/userctl.c
@@ -1,15 +1,15 @@
|
||||
#include <stdio.h>
|
||||
#include <sha256.h>
|
||||
#include <dirent.h>
|
||||
#define NOH_IMPLEMENTATION
|
||||
#include "noh.h"
|
||||
|
||||
#define SV Noh_String_View
|
||||
#define sv noh_sv_from_cstr
|
||||
Noh_Arena temp;
|
||||
|
||||
#include "path.c"
|
||||
#include "users.c"
|
||||
|
||||
Noh_Arena temp;
|
||||
#define SV Noh_String_View
|
||||
#define sv noh_sv_from_cstr
|
||||
|
||||
static void show_usage(char *program_name, char *error) {
|
||||
noh_log(NOH_INFO, "Usage:");
|
||||
@@ -35,42 +35,6 @@ static void show_usage(char *program_name, char *error) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Noh_String hash_password(Noh_String_View username, Noh_String_View password) {
|
||||
// Build the sha input string.
|
||||
Noh_String input = {0};
|
||||
noh_string_append_sv(&input, username);
|
||||
noh_string_append_cstr(&input, "<><>");
|
||||
noh_string_append_sv(&input, password);
|
||||
|
||||
// Calculate the hash.
|
||||
SHA256_CTX ctx = {0};
|
||||
BYTE hash[32];
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, (BYTE*)input.elems, input.count);
|
||||
sha256_final(&ctx, hash);
|
||||
|
||||
// Build the result string.
|
||||
noh_arena_save(&temp);
|
||||
Noh_String result = {0};
|
||||
for (int i = 0; i < 32; i++) {
|
||||
noh_string_append_cstr(&result, noh_arena_sprintf(&temp, "%02x", hash[i]));
|
||||
}
|
||||
noh_arena_reset(&temp);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Noh_String_View username;
|
||||
Noh_String_View hash;
|
||||
} User;
|
||||
|
||||
typedef struct {
|
||||
User *elems;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} Users;
|
||||
|
||||
bool remove_directory(char *path) {
|
||||
noh_arena_save(&temp);
|
||||
bool result = true;
|
||||
@@ -103,55 +67,6 @@ bool remove_directory(char *path) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool read_users_file(char *file_path, Users *users) {
|
||||
Noh_String users_file = {0};
|
||||
|
||||
noh_log(NOH_INFO, "Reading users file: %s", file_path);
|
||||
if (!noh_file_exists(file_path)) return true;
|
||||
if (!noh_string_read_file(&users_file, file_path)) return false;
|
||||
Noh_String_View users_file_sv = noh_sv_from_string(users_file);
|
||||
|
||||
Noh_String_View line = {0};
|
||||
for (size_t i = 0; users_file_sv.count > 0; i++) {
|
||||
line = noh_sv_chop_line(&users_file_sv);
|
||||
|
||||
// Ignore empty lines.
|
||||
noh_sv_trim_space(&line);
|
||||
if (line.count == 0) continue;
|
||||
|
||||
Noh_String_View un = noh_sv_chop_by_delim(&line, ':');
|
||||
|
||||
if (line.count == 0) {
|
||||
noh_log(NOH_ERROR, "%s %zu: Line not in username:hash format.", file_path, i);
|
||||
noh_string_free(&users_file);
|
||||
return false;
|
||||
}
|
||||
|
||||
noh_da_append(users, ((User){ .username = un, .hash = line }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_users_file(char *file_path, Users *users, User *user_to_skip) {
|
||||
Noh_String users_file = {0};
|
||||
|
||||
for (size_t i = 0; i < users->count; i++) {
|
||||
User *u = &users->elems[i];
|
||||
if (u == user_to_skip) continue;
|
||||
|
||||
noh_string_append_sv(&users_file, u->username);
|
||||
noh_string_append_cstr(&users_file, ":");
|
||||
noh_string_append_sv(&users_file, u->hash);
|
||||
noh_string_append_cstr(&users_file, "\n");
|
||||
}
|
||||
|
||||
bool result = noh_string_write_file(&users_file, file_path);
|
||||
noh_string_free(&users_file);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// Prompts a user with a question and returns their response.
|
||||
bool prompt_user(char *question) {
|
||||
noh_arena_save(&temp);
|
||||
@@ -168,18 +83,22 @@ bool prompt_user(char *question) {
|
||||
}
|
||||
|
||||
bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_String_View password) {
|
||||
bool result = true;
|
||||
|
||||
// Read the file.
|
||||
Noh_String_View file_name = noh_sv_from_cstr("users.conf");
|
||||
Noh_String users_file_path = build_path(false, &library_path, &file_name);
|
||||
noh_string_append_null(&users_file_path);
|
||||
|
||||
// Build users map.
|
||||
Noh_String hashed = {0};
|
||||
Users users = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users)) return false;
|
||||
bool result = true;
|
||||
Noh_String users_file = {0};
|
||||
Noh_String user_folder_path = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users_file, &users)) noh_return_defer(false);
|
||||
|
||||
// Hash the password.
|
||||
Noh_String hashed = hash_password(username, password);
|
||||
hashed = hash_password(username, password);
|
||||
|
||||
// Find the user in the map of users.
|
||||
for (size_t i = 0; i < users.count; i++) {
|
||||
@@ -197,7 +116,7 @@ bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_S
|
||||
if (!write_users_file(users_file_path.elems, &users, NULL)) noh_return_defer(false);
|
||||
|
||||
// Create user folder if needed.
|
||||
Noh_String user_folder_path = build_path(false, &library_path, &username);
|
||||
user_folder_path = build_path(false, &library_path, &username);
|
||||
noh_string_append_null(&user_folder_path);
|
||||
if (!noh_mkdir_if_needed(user_folder_path.elems)) noh_return_defer(false);
|
||||
|
||||
@@ -210,25 +129,32 @@ bool register_user(Noh_String_View library_path, Noh_String_View username, Noh_S
|
||||
if (!write_users_file(users_file_path.elems, &users, NULL)) noh_return_defer(false);
|
||||
|
||||
// Create user folder if needed.
|
||||
Noh_String user_folder_path = build_path(false, &library_path, &username);
|
||||
user_folder_path = build_path(false, &library_path, &username);
|
||||
noh_string_append_null(&user_folder_path);
|
||||
if (!noh_mkdir_if_needed(user_folder_path.elems)) noh_return_defer(false);
|
||||
|
||||
defer:
|
||||
noh_string_free(&users_file_path);
|
||||
noh_string_free(&user_folder_path);
|
||||
noh_da_free(&users);
|
||||
noh_string_free(&users_file);
|
||||
noh_string_free(&hashed);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool delete_user(Noh_String_View library_path, Noh_String_View username) {
|
||||
bool result = true;
|
||||
|
||||
// Read the file.
|
||||
Noh_String_View file_name = noh_sv_from_cstr("users.conf");
|
||||
Noh_String users_file_path = build_path(false, &library_path, &file_name);
|
||||
noh_string_append_null(&users_file_path);
|
||||
|
||||
// Build users map.
|
||||
Noh_String user_folder_path = {0};
|
||||
Users users = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users)) return false;
|
||||
bool result = true;
|
||||
Noh_String users_file = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users_file, &users)) noh_return_defer(false);
|
||||
|
||||
// Check if the user exists.
|
||||
User *user_to_delete = NULL;
|
||||
@@ -256,7 +182,7 @@ bool delete_user(Noh_String_View library_path, Noh_String_View username) {
|
||||
if (!write_users_file(users_file_path.elems, &users, user_to_delete)) noh_return_defer(false);
|
||||
|
||||
// Prompt to delete the user's library folder.
|
||||
Noh_String user_folder_path = build_path(false, &library_path, &username);
|
||||
user_folder_path = build_path(false, &library_path, &username);
|
||||
noh_string_append_null(&user_folder_path);
|
||||
if (!noh_file_exists(user_folder_path.elems)) noh_return_defer(true);
|
||||
|
||||
@@ -266,7 +192,10 @@ bool delete_user(Noh_String_View library_path, Noh_String_View username) {
|
||||
}
|
||||
|
||||
defer:
|
||||
noh_string_free(&user_folder_path);
|
||||
noh_string_free(&users_file_path);
|
||||
noh_da_free(&users);
|
||||
noh_string_free(&users_file);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -278,11 +207,13 @@ bool list_users(Noh_String_View library_path) {
|
||||
|
||||
// Build users map.
|
||||
Users users = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users)) return false;
|
||||
Noh_String users_file = {0};
|
||||
if (!read_users_file(users_file_path.elems, &users_file, &users)) return false;
|
||||
|
||||
if (users.count == 0) {
|
||||
noh_log(NOH_INFO, "No users registered.");
|
||||
noh_da_free(&users);
|
||||
noh_string_free(&users_file);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -294,7 +225,9 @@ bool list_users(Noh_String_View library_path) {
|
||||
}
|
||||
noh_arena_reset(&temp);
|
||||
|
||||
noh_string_free(&users_file_path);
|
||||
noh_da_free(&users);
|
||||
noh_string_free(&users_file);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
87
src/users.c
Normal file
87
src/users.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <sha256.h>
|
||||
|
||||
typedef struct {
|
||||
Noh_String_View username;
|
||||
Noh_String_View hash;
|
||||
} User;
|
||||
|
||||
typedef struct {
|
||||
User *elems;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} Users;
|
||||
|
||||
Noh_String hash_password(Noh_String_View username, Noh_String_View password) {
|
||||
// Build the sha input string.
|
||||
Noh_String input = {0};
|
||||
noh_string_append_sv(&input, username);
|
||||
noh_string_append_cstr(&input, "<><>");
|
||||
noh_string_append_sv(&input, password);
|
||||
|
||||
// Calculate the hash.
|
||||
SHA256_CTX ctx = {0};
|
||||
BYTE hash[32];
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, (BYTE*)input.elems, input.count);
|
||||
sha256_final(&ctx, hash);
|
||||
|
||||
// Build the result string.
|
||||
noh_arena_save(&temp);
|
||||
Noh_String result = {0};
|
||||
for (int i = 0; i < 32; i++) {
|
||||
noh_string_append_cstr(&result, noh_arena_sprintf(&temp, "%02x", hash[i]));
|
||||
}
|
||||
noh_arena_reset(&temp);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reads the users file into users_file. Free this string when the data is no longer needed.
|
||||
// Note that the string needs to be initialized and empty before calling this function.
|
||||
// The users struct will contain string views referencing the data in this string.
|
||||
bool read_users_file(char *file_path, Noh_String *users_file, Users *users) {
|
||||
noh_log(NOH_INFO, "Reading users file: %s", file_path);
|
||||
if (!noh_file_exists(file_path)) return true;
|
||||
if (!noh_string_read_file(users_file, file_path)) return false;
|
||||
Noh_String_View users_file_sv = noh_sv_from_string(*users_file);
|
||||
|
||||
Noh_String_View line = {0};
|
||||
for (size_t i = 0; users_file_sv.count > 0; i++) {
|
||||
line = noh_sv_chop_line(&users_file_sv);
|
||||
|
||||
// Ignore empty lines.
|
||||
noh_sv_trim_space(&line);
|
||||
if (line.count == 0) continue;
|
||||
|
||||
Noh_String_View un = noh_sv_chop_by_delim(&line, ':');
|
||||
|
||||
if (line.count == 0) {
|
||||
noh_log(NOH_ERROR, "%s %zu: Line not in username:hash format.", file_path, i);
|
||||
noh_string_free(users_file);
|
||||
return false;
|
||||
}
|
||||
|
||||
noh_da_append(users, ((User){ .username = un, .hash = line }));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_users_file(char *file_path, Users *users, User *user_to_skip) {
|
||||
Noh_String users_file = {0};
|
||||
|
||||
for (size_t i = 0; i < users->count; i++) {
|
||||
User *u = &users->elems[i];
|
||||
if (u == user_to_skip) continue;
|
||||
|
||||
noh_string_append_sv(&users_file, u->username);
|
||||
noh_string_append_cstr(&users_file, ":");
|
||||
noh_string_append_sv(&users_file, u->hash);
|
||||
noh_string_append_cstr(&users_file, "\n");
|
||||
}
|
||||
|
||||
bool result = noh_string_write_file(&users_file, file_path);
|
||||
noh_string_free(&users_file);
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user