login endpoint for backend

This commit is contained in:
Mikkel Troels Kongsted 2025-03-10 12:00:22 +01:00
parent caf97efc0c
commit b5f8c383c5
12 changed files with 170 additions and 25 deletions

View File

@ -35,6 +35,12 @@ else
OPTIMIZATION += -Og
endif
ifeq ($(RUN_TESTS),1)
C_FLAGS += -DRUN_TESTS
endif
HEADERS = $(shell find src/ -name *.h)
C_FILES = $(shell find src/ -name *.c)
O_FILES = $(patsubst src/%.c,build/%.o,$(C_FILES))

View File

@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS cart_items (
INSERT OR REPLACE INTO users VALUES(1,'User','test@email.com','d980840fcb82970ab86656feebdccdd288be0e9b05f14e712b59529a2868fee3d980840fcb82970ab86656feebdccdd288be0e9b05f14e712b59529a2868fee3',10000);
INSERT OR REPLACE INTO users VALUES(1,'User','test@email.com','08ce0220f6d63d85c3ac313e308f4fca35ecfb850baa8ddb924cfab98137b6b18b4a8e027067cb98802757df1337246a0f3aa25c44c2b788517a871086419dcf',10000);
INSERT OR REPLACE INTO products VALUES(1,'Letmælk',1195,'Mælk fra ko',NULL,NULL);
INSERT OR REPLACE INTO products VALUES(2,'Smør',2000,'Smør fra mejeri',NULL,NULL);

View File

@ -2,11 +2,13 @@
#include "db.h"
#include "http_server.h"
#include "session.h"
#include <stdio.h>
#include <string.h>
typedef struct {
int number;
SessionVec sessions;
Db* db;
} Cx;
@ -18,6 +20,8 @@ void route_get_products_all(HttpCtx* ctx);
void route_post_user_register(HttpCtx* ctx);
void route_post_auth_login(HttpCtx* ctx);
#define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \
{ \
HttpCtx* _ctx = (HTTP_CTX); \
@ -42,3 +46,9 @@ void route_post_user_register(HttpCtx* ctx);
RESPOND_JSON(HTTP_CTX, 400, "{\"ok\":false,\"msg\":\"%s\"}", (MSG))
#define RESPOND_SERVER_ERROR(HTTP_CTX) \
RESPOND_JSON(HTTP_CTX, 500, "{\"ok\":false,\"msg\":\"server error\"}")
__attribute__((unused))
static inline void ___include_user(void)
{
RESPOND((HttpCtx*)0, 200, "text/html", "")
}

View File

@ -2,9 +2,53 @@
#include "../http_server.h"
#include "../models_json.h"
#include "../str_util.h"
#include <string.h>
void route_post_auth_login(HttpCtx* ctx)
{
Cx* cx = http_ctx_user_ctx(ctx);
const char* body_str = http_ctx_req_body(ctx);
JsonValue* body_json = json_parse(body_str, strlen(body_str));
AuthLoginReq req;
int parse_res = auth_login_req_from_json(&req, body_json);
json_free(body_json);
if (parse_res != 0) {
RESPOND_BAD_REQUEST(ctx, "bad request");
goto l0_return;
}
if (strlen(req.email) == 0
|| strlen(req.password) > MAX_HASH_INPUT_LEN) {
RESPOND_BAD_REQUEST(ctx, "bad request");
goto l0_return;
}
User user;
DbRes db_res = db_user_from_email(cx->db, &user, req.email);
if (db_res == DbRes_NotFound) {
RESPOND_BAD_REQUEST(ctx, "user with email not found");
goto l0_return;
}
else if (db_res == DbRes_Error) {
RESPOND_SERVER_ERROR(ctx);
goto l0_return;
}
if (!str_hash_equal(user.password_hash, req.password)) {
RESPOND_BAD_REQUEST(ctx, "wrong password");
goto l2_return;
}
session_vec_push(&cx->sessions, (Session) {.user_id = user.id});
RESPOND_JSON(ctx, 200, "{\"ok\":true}");
l2_return:
user_destroy(&user);
l0_return:
auth_login_req_destroy(&req);
}

View File

@ -23,7 +23,7 @@ void route_post_user_register(HttpCtx* ctx)
if (strlen(req.name) == 0 || strlen(req.email) == 0
|| strlen(req.password) > MAX_HASH_INPUT_LEN) {
RESPOND_BAD_REQUEST(ctx, "bad request");
users_register_req_free(&req);
users_register_req_destroy(&req);
return;
}
@ -32,13 +32,13 @@ void route_post_user_register(HttpCtx* ctx)
if (db_users_with_email(cx->db, &ids, req.email) != DbRes_Ok) {
RESPOND_SERVER_ERROR(ctx);
ids_destroy(&ids);
users_register_req_free(&req);
users_register_req_destroy(&req);
return;
}
if (ids.size > 0) {
RESPOND_BAD_REQUEST(ctx, "email in use");
ids_destroy(&ids);
users_register_req_free(&req);
users_register_req_destroy(&req);
return;
}
ids_destroy(&ids);
@ -57,10 +57,10 @@ void route_post_user_register(HttpCtx* ctx)
RESPOND_SERVER_ERROR(ctx);
free(password_hash);
users_register_req_free(&req);
users_register_req_destroy(&req);
return;
}
free(password_hash);
users_register_req_free(&req);
users_register_req_destroy(&req);
RESPOND_JSON(ctx, 200, "{\"ok\":true}");
}

View File

@ -16,9 +16,16 @@ typedef struct Db Db;
/// `user.id` field is ignored.
DbRes db_user_insert(Db* db, const User* user);
/// `user` field is an out parameter.
DbRes db_user_from_id(Db* db, User* user, int64_t id);
/// Expects `ids` to be constructed.
DbRes db_users_with_email(Db* db, Ids* ids, const char* email);
/// `user` is an out parameter.
DbRes db_user_from_email(Db* db, User* user, const char* email);
/// Expects `vec` to be constructed.
DbRes db_product_all(Db* db, ProductVec* vec);

View File

@ -110,7 +110,7 @@ DbRes db_user_from_id(Db* db, User* user, int64_t id)
sqlite3_stmt* stmt;
sqlite3_prepare_v2(connection,
"SELECT id, name, email, password_hash, balance_dkk_cent"
"FROM users WHERE id = ?",
" FROM users WHERE id = ?",
-1, &stmt, NULL);
sqlite3_bind_int64(stmt, 1, id);
@ -171,6 +171,53 @@ l0_return:
return res;
}
DbRes db_user_from_email(Db* db, User* user, const char* email)
{
static_assert(sizeof(User) == 40, "model has changed");
sqlite3* connection;
CONNECT;
DbRes res;
sqlite3_stmt* stmt;
int prepare_res = sqlite3_prepare_v2(connection,
"SELECT id, name, email, password_hash, balance_dkk_cent"
" FROM users WHERE email = ?",
-1, &stmt, NULL);
if (prepare_res != SQLITE_OK) {
fprintf(stderr, "error: %s\n at %s:%d\n", sqlite3_errmsg(connection), __func__, __LINE__);
res = DbRes_Error;
goto l0_return;
}
sqlite3_bind_text(stmt, 1, email, -1, NULL);
int step_res = sqlite3_step(stmt);
if (step_res == SQLITE_DONE) {
res = DbRes_NotFound;
goto l0_return;
} else if (step_res != SQLITE_ROW) {
printf("step_res = %d, email = '%s'\n", step_res, email);
fprintf(stderr, "error: %s\n at %s:%d\n", sqlite3_errmsg(connection), __func__, __LINE__);
res = DbRes_Error;
goto l0_return;
}
*user = (User) {
.id = GET_INT(0),
.name = GET_STR(1),
.email = GET_STR(2),
.password_hash = GET_STR(3),
.balance_dkk_cent = GET_INT(4),
};
res = DbRes_Ok;
l0_return:
if (stmt)
sqlite3_finalize(stmt);
DISCONNECT;
return res;
}
DbRes db_product_all(Db* db, ProductVec* vec)
{
sqlite3* connection;

View File

@ -4,20 +4,30 @@
#include "json.h"
#include "models.h"
#include "models_json.h"
#include "session.h"
#include "str_util.h"
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>
void test(void);
HttpServer* server;
int main(void)
{
#ifdef RUN_TESTS
test();
#endif
Db* db = db_sqlite_new();
Cx cx = {
.number = 1,
.db = db,
};
session_vec_construct(&cx.sessions);
server = http_server_new((HttpServerOpts) {
.port = 8080,
@ -32,6 +42,7 @@ int main(void)
http_server_get(server, "/api/products/all", route_get_products_all);
http_server_post(server, "/api/users/register", route_post_user_register);
http_server_post(server, "/api/auth/login", route_post_auth_login);
http_server_get(server, "/", route_get_index);
http_server_post(server, "/set_number", route_post_set_number);
@ -44,3 +55,10 @@ int main(void)
http_server_free(server);
db_sqlite_free(db);
}
void test(void)
{
str_util_test();
printf("ALL TESTS PASSED 💅\n");
exit(0);
}

View File

@ -6,7 +6,7 @@
#include <stdlib.h>
#include <string.h>
void user_free(User* m)
void user_destroy(User* m)
{
static_assert(sizeof(User) == 40, "model has changed");
@ -15,14 +15,14 @@ void user_free(User* m)
free(m->password_hash);
}
void coord_free(Coord* m)
void coord_destroy(Coord* m)
{
static_assert(sizeof(Coord) == 24, "model has changed");
(void)m;
}
void product_free(Product* m)
void product_destroy(Product* m)
{
static_assert(sizeof(Product) == 40, "model has changed");
@ -30,28 +30,28 @@ void product_free(Product* m)
free(m->barcode);
}
void product_price_free(ProductPrice* m)
void product_price_destroy(ProductPrice* m)
{
static_assert(sizeof(ProductPrice) == 24, "model has changed");
(void)m;
}
void cart_free(Cart* m)
void cart_destroy(Cart* m)
{
static_assert(sizeof(Cart) == 16, "model has changed");
(void)m;
}
void cart_item_free(CartItem* m)
void cart_item_destroy(CartItem* m)
{
static_assert(sizeof(CartItem) == 24, "model has changed");
(void)m;
}
void users_register_req_free(UsersRegisterReq* model)
void users_register_req_destroy(UsersRegisterReq* model)
{
static_assert(sizeof(UsersRegisterReq) == 24, "model has changed");
@ -60,7 +60,7 @@ void users_register_req_free(UsersRegisterReq* model)
free(model->password);
}
void auth_login_req_free(AuthLoginReq* model)
void auth_login_req_destroy(AuthLoginReq* model)
{
static_assert(sizeof(AuthLoginReq) == 16, "model has changed");

View File

@ -41,12 +41,12 @@ typedef struct {
int64_t amount;
} CartItem;
void user_free(User* model);
void coord_free(Coord* model);
void product_free(Product* model);
void product_price_free(ProductPrice* model);
void cart_free(Cart* model);
void cart_item_free(CartItem* model);
void user_destroy(User* model);
void coord_destroy(Coord* model);
void product_destroy(Product* model);
void product_price_destroy(ProductPrice* model);
void cart_destroy(Cart* model);
void cart_item_destroy(CartItem* model);
//
@ -61,5 +61,5 @@ typedef struct {
char* password;
} AuthLoginReq;
void users_register_req_free(UsersRegisterReq* model);
void auth_login_req_free(AuthLoginReq* model);
void users_register_req_destroy(UsersRegisterReq* model);
void auth_login_req_destroy(AuthLoginReq* model);

View File

@ -1,4 +1,5 @@
#include "str_util.h"
#include "panic.h"
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <stddef.h>
@ -124,7 +125,7 @@ static inline char* hashdata_to_string(HashData hash)
}
for (size_t i = 0; i < STR_HASH_HASH_SIZE; ++i) {
char bytestr[3] = { 0 };
snprintf(bytestr, 3, "%02x", hash.salt[i]);
snprintf(bytestr, 3, "%02x", hash.hash[i]);
result[(STR_HASH_SALT_SIZE + i) * 2] = bytestr[0];
result[(STR_HASH_SALT_SIZE + i) * 2 + 1] = bytestr[1];
}
@ -144,7 +145,6 @@ static inline HashData hashdata_from_hash_string(const char* str)
}
HashData hash;
// memcpy((uint8_t*)&hash, result, sizeof(result));
for (size_t i = 0; i < 32; ++i) {
hash.salt[i] = result[i];
hash.hash[i] = result[32 + i];
@ -163,3 +163,14 @@ bool str_hash_equal(const char* hash, const char* input)
HashData data = hashdata_from_hash_string(hash);
return hashdata_is_equal(data, input);
}
void str_util_test(void) {
char* hash = str_hash("1234");
if (!str_hash_equal(hash, "1234")) {
PANIC("hash should be equal");
}
if (str_hash_equal(hash, "4321")) {
PANIC("hash should not be equal");
}
free(hash);
}

View File

@ -37,3 +37,5 @@ DEFINE_VEC(char*, RawStrVec, rawstr_vec, 8)
char* str_hash(const char* input);
bool str_hash_equal(const char* hash, const char* input);
void str_util_test(void);