diff --git a/backend/src/controllers.h b/backend/src/controllers.h index 6d966d7..ec97448 100644 --- a/backend/src/controllers.h +++ b/backend/src/controllers.h @@ -21,6 +21,10 @@ void route_get_products_all(HttpCtx* ctx); void route_post_user_register(HttpCtx* ctx); void route_post_auth_login(HttpCtx* ctx); +void route_post_auth_logout(HttpCtx* ctx); + +const Session* header_session(HttpCtx* ctx); +const Session* middleware_session(HttpCtx* ctx); #define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \ { \ @@ -47,8 +51,7 @@ void route_post_auth_login(HttpCtx* ctx); #define RESPOND_SERVER_ERROR(HTTP_CTX) \ RESPOND_JSON(HTTP_CTX, 500, "{\"ok\":false,\"msg\":\"server error\"}") -__attribute__((unused)) -static inline void ___include_user(void) +__attribute__((unused)) static inline void ___include_user(void) { RESPOND((HttpCtx*)0, 200, "text/html", "") } diff --git a/backend/src/controllers/auth.c b/backend/src/controllers/auth.c index 4fb2a34..77ddfb2 100644 --- a/backend/src/controllers/auth.c +++ b/backend/src/controllers/auth.c @@ -41,13 +41,46 @@ void route_post_auth_login(HttpCtx* ctx) goto l2_return; } - session_vec_remove_user_id(&cx->sessions, user.id); - char* token = str_random(64); - session_vec_add(&cx->sessions, user.id, token); + sessions_remove(&cx->sessions, user.id); + Session* session = sessions_add(&cx->sessions, user.id); - RESPOND_JSON(ctx, 200, "{\"ok\":true,\"token\":\"%s\"}", token); + RESPOND_JSON(ctx, 200, "{\"ok\":true,\"token\":\"%s\"}", session->token); l2_return: user_destroy(&user); l0_return: auth_login_req_destroy(&req); } + +void route_post_auth_logout(HttpCtx* ctx) +{ + Cx* cx = http_ctx_user_ctx(ctx); + const Session* session = header_session(ctx); + if (!session) { + RESPOND_JSON(ctx, 200, "{\"ok\":true}"); + return; + } + sessions_remove(&cx->sessions, session->user_id); + RESPOND_JSON(ctx, 200, "{\"ok\":true}"); +} + +const Session* header_session(HttpCtx* ctx) +{ + Cx* cx = http_ctx_user_ctx(ctx); + if (!http_ctx_req_headers_has(ctx, "Session-Token")) { + return NULL; + } + const char* token = http_ctx_req_headers_get(ctx, "Session-Token"); + // session expiration should be handled here + return sessions_find(&cx->sessions, token); +} + +// Returns NULL AND responds if no valid session is found. +const Session* middleware_session(HttpCtx* ctx) +{ + const Session* session = header_session(ctx); + if (!session) { + RESPOND_JSON(ctx, 200, "{\"ok\":false,\"msg\":\"unauthorized\"}"); + return NULL; + } + return session; +} diff --git a/backend/src/http_server.c b/backend/src/http_server.c index 51cdd27..1fb6fdc 100644 --- a/backend/src/http_server.c +++ b/backend/src/http_server.c @@ -1,6 +1,7 @@ #include "http_server.h" #include "http_server_internal.h" #include "str_util.h" +#include #include #include #include @@ -358,10 +359,19 @@ static inline int parse_header( return -1; } + if (method_str.len >= 8) { + fprintf(stderr, "error: malformed http method\n"); + return -1; + } + char normalized_method[8] = ""; + for (size_t i = 0; i < method_str.len; ++i) { + normalized_method[i] = (char)toupper(method_str.ptr[i]); + } + Method method; - if (strncmp(method_str.ptr, "GET", method_str.len) == 0) { + if (strncmp(normalized_method, "GET", method_str.len) == 0) { method = Method_GET; - } else if (strncmp(method_str.ptr, "POST", method_str.len) == 0) { + } else if (strncmp(normalized_method, "POST", method_str.len) == 0) { method = Method_POST; } else { fprintf(stderr, "error: unrecognized http method '%.*s'\n", diff --git a/backend/src/main.c b/backend/src/main.c index ede6a88..a0b5bda 100644 --- a/backend/src/main.c +++ b/backend/src/main.c @@ -46,6 +46,7 @@ int main(void) http_server_post(server, "/api/users/register", route_post_user_register); http_server_post(server, "/api/auth/login", route_post_auth_login); + http_server_post(server, "/api/auth/logout", route_post_auth_logout); http_server_get(server, "/", route_get_index); http_server_post(server, "/set_number", route_post_set_number); diff --git a/backend/src/session.c b/backend/src/session.c index 0897011..e7820ec 100644 --- a/backend/src/session.c +++ b/backend/src/session.c @@ -1,28 +1,54 @@ #include "session.h" +#include "str_util.h" #include +void session_construct(Session* session, int64_t user_id) +{ + char* token = str_random(64); + size_t token_hash = str_fast_hash(token); + *session = (Session) { user_id, token, token_hash }; +} + void session_destroy(Session* session) { free(session->token); + *session = (Session) { + .user_id = 0, + .token = NULL, + .token_hash = 0, + }; } -void session_vec_remove_user_id(SessionVec* vec, int64_t user_id) +void sessions_remove(SessionVec* vec, int64_t user_id) { for (size_t i = 0; i < vec->size; ++i) { if (vec->data[i].user_id == user_id) { session_destroy(&vec->data[i]); - vec->data[i] = (Session) { 0, NULL }; } } } -void session_vec_add(SessionVec* vec, int64_t user_id, char* token) +Session* sessions_add(SessionVec* vec, int64_t user_id) { for (size_t i = 0; i < vec->size; ++i) { if (vec->data[i].user_id == 0) { - vec->data[i] = (Session) { user_id, token }; - return; + session_construct(&vec->data[i], user_id); + return &vec->data[i]; } } - session_vec_push(vec, (Session) { user_id, token }); + Session session; + session_construct(&session, user_id); + session_vec_push(vec, session); + return &vec->data[vec->size - 1]; +} + +const Session* sessions_find(SessionVec* vec, const char* token) +{ + size_t token_hash = str_fast_hash(token); + for (size_t i = 0; i < vec->size; ++i) { + if (vec->data[i].token_hash == token_hash) { + return &vec->data[i]; + } + } + return NULL; } diff --git a/backend/src/session.h b/backend/src/session.h index 0c98d4b..cdd359e 100644 --- a/backend/src/session.h +++ b/backend/src/session.h @@ -6,11 +6,14 @@ typedef struct { int64_t user_id; char* token; + size_t token_hash; } Session; +void session_construct(Session* session, int64_t user_id); void session_destroy(Session* session); DEFINE_VEC(Session, SessionVec, session_vec, 16) -void session_vec_remove_user_id(SessionVec* vec, int64_t user_id); -void session_vec_add(SessionVec* vec, int64_t user_id, char* token); +void sessions_remove(SessionVec* vec, int64_t user_id); +Session* sessions_add(SessionVec* vec, int64_t user_id); +const Session* sessions_find(SessionVec* vec, const char* token); diff --git a/backend/src/str_util.c b/backend/src/str_util.c index b82eeb1..1c2c9b7 100644 --- a/backend/src/str_util.c +++ b/backend/src/str_util.c @@ -164,6 +164,85 @@ bool str_hash_equal(const char* hash, const char* input) return hashdata_is_equal(data, input); } +static inline uint64_t chibihash64__load32le(const uint8_t* p) +{ + return (uint64_t)p[0] << 0 | (uint64_t)p[1] << 8 | (uint64_t)p[2] << 16 + | (uint64_t)p[3] << 24; +} +static inline uint64_t chibihash64__load64le(const uint8_t* p) +{ + return chibihash64__load32le(p) | (chibihash64__load32le(p + 4) << 32); +} +static inline uint64_t chibihash64__rotl(uint64_t x, int n) +{ + return (x << n) | (x >> (-n & 63)); +} + +static inline uint64_t chibihash64( + const void* keyIn, ptrdiff_t len, uint64_t seed) +{ + const uint8_t* p = (const uint8_t*)keyIn; + ptrdiff_t l = len; + + const uint64_t K = UINT64_C(0x2B7E151628AED2A7); // digits of e + uint64_t seed2 + = chibihash64__rotl(seed - K, 15) + chibihash64__rotl(seed - K, 47); + uint64_t h[4] = { seed, seed + K, seed2, seed2 + (K * K ^ K) }; + + // depending on your system unrolling might (or might not) make things + // a tad bit faster on large strings. on my system, it actually makes + // things slower. + // generally speaking, the cost of bigger code size is usually not + // worth the trade-off since larger code-size will hinder inlinability + // but depending on your needs, you may want to uncomment the pragma + // below to unroll the loop. + // #pragma GCC unroll 2 + for (; l >= 32; l -= 32) { + for (int i = 0; i < 4; ++i, p += 8) { + uint64_t stripe = chibihash64__load64le(p); + h[i] = (stripe + h[i]) * K; + h[(i + 1) & 3] += chibihash64__rotl(stripe, 27); + } + } + + for (; l >= 8; l -= 8, p += 8) { + h[0] ^= chibihash64__load32le(p + 0); + h[0] *= K; + h[1] ^= chibihash64__load32le(p + 4); + h[1] *= K; + } + + if (l >= 4) { + h[2] ^= chibihash64__load32le(p); + h[3] ^= chibihash64__load32le(p + l - 4); + } else if (l > 0) { + h[2] ^= p[0]; + h[3] ^= p[l / 2] | ((uint64_t)p[l - 1] << 8); + } + + h[0] += chibihash64__rotl(h[2] * K, 31) ^ (h[2] >> 31); + h[1] += chibihash64__rotl(h[3] * K, 31) ^ (h[3] >> 31); + h[0] *= K; + h[0] ^= h[0] >> 31; + h[1] += h[0]; + + uint64_t x = (uint64_t)len * K; + x ^= chibihash64__rotl(x, 29); + x += seed; + x ^= h[1]; + + x ^= chibihash64__rotl(x, 15) ^ chibihash64__rotl(x, 42); + x *= K; + x ^= chibihash64__rotl(x, 13) ^ chibihash64__rotl(x, 31); + + return x; +} + +uint64_t str_fast_hash(const char* input) +{ + return chibihash64(input, sizeof(char*), 0x80085); +} + char* str_random(size_t length) { char* string = calloc(length + 1, sizeof(char)); diff --git a/backend/src/str_util.h b/backend/src/str_util.h index 5552ad3..90ed380 100644 --- a/backend/src/str_util.h +++ b/backend/src/str_util.h @@ -38,6 +38,8 @@ DEFINE_VEC(char*, RawStrVec, rawstr_vec, 8) char* str_hash(const char* input); bool str_hash_equal(const char* hash, const char* input); +uint64_t str_fast_hash(const char* input); + char* str_random(size_t length); void str_util_test(void);