timekeeper

[Abandoned unfinished] CGI web application in C for time tracking. (My first, just a learning project)
Log | Files | Refs | README

commit 3b59becd574888d4dce219fb6f4e1b4e337a62f7
parent fefac6e2b98cefe2abfb2cb7942abccb7b95a7ce
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Thu, 29 Feb 2024 17:36:52 -0800

Add account management page

This page currently only allows you to delete your account.

Diffstat:
Mtimekeeper.c | 140++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 117 insertions(+), 23 deletions(-)

diff --git a/timekeeper.c b/timekeeper.c @@ -31,6 +31,7 @@ enum Field { KeyPassword, KeyHash, /* Authentication hash */ KeyCreate, /* Whether to create a new user in login page */ + KeyDelete, /* Delete account in account page */ KeyMax }; @@ -38,6 +39,7 @@ enum Page { PageIndex, PageLogin, PageLogout, + PageAccount, Page404, PageMax }; @@ -47,6 +49,7 @@ enum StmtID { StmtAddUser, StmtGetUserByHash, StmtGetUserByName, + StmtDeleteUser, StmtMax }; @@ -67,6 +70,7 @@ static char *pages[] = { [PageIndex] = "index", [PageLogin] = "login", [PageLogout] = "logout", + [PageAccount] = "account", [Page404] = "404" }; @@ -199,6 +203,16 @@ getuser(struct pagedata *pd, char *field, char *value) 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) { @@ -252,15 +266,36 @@ starthtmldoc(struct pagedata *pd, enum khttp code) } enum kcgi_err -page404(struct pagedata *pd) +errorpage(struct pagedata *pd, enum khttp code) { enum kcgi_err status; - if ((status = khttp_head(&pd->req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_404])) != KCGI_OK || + if ((status = khttp_head(&pd->req, kresps[KRESP_STATUS], "%s", khttps[code])) != KCGI_OK || (status = khttp_head(&pd->req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_TEXT_HTML])) != KCGI_OK || (status = khttp_body(&pd->req))) return status; - return khttp_puts(&pd->req, "<!doctype HTML><html><head><title>404 Not found</title></head><h1>404 Not found</h1></html>"); + return khttp_printf(&pd->req, + "<!doctype HTML><html><head><title>%1$s</title></head><h1>%1$s</h1></html>", + khttps[code]); +} + +enum kcgi_err +page404(struct pagedata *pd) +{ + return errorpage(pd, KHTTP_404); +} + +enum kcgi_err +htmllink(struct pagedata *pd, char *link, char *text) +{ + enum kcgi_err status; + + if ((status = khtml_attr(&pd->html, KELEM_A, + KATTR_HREF, link, KATTR__MAX)) != KCGI_OK || + (status = khtml_puts(&pd->html, text)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + return status; + return KCGI_OK; } enum kcgi_err @@ -278,13 +313,16 @@ pageindex(struct pagedata *pd) khtml_printf(&pd->html, "Welcome %s!", user ? user->name : "friend"); khtml_closeelem(&pd->html, 1); - if ((status = khtml_attr(&pd->html, KELEM_A, - KATTR_HREF, user ? "/logout" : "/login", - KATTR__MAX)) != KCGI_OK || - (status = khtml_puts(&pd->html, user ? "Logout" : "Login")) != KCGI_OK || - (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) + if ((status = htmllink(pd, user ? "/logout" : "/login", + user ? "Logout" : "Login")) != KCGI_OK) return status; + if (user) { + if ((status = khtml_puts(&pd->html, " | ")) != KCGI_OK || + (status = htmllink(pd, pages[PageAccount], "Manage account"))) + return status; + } + return khtml_close(&pd->html); } @@ -362,22 +400,25 @@ pagelogin(struct pagedata *pd) fpass = pd->req.fieldmap[KeyPassword]->parsed.s; if (pd->req.fieldmap[KeyCreate]) { - /* Ignoring return value */ - adduser(pd, fuser, fpass); + if (adduser(pd, fuser, fpass)) + ls = LoginError; + } - ls = loginuser(&user, pd, fuser, fpass); - if (ls == LoginValid) { - status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], "%s=%s; Path=/", - pd->keys[KeyHash].name, user->hash); - if (status != KCGI_OK) - return status; + if (ls == LoginUntried) { + ls = loginuser(&user, pd, fuser, fpass); + if (ls == LoginValid) { + status = khttp_head(&pd->req, kresps[KRESP_SET_COOKIE], "%s=%s; Path=/", + pd->keys[KeyHash].name, user->hash); + if (status != KCGI_OK) + return status; + } } } else { user = sitegetlogin(pd); } if (user) - return redirect(pd, "index", "Logged in"); + return redirect(pd, pages[PageIndex], "Logged in"); if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK) return status; @@ -406,7 +447,7 @@ pagelogin(struct pagedata *pd) (status = khtml_puts(&pd->html, "Create user ")) != KCGI_OK || (status = khtml_attr(&pd->html, KELEM_INPUT, KATTR_TYPE, "checkbox", - KATTR_NAME, "create", + KATTR_NAME, pd->keys[KeyCreate].name, KATTR__MAX)) != KCGI_OK || (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || @@ -436,16 +477,63 @@ pagelogout(struct pagedata *pd) return redirect(pd, pages[PageIndex], "You are being logged out"); } +enum kcgi_err +pageaccount(struct pagedata *pd) +{ + enum kcgi_err status; + struct user *user; + + user = sitegetlogin(pd); + if (!user) + return errorpage(pd, KHTTP_401); + + if (pd->req.fieldmap[KeyDelete] && strcmp(pd->req.fieldmap[KeyDelete]->parsed.s, "yes") == 0) { + if (deleteuser(pd, user->hash) == 0) + return redirect(pd, pages[PageIndex], "Account deleted"); + else + return errorpage(pd, KHTTP_500); + } + + if ((status = starthtmldoc(pd, KHTTP_200)) != KCGI_OK || + (status = htmlwithin(pd, KELEM_H1, "Account")) != KCGI_OK || + (status = htmlwithin(pd, KELEM_H2, "Delete account")) != KCGI_OK || + (status = htmlwithin(pd, KELEM_P, "Deleting an account is irreversible:" + " no account data can be restored after deletion.")) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_FORM)) != KCGI_OK || + (status = khtml_elem(&pd->html, KELEM_LABEL)) != KCGI_OK || + (status = khtml_puts(&pd->html, "Confirm permanent deletion:")) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_INPUT, + KATTR_TYPE, "checkbox", + KATTR_NAME, pd->keys[KeyDelete].name, + KATTR_VALUE, "yes", + KATTR_REQUIRED, "true", + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK || + (status = khtml_putc(&pd->html, ' ')) != KCGI_OK || + (status = khtml_attr(&pd->html, KELEM_INPUT, + KATTR_TYPE, "submit", + KATTR_VALUE, "Delete", + KATTR__MAX)) != KCGI_OK || + (status = khtml_closeelem(&pd->html, 1)) != KCGI_OK) { + freeuser(user); + return status; + } + + freeuser(user); + return khtml_close(&pd->html); +} + int main(void) { - enum kcgi_err status; + enum kcgi_err status, pagestatus; struct kfcgi *fcgi; struct kvalid keys[] = { [KeyUsername] = { kvalid_name, "username" }, [KeyPassword] = { kvalid_pass, "password" }, [KeyHash] = { kvalid_stringne, "hash" }, [KeyCreate] = { kvalid_stringne, "create" }, + [KeyDelete] = { kvalid_stringne, "delete" } }; struct pagedata pd = { .keys = keys @@ -458,12 +546,14 @@ main(void) [StmtInit] = { .stmt = "CREATE TABLE IF NOT EXISTS users (hash TEXT PRIMARY KEY, name TEXT UNIQUE)" }, [StmtAddUser] = { .stmt = "INSERT INTO users (hash, name) VALUES (?, ?)" }, [StmtGetUserByName] = { .stmt = "SELECT * FROM users WHERE name IS ?" }, - [StmtGetUserByHash] = { .stmt = "SELECT * FROM users WHERE hash IS ?" } + [StmtGetUserByHash] = { .stmt = "SELECT * FROM users WHERE hash IS ?" }, + [StmtDeleteUser] = { .stmt = "DELETE FROM users WHERE hash IS ?" } }; enum kcgi_err (*pagefunctions[])(struct pagedata *) = { [PageIndex] = pageindex, [PageLogin] = pagelogin, [PageLogout] = pagelogout, + [PageAccount] = pageaccount, [Page404] = page404 }; @@ -489,11 +579,15 @@ main(void) err(1, "Unable to setup database"); status = KCGI_OK; - while (status == KCGI_OK && (status = khttp_fcgi_parse(fcgi, &pd.req)) == KCGI_OK) { + while ((status = khttp_fcgi_parse(fcgi, &pd.req)) == KCGI_OK) { if (pd.req.page == PageMax) - status = pagefunctions[Page404](&pd); + pagestatus = pagefunctions[Page404](&pd); else - status = pagefunctions[pd.req.page](&pd); + pagestatus = pagefunctions[pd.req.page](&pd); + if (pagestatus) + kutil_warn(&pd.req, NULL, "%s: Returned %d", + pages[(pd.req.page == PageMax ? Page404 : pd.req.page)], + pagestatus); khttp_free(&pd.req); }