commit e46326f376410983fc97685498425a6c5a361760
parent 3e3f2431e1b27841db2b36dd68c630323ff7c36c
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Tue, 5 Mar 2024 22:07:51 -0800
Move backend user functions to user.c
Also move SQL statements to stmt.c and small, general purpose
utilities to util.h (currently only macros).
Diffstat:
M | Makefile | | | 4 | ++-- |
A | stmt.c | | | 31 | +++++++++++++++++++++++++++++++ |
A | stmt.h | | | 17 | +++++++++++++++++ |
M | timekeeper.c | | | 181 | ++----------------------------------------------------------------------------- |
A | user.c | | | 135 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | user.h | | | 23 | +++++++++++++++++++++++ |
A | util.h | | | 3 | +++ |
7 files changed, 215 insertions(+), 179 deletions(-)
diff --git a/Makefile b/Makefile
@@ -4,8 +4,8 @@ cflags = ${CFLAGS} -O0 -Wall -Wextra -I/usr/local/include
lddflags = ${LDDFLAGS} -static -L/usr/local/lib -lkcgi -lkcgihtml -lz -lsqlbox -lsqlite3 -lm -lpthread
prefix = /var/www/htdocs/${name}.primus.lan
-src = page.c html.c pages/util.c
-hdr = ${src:.c=.h}
+src = page.c html.c user.c stmt.c pages/util.c
+hdr = ${src:.c=.h} util.h
obj = ${src:.c=.o}
all: ${name}
diff --git a/stmt.c b/stmt.c
@@ -0,0 +1,31 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <sqlbox.h>
+
+#include "stmt.h"
+
+struct sqlbox_pstmt pstmts[] = {
+ [StmtInit0] = { .stmt = "PRAGMA foreign_keys = ON" },
+ [StmtInit1] = { .stmt = "CREATE TABLE IF NOT EXISTS auth (hash TEXT PRIMARY KEY, name TEXT UNIQUE)" },
+ [StmtInit2] = { .stmt = "CREATE TABLE IF NOT EXISTS userdata (id TEXT PRIMARY KEY REFERENCES auth (hash), times BLOB DEFAULT '[]')" },
+ [StmtInit3] = { .stmt = "CREATE TRIGGER IF NOT EXISTS userdatahash AFTER INSERT ON auth FOR EACH ROW BEGIN\n"
+ "INSERT INTO userdata (id) VALUES (NEW.hash); END" },
+ [StmtInitLast] = { .stmt = "CREATE TRIGGER IF NOT EXISTS userdelete AFTER DELETE ON auth FOR EACH ROW BEGIN\n"
+ "DELETE FROM userdata WHERE id IS OLD.hash; END" },
+ [StmtAddUser] = { .stmt = "INSERT INTO auth (hash, name) VALUES (?, ?)" },
+ [StmtGetUserByName] = { .stmt = "SELECT * FROM auth WHERE name IS ?" },
+ [StmtGetUserByHash] = { .stmt = "SELECT * FROM auth WHERE hash IS ?" },
+ [StmtDeleteUser] = { .stmt = "DELETE FROM auth WHERE hash IS ?" },
+ [StmtNewTimeRow] = { .stmt = "UPDATE userdata SET times =\n"
+ "json_insert(times, '$[#]', json_array(null, null, null, null)) WHERE id IS ? AND\n"
+ "(json_array_length(times) = 0 OR json_extract(times, '$[#-1][3]') IS NOT null)" },
+ /* Currently, the user can set the end time with a
+ * break open and the break won't be ended. This is
+ * intentional, but also it should set the break end
+ * time to the end time in that case.
+ */
+ [StmtSetTime] = { .stmt = "UPDATE userdata SET times =\n"
+ "json_replace(times, '$[#-1][' || ? || ']', ?) WHERE id IS ? AND json_extract(times, '$[#-1][' || ? || ']') IS null" },
+ [StmtGetTimes] = { .stmt = "SELECT json_array_length(times) * 4 FROM userdata UNION ALL\n"
+ "SELECT value FROM userdata, json_tree(times) WHERE userdata.id IS ? AND type IS NOT 'array'" }
+};
diff --git a/stmt.h b/stmt.h
@@ -0,0 +1,17 @@
+enum StmtID {
+ StmtInit0,
+ StmtInit1,
+ StmtInit2,
+ StmtInit3,
+ StmtInitLast,
+ StmtAddUser,
+ StmtGetUserByHash,
+ StmtGetUserByName,
+ StmtDeleteUser,
+ StmtNewTimeRow,
+ StmtSetTime,
+ StmtGetTimes,
+ StmtMax
+};
+
+extern struct sqlbox_pstmt pstmts[];
diff --git a/timekeeper.c b/timekeeper.c
@@ -22,17 +22,13 @@
#include "page.h"
#include "html.h"
+#include "user.h"
+#include "stmt.h"
#include "pages/util.h"
-#define Promises "stdio rpath wpath cpath proc recvfd unix sendfd"
-#define Len(X) (sizeof(X) / sizeof(X[0]))
-#define Max(A,B) ((A) > (B) ? (A) : (B))
-#define Min(A,B) ((A) < (B) ? (A) : (B))
+#include "util.h"
-#define NameMin 3
-#define NameMax 64
-#define PassMin 8
-#define PassMax 72
+#define Promises "stdio rpath wpath cpath proc recvfd unix sendfd"
enum Field {
KeyUsername,
@@ -54,22 +50,6 @@ enum Page {
PageMax
};
-enum StmtID {
- StmtInit0,
- StmtInit1,
- StmtInit2,
- StmtInit3,
- StmtInitLast,
- StmtAddUser,
- StmtGetUserByHash,
- StmtGetUserByName,
- StmtDeleteUser,
- StmtNewTimeRow,
- StmtSetTime,
- StmtGetTimes,
- StmtMax
-};
-
enum TimeField {
StartTime,
BreakStartTime,
@@ -77,11 +57,6 @@ enum TimeField {
EndTime
};
-struct user {
- char *name;
- char *hash;
-};
-
static char *pages[] = {
[PageIndex] = "index",
[PageLogin] = "login",
@@ -120,13 +95,6 @@ kvalid_pass(struct kpair *p)
return 1;
}
-enum LoginStatus {
- LoginUntried = -1,
- LoginValid,
- LoginInvalid,
- LoginError
-};
-
int
initdb(struct pagedata *pd)
{
@@ -138,122 +106,6 @@ initdb(struct pagedata *pd)
return 0;
}
-int
-adduser(struct pagedata *pd, char *name, char *key)
-{
- char *salt, *hash;
-
- salt = bcrypt_gensalt(8);
- hash = bcrypt(key, salt);
- if (!hash)
- return 1;
-
- struct sqlbox_parm p[] = {
- { .sparm = hash, .type = SQLBOX_PARM_STRING },
- { .sparm = name, .type = SQLBOX_PARM_STRING }
- };
-
- if (sqlbox_exec(pd->db, pd->dbid, StmtAddUser, Len(p), p, 0) != SQLBOX_CODE_OK)
- return 1;
- return 0;
-}
-
-void
-freeuser(struct user *user)
-{
- if (!user)
- return;
- free(user->hash);
- free(user->name);
-}
-
-struct user *
-getuser(struct pagedata *pd, char *field, char *value)
-{
- struct user *user;
- size_t stmtid;
- enum StmtID gets;
- struct sqlbox_parmset *res;
- struct sqlbox_parm p[] = {
- { .sparm = value, .type = SQLBOX_PARM_STRING }
- };
-
- if (strcmp(field, "name") == 0)
- gets = StmtGetUserByName;
- else if (strcmp(field, "hash") == 0)
- gets = StmtGetUserByHash;
- else
- err(1, "Expected valid field");
-
- if (!(stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, gets, Len(p), p, 0)))
- err(1, "sdqlb");
-
- if (!(res = sqlbox_step(pd->db, stmtid)))
- err(1, "step");
-
- if (!res->psz && res->code == SQLBOX_CODE_OK) {
- kutil_info(&pd->req, NULL, "getuser: %s: %s not found", value, field);
- sqlbox_finalise(pd->db, stmtid);
- return NULL;
- }
-
- user = calloc(1, sizeof(*user));
- if (!user)
- err(1, "IDC");
-
- if (res->psz != 2)
- errx(1, "%zu: Invalid number of fields in user-data", res->psz);
- user->hash = strdup(res->ps[0].sparm);
- if (!user->hash)
- err(1, "IDC");
- user->name = strdup(res->ps[1].sparm);
- if (!user->name)
- err(1, "IDC");
-
- if (!sqlbox_finalise(pd->db, stmtid))
- err(1, "finalize");
-
- return user;
-}
-
-int
-deleteuser(struct pagedata *pd, char *hash)
-{
- struct sqlbox_parm p = {
- .sparm = hash, .type = SQLBOX_PARM_STRING
- };
-
- return sqlbox_exec(pd->db, 0, StmtDeleteUser, 1, &p, 0) != SQLBOX_CODE_OK;
-}
-
-enum LoginStatus
-loginuser(struct user **userp, struct pagedata *pd, char *name, char *key)
-{
- struct user *user;
- char *testhash;
-
- user = getuser(pd, "name", name);
- if (!user)
- return LoginInvalid;
-
- testhash = bcrypt(key, user->hash);
- if (!testhash) {
- freeuser(user);
- return LoginError;
- }
-
- if (strcmp(user->hash, testhash) != 0) {
- freeuser(user);
- return LoginInvalid;
- }
-
- if (userp)
- *userp = user;
- else
- freeuser(user);
- return LoginValid;
-}
-
/* getlogin is taken */
struct user *
sitegetlogin(struct pagedata *pd)
@@ -823,31 +675,6 @@ main(void)
struct sqlbox_src srcs[] = {
{ .fname = "/tmp/timekeeper.db", .mode = SQLBOX_SRC_RWC }
};
- struct sqlbox_pstmt pstmts[] = {
- [StmtInit0] = { .stmt = "PRAGMA foreign_keys = ON" },
- [StmtInit1] = { .stmt = "CREATE TABLE IF NOT EXISTS auth (hash TEXT PRIMARY KEY, name TEXT UNIQUE)" },
- [StmtInit2] = { .stmt = "CREATE TABLE IF NOT EXISTS userdata (id TEXT PRIMARY KEY REFERENCES auth (hash), times BLOB DEFAULT '[]')" },
- [StmtInit3] = { .stmt = "CREATE TRIGGER IF NOT EXISTS userdatahash AFTER INSERT ON auth FOR EACH ROW BEGIN\n"
- "INSERT INTO userdata (id) VALUES (NEW.hash); END" },
- [StmtInitLast] = { .stmt = "CREATE TRIGGER IF NOT EXISTS userdelete AFTER DELETE ON auth FOR EACH ROW BEGIN\n"
- "DELETE FROM userdata WHERE id IS OLD.hash; END" },
- [StmtAddUser] = { .stmt = "INSERT INTO auth (hash, name) VALUES (?, ?)" },
- [StmtGetUserByName] = { .stmt = "SELECT * FROM auth WHERE name IS ?" },
- [StmtGetUserByHash] = { .stmt = "SELECT * FROM auth WHERE hash IS ?" },
- [StmtDeleteUser] = { .stmt = "DELETE FROM auth WHERE hash IS ?" },
- [StmtNewTimeRow] = { .stmt = "UPDATE userdata SET times =\n"
- "json_insert(times, '$[#]', json_array(null, null, null, null)) WHERE id IS ? AND\n"
- "(json_array_length(times) = 0 OR json_extract(times, '$[#-1][3]') IS NOT null)" },
- /* Currently, the user can set the end time with a
- * break open and the break won't be ended. This is
- * intentional, but also it should set the break end
- * time to the end time in that case.
- */
- [StmtSetTime] = { .stmt = "UPDATE userdata SET times =\n"
- "json_replace(times, '$[#-1][' || ? || ']', ?) WHERE id IS ? AND json_extract(times, '$[#-1][' || ? || ']') IS null" },
- [StmtGetTimes] = { .stmt = "SELECT json_array_length(times) * 4 FROM userdata UNION ALL\n"
- "SELECT value FROM userdata, json_tree(times) WHERE userdata.id IS ? AND type IS NOT 'array'" }
- };
enum kcgi_err (*pagefunctions[])(struct pagedata *) = {
[PageIndex] = pageindex,
[PageLogin] = pagelogin,
diff --git a/user.c b/user.c
@@ -0,0 +1,135 @@
+#define const
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+#include <err.h>
+#include <pwd.h>
+
+#include <sqlbox.h>
+
+#include "page.h"
+#include "stmt.h"
+#include "user.h"
+#include "util.h"
+
+int
+adduser(struct pagedata *pd, char *name, char *key)
+{
+ char *salt, *hash;
+
+ salt = bcrypt_gensalt(8);
+ hash = bcrypt(key, salt);
+ if (!hash)
+ return 1;
+
+ struct sqlbox_parm p[] = {
+ { .sparm = hash, .type = SQLBOX_PARM_STRING },
+ { .sparm = name, .type = SQLBOX_PARM_STRING }
+ };
+
+ if (sqlbox_exec(pd->db, pd->dbid, StmtAddUser, Len(p), p, 0) != SQLBOX_CODE_OK)
+ return 1;
+ return 0;
+}
+
+void
+freeuser(struct user *user)
+{
+ if (!user)
+ return;
+ free(user->hash);
+ free(user->name);
+}
+
+struct user *
+getuser(struct pagedata *pd, char *field, char *value)
+{
+ struct user *user;
+ size_t stmtid;
+ enum StmtID gets;
+ struct sqlbox_parmset *res;
+ struct sqlbox_parm p[] = {
+ { .sparm = value, .type = SQLBOX_PARM_STRING }
+ };
+
+ if (strcmp(field, "name") == 0)
+ gets = StmtGetUserByName;
+ else if (strcmp(field, "hash") == 0)
+ gets = StmtGetUserByHash;
+ else
+ err(1, "Expected valid field");
+
+ if (!(stmtid = sqlbox_prepare_bind(pd->db, pd->dbid, gets, Len(p), p, 0)))
+ err(1, "sdqlb");
+
+ if (!(res = sqlbox_step(pd->db, stmtid)))
+ err(1, "step");
+
+ if (!res->psz && res->code == SQLBOX_CODE_OK) {
+ kutil_info(&pd->req, NULL, "getuser: %s: %s not found", value, field);
+ sqlbox_finalise(pd->db, stmtid);
+ return NULL;
+ }
+
+ user = calloc(1, sizeof(*user));
+ if (!user)
+ err(1, "IDC");
+
+ if (res->psz != 2)
+ errx(1, "%zu: Invalid number of fields in user-data", res->psz);
+ user->hash = strdup(res->ps[0].sparm);
+ if (!user->hash)
+ err(1, "IDC");
+ user->name = strdup(res->ps[1].sparm);
+ if (!user->name)
+ err(1, "IDC");
+
+ if (!sqlbox_finalise(pd->db, stmtid))
+ err(1, "finalize");
+
+ return user;
+}
+
+int
+deleteuser(struct pagedata *pd, char *hash)
+{
+ struct sqlbox_parm p = {
+ .sparm = hash, .type = SQLBOX_PARM_STRING
+ };
+
+ return sqlbox_exec(pd->db, 0, StmtDeleteUser, 1, &p, 0) != SQLBOX_CODE_OK;
+}
+
+enum LoginStatus
+loginuser(struct user **userp, struct pagedata *pd, char *name, char *key)
+{
+ struct user *user;
+ char *testhash;
+
+ user = getuser(pd, "name", name);
+ if (!user)
+ return LoginInvalid;
+
+ testhash = bcrypt(key, user->hash);
+ if (!testhash) {
+ freeuser(user);
+ return LoginError;
+ }
+
+ if (strcmp(user->hash, testhash) != 0) {
+ freeuser(user);
+ return LoginInvalid;
+ }
+
+ if (userp)
+ *userp = user;
+ else
+ freeuser(user);
+ return LoginValid;
+}
diff --git a/user.h b/user.h
@@ -0,0 +1,23 @@
+#define NameMin 3
+#define NameMax 64
+#define PassMin 8
+#define PassMax 72
+
+enum LoginStatus {
+ LoginUntried = -1,
+ LoginValid,
+ LoginInvalid,
+ LoginError
+};
+
+struct user {
+ char *name;
+ char *hash;
+};
+
+int adduser(struct pagedata *pd, char *name, char *key);
+void freeuser(struct user *user);
+struct user *getuser(struct pagedata *pd, char *field, char *value);
+int deleteuser(struct pagedata *pd, char *hash);
+enum LoginStatus loginuser(struct user **userp, struct pagedata *pd,
+ char *name, char *key);
diff --git a/util.h b/util.h
@@ -0,0 +1,3 @@
+#define Len(X) (sizeof(X) / sizeof(X[0]))
+#define Max(A,B) ((A) > (B) ? (A) : (B))
+#define Min(A,B) ((A) < (B) ? (A) : (B))