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:
M | timekeeper.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);
}