diff options
| author | Xavier Del Campo Romero <xavi92@disroot.org> | 2025-09-22 17:32:44 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi92@disroot.org> | 2026-02-13 09:57:39 +0100 |
| commit | 78bf2fe4a5bf37514f6dfd203ef969da0bf40c2e (patch) | |
| tree | 33f9440b8ee0fa7a3b3ad033616d722d2101bb4d /auth.c | |
| parent | 107a2e43d54f9a42fb85b00b83cb0d9abb194680 (diff) | |
Diffstat (limited to 'auth.c')
| -rw-r--r-- | auth.c | 327 |
1 files changed, 327 insertions, 0 deletions
@@ -0,0 +1,327 @@ +/* + * nanobbs, a tiny forums software. + * Copyright (C) 2025-2026 Xavier Del Campo Romero + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "auth.h" +#include "db.h" +#include "defs.h" +#include "endpoints.h" +#include "jwt.h" +#include "op.h" +#include <dynstr.h> +#include <libweb/http.h> +#include <sodium.h> +#include <sqlite3.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +struct validate +{ + bool valid; + unsigned long id; + enum auth_role role; + sqlite3 *db; + auth_fn fn; + void *user; +}; + +static void free_validate(void *const p) +{ + int error; + struct validate *const v = p; + + if (!v) + return; + else if ((error = sqlite3_close(v->db)) != SQLITE_OK) + fprintf(stderr, "%s: sqlite3_close: %s\n", __func__, + sqlite3_errstr(error)); + + free(v); +} + +static int check_role(const char *const s, enum auth_role *const out) +{ + static const struct r + { + const char *s; + enum auth_role role; + } roles[] = + { + {"admin", AUTH_ROLE_ADMIN}, + {"mod", AUTH_ROLE_MOD}, + {"user", AUTH_ROLE_USER}, + {"banned", AUTH_ROLE_BANNED} + }; + + for (size_t i = 0; i < sizeof roles / sizeof *roles; i++) + { + const struct r *const r = &roles[i]; + + if (!strcmp(s, r->s)) + { + *out = r->role; + return 0; + } + } + + fprintf(stderr, "%s: invalid role: %s\n", __func__, s); + return -1; +} + +static int end(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + struct validate *const v = args; + + if (!v->valid) + ret = v->fn(p, r, v->user, v->db, NULL); + else + { + const struct http_cookie *const c = &p->cookie; + const char *const username = c->field; + const struct auth_user u = + { + .username = username, + .role = v->role, + .id = v->id + }; + + ret = v->fn(p, r, v->user, v->db, &u); + } + + free(v); + return ret; +} + +static int signkey(const struct http_cookie *const c, const char *const key, + struct validate *const v) +{ + int ret = -1; + cJSON *j = NULL; + unsigned char dkey[crypto_auth_hmacsha256_KEYBYTES]; + const size_t len = strlen(key), explen = sizeof dkey * 2; + const cJSON *u; + const char *user; + + if (len != explen) + { + fprintf(stderr, "%s: key size mismatch, got %zu, expected %zu\n", + __func__, len, explen); + goto end; + } + else if (sodium_hex2bin(dkey, sizeof dkey, key, len, NULL, NULL, NULL)) + { + fprintf(stderr, "%s: sodium_hex2bin failed\n", __func__); + goto end; + } + else if ((ret = jwt_decode(c->value, dkey, sizeof dkey, &j))) + { + if (ret < 0) + fprintf(stderr, "%s: jwt_check failed\n", __func__); + + goto end; + } + else if (!(u = cJSON_GetObjectItem(j, "name")) + || !(user = cJSON_GetStringValue(u)) + || strcmp(user, c->field)) + { + ret = 1; + goto end; + } + + ret = 0; + +end: + cJSON_Delete(j); + return ret; +} + +static int row(sqlite3_stmt *const stmt, const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1, error; + struct validate *const v = args; + sqlite3 *const db = v->db; + char *const key = db_str(db, stmt, "signkey"), *name = NULL; + enum auth_role role; + unsigned id; + + if (!key) + { + fprintf(stderr, "%s: db_str %s failed\n", __func__, "signkey"); + goto end; + } + else if ((ret = db_uint(db, stmt, "id", &id))) + { + fprintf(stderr, "%s: db_uint %s failed\n", __func__, "id"); + goto end; + } + else if (!(name = db_str(db, stmt, "name"))) + { + fprintf(stderr, "%s: db_str %s failed\n", __func__, "name"); + goto end; + } + else if (check_role(name, &role)) + { + fprintf(stderr, "%s: role failed\n", __func__); + goto end; + } + else if ((error = signkey(&p->cookie, key, v))) + { + if (error < 0) + { + fprintf(stderr, "%s: signkey failed\n", __func__); + goto end; + } + } + else if (!error) + { + v->valid = true; + v->id = id; + v->role = role; + } + + ret = 0; + +end: + free(name); + free(key); + return ret; +} + +static int check_username(const char *const name) +{ + return strspn(name, USER_SYMS) != strlen(name); +} + +static int setup(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + int ret = -1; + const struct http_cookie *const c = &p->cookie; + const char *const username = c->field; + struct validate *const v = args; + struct dynstr d; + + dynstr_init(&d); + + if (dynstr_append(&d, "SELECT users.id, users.signkey, roles.name " + "FROM users JOIN roles ON users.roleid = roles.id " + "WHERE users.name = '%s';", username)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + const struct op_cfg op = + { + .error = free_validate, + .end = end, + .row = row, + .args = v + }; + + if (!op_run(v->db, d.str, &op, r)) + { + fprintf(stderr, "%s: op_run failed\n", __func__); + goto end; + } + + ret = 0; + +end: + + if (ret) + free_validate(v); + + dynstr_free(&d); + return ret; +} + +static int open_db(const struct http_payload *const p, + struct http_response *const r, void *const user, void *const args) +{ + struct validate *const v = args; + const struct cfg *const cfg = user; + const int error = db_open(cfg->dir, &v->db); + + if (error != SQLITE_OK) + { + if (error != SQLITE_BUSY) + { + fprintf(stderr, "%s: db_open: %s\n", __func__, + sqlite3_errstr(error)); + goto failure; + } + } + else + *r = (const struct http_response) + { + .step.payload = setup, + .args = v + }; + + return 0; + +failure: + free_validate(v); + return -1; +} + +int auth_validate(const struct http_payload *const p, + struct http_response *const r, void *const user, const auth_fn fn) +{ + int ret = -1; + struct validate *v = NULL; + const struct http_cookie *const c = &p->cookie; + const char *const username = c->field; + + if (!username || !c->value || check_username(username)) + { + ret = 1; + goto failure; + } + else if (!(v = malloc(sizeof *v))) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + goto failure; + } + + *r = (const struct http_response) + { + .step.payload = open_db, + .free = free_validate, + .args = v + }; + + *v = (const struct validate) + { + .fn = fn, + .user = user + }; + + return 0; + +failure: + free(v); + return ret; +} |
