blog

The tiny blog platform powering https://blog.kore.io
Commits | Files | Refs | README | git clone https://git.kore.io/kore-blog.git

commit fc8c4cf95034e305b65ef22f59b8b20631015c6c
parent 92ae036fd259084ff14241f9869318ffbebd9e68
Author: Joris Vink <joris@coders.se>
Date:   Tue, 24 Apr 2018 20:12:25 +0200

add login to view drafts.

Users are stored under users.conf and are in the form of:
	username:argon2hash

Diffstat:
assets/login.html | 28++++++++++++++++++++++++++++
assets/post_end.html | 1-
conf/blog.conf | 21+++++++++++++++++++--
conf/build.conf | 3+++
src/blog.c | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 288 insertions(+), 3 deletions(-)

diff --git a/assets/login.html b/assets/login.html @@ -0,0 +1,28 @@ +<!DOCTYPE> + +<html> +<head> +<title>Login</title> +<link rel="stylesheet" type="text/css" href="/style.css"> +</head> + +<body> + +<div style="width: 100%; text-align: center"> + <a href="/"><img class="logo" src="/logo.png"></a> +</div> + +<div style="margin-top: 45px"></div> + +<div style="text-align: center"> + <form method="POST" action="/login/"> + <input type="text" name="user" placeholder="Username"> + <input type="password" name="passphrase" placeholder="Password"> + <br> + <br> + <input type="submit" value="login"> + </form> +</div> + +</body> +</html> diff --git a/assets/post_end.html b/assets/post_end.html @@ -1,4 +1,3 @@ - <div style="margin-top: 45px"></div> </body> diff --git a/conf/blog.conf b/conf/blog.conf @@ -6,22 +6,39 @@ workers 4 runas nobody chroot /var/chroot/kore-blog +validator v_any regex ^.*$ validator v_referer function referer +validator v_session function auth_session +validator v_user function auth_user_exists authentication referer_log { authentication_type request authentication_validator v_referer } +authentication author { + authentication_type cookie + authentication_validator v_session + authentication_value blog_token + authentication_uri /login/ +} + domain * { static / post_list referer_log dynamic ^/posts/[a-z1-9\-]+$ post_render referer_log - static /drafts/ draft_list - dynamic ^/drafts/[a-z1-9\-]+$ draft_render + static /drafts/ draft_list author + dynamic ^/drafts/[a-z1-9\-]+$ draft_render author static /logo.png asset_serve_kore_png static /style.css asset_serve_style_css + static /login/ auth_login referer_log + + params post /login/ { + validate user v_user + validate passphrase v_any + } + dynamic ^.*$ redirect } diff --git a/conf/build.conf b/conf/build.conf @@ -16,6 +16,9 @@ cflags=-Wpointer-arith -Wcast-qual -Wsign-compare mime_add=png:image/png mime_add=css:text/css +mime_add=html:text/html; charset=utf-8 + +ldflags=-lsodium dev { cflags=-g diff --git a/src/blog.c b/src/blog.c @@ -25,6 +25,8 @@ #include <kore/kore.h> #include <kore/http.h> +#include <sodium.h> + #include <ctype.h> #include <fts.h> #include <fcntl.h> @@ -34,8 +36,14 @@ #include "assets.h" +#define BLOG_SESSION_LEN 32 + +#define MSG_SESSION_ADD 100 +#define MSG_SESSION_DEL 200 + #define BLOG_DIR "blogs" #define BLOG_VER "kore-blog v0.1" +#define BLOG_USER_CONF "users.conf" #define POST_FLAG_DRAFT 0x0001 @@ -44,6 +52,19 @@ struct cache { struct kore_buf buf; }; +struct session { + uint32_t uid; + char data[(BLOG_SESSION_LEN * 2) + 1]; +}; + +struct user { + u_int32_t uid; + char *name; + struct session session; + char *passphrase; + TAILQ_ENTRY(user) list; +}; + struct post { int flags; size_t coff; @@ -56,6 +77,7 @@ struct post { TAILQ_ENTRY(post) list; }; +void user_reload(void); void index_rebuild(void); void signal_handler(int); void tick(void *, u_int64_t); @@ -69,6 +91,12 @@ void post_remove(struct post *); int post_send(struct http_request *, const char *, int); void cache_ref_drop(struct cache **); +int auth_login(struct http_request *); +int auth_user_exists(struct http_request *, char *); +void auth_session_add(struct kore_msg *, const void *); +void auth_session_del(struct kore_msg *, const void *); +int auth_session(struct http_request *, const char *); + int redirect(struct http_request *); int post_list(struct http_request *); int post_render(struct http_request *); @@ -78,6 +106,7 @@ int referer(struct http_request *, const void *); int list_posts(struct http_request *, const char *, struct cache **, int); static TAILQ_HEAD(, post) posts; +static TAILQ_HEAD(, user) users; static volatile sig_atomic_t blog_sig = -1; static struct cache *live_index = NULL; @@ -95,6 +124,7 @@ tick(void *unused, u_int64_t now) if (blog_sig == SIGHUP) { blog_sig = -1; index_rebuild(); + user_reload(); } } @@ -114,7 +144,13 @@ kore_worker_configure(void) (void)kore_timer_add(tick, 1000, NULL, 0); TAILQ_INIT(&posts); + TAILQ_INIT(&users); + index_rebuild(); + user_reload(); + + kore_msg_register(MSG_SESSION_ADD, auth_session_add); + kore_msg_register(MSG_SESSION_DEL, auth_session_del); } struct cache * @@ -170,6 +206,64 @@ fts_compare(const FTSENT **a, const FTSENT **b) } void +user_reload(void) +{ + FILE *fp; + u_int32_t uids; + struct user *user; + int lineno; + char *line, *pwd, buf[256]; + + while (!TAILQ_EMPTY(&users)) { + user = TAILQ_FIRST(&users); + TAILQ_REMOVE(&users, user, list); + kore_free(user->passphrase); + kore_free(user->name); + kore_free(user); + } + + TAILQ_INIT(&users); + + if ((fp = fopen(BLOG_USER_CONF, "r")) == NULL) { + if (errno != ENOENT) { + kore_log(LOG_INFO, + "fopen(%s): %s", BLOG_USER_CONF, errno_s); + } + return; + } + + uids = 1; + lineno = 0; + + while ((line = kore_read_line(fp, buf, sizeof(buf))) != NULL) { + lineno++; + + if (*line == '\0') + continue; + + if ((pwd = strchr(line, ':')) == NULL) { + kore_log(LOG_INFO, "malformed user @ %d", lineno); + continue; + } + + *(pwd)++ = '\0'; + + if (*line == '\0' || *pwd == '\0') { + kore_log(LOG_INFO, "malformed user @ %d", lineno); + continue; + } + + user = kore_calloc(1, sizeof(*user)); + user->uid = uids++; + user->name = kore_strdup(line); + user->passphrase = kore_strdup(pwd); + TAILQ_INSERT_TAIL(&users, user, list); + } + + fclose(fp); +} + +void index_rebuild(void) { FTSENT *fe; @@ -359,6 +453,150 @@ referer(struct http_request *req, const void *unused) } int +auth_login(struct http_request *req) +{ + size_t i; + int len; + struct user *up; + struct session session; + char *user, *pass; + u_int8_t buf[BLOG_SESSION_LEN]; + + if (req->method == HTTP_METHOD_GET) + return (asset_serve_login_html(req)); + + http_populate_post(req); + + if (!http_argument_get_string(req, "user", &user) || + !http_argument_get_string(req, "passphrase", &pass)) { + req->method = HTTP_METHOD_GET; + return (asset_serve_login_html(req)); + } + + up = NULL; + TAILQ_FOREACH(up, &users, list) { + if (!strcmp(user, up->name)) + break; + } + + if (up == NULL) { + req->method = HTTP_METHOD_GET; + kore_log(LOG_INFO, "auth_login: no user data?"); + return (asset_serve_login_html(req)); + } + + if (crypto_pwhash_str_verify(up->passphrase, pass, strlen(pass)) != 0) { + req->method = HTTP_METHOD_GET; + return (asset_serve_login_html(req)); + } + + session.uid = up->uid; + memset(session.data, 0, sizeof(session.data)); + + randombytes_buf(buf, sizeof(buf)); + for (i = 0; i < sizeof(buf); i++) { + len = snprintf(session.data + (i * 2), + sizeof(session.data) - (i * 2), "%02x", buf[i]); + if (len == -1 || (size_t)len >= sizeof(session.data)) { + kore_log(LOG_ERR, "failed to hexify session"); + req->method = HTTP_METHOD_GET; + return (asset_serve_login_html(req)); + } + } + + kore_msg_send(KORE_MSG_WORKER_ALL, MSG_SESSION_ADD, + &session, sizeof(session)); + + http_response_header(req, "location", "/drafts/"); + http_response_cookie(req, "blog_token", session.data, + "/drafts/", 0, 0, NULL); + + kore_log(LOG_INFO, "login for '%s'", up->name); + http_response(req, HTTP_STATUS_FOUND, NULL, 0); + + return (KORE_RESULT_OK); +} + +int +auth_user_exists(struct http_request *req, char *user) +{ + struct user *usr; + + if (user == NULL) + return (KORE_RESULT_ERROR); + + TAILQ_FOREACH(usr, &users, list) { + if (!strcmp(usr->name, user)) + return (KORE_RESULT_OK); + } + + return (KORE_RESULT_ERROR); +} + +void +auth_session_add(struct kore_msg *msg, const void *data) +{ + struct user *user; + const struct session *session; + + if (msg->length != sizeof(*session)) { + kore_log(LOG_ERR, "auth_session_add: invalid len (%u)", + msg->length); + return; + } + + session = data; + + TAILQ_FOREACH(user, &users, list) { + if (user->uid == session->uid) { + memcpy(&user->session, session, sizeof(*session)); + break; + } + } +} + +void +auth_session_del(struct kore_msg *msg, const void *data) +{ + u_int32_t uid; + struct user *user; + + if (msg->length != sizeof(uid)) { + kore_log(LOG_ERR, "auth_session_del: invalid len (%u)", + msg->length); + return; + } + + memcpy(&uid, data, sizeof(uid)); + + TAILQ_FOREACH(user, &users, list) { + if (user->uid == uid) { + memset(&user->session, 0, sizeof(user->session)); + break; + } + } +} + +int +auth_session(struct http_request *req, const char *cookie) +{ + struct user *user; + + if (cookie == NULL) + return (KORE_RESULT_ERROR); + + TAILQ_FOREACH(user, &users, list) { + if (!strcmp(user->session.data, cookie)) { + kore_log(LOG_INFO, "%s requested by %s", + req->path, user->name); + return (KORE_RESULT_OK); + } + } + + return (KORE_RESULT_ERROR); +} + +int redirect(struct http_request *req) { http_response_header(req, "location", "/");