"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
gotwebd login status and logout link
To:
gameoftrees@openbsd.org
Date:
Sat, 7 Feb 2026 15:39:46 +0100

Download raw body.

Thread
Make gotwebd display the name of the logged in user, and add a link which
can be clicked to log out (asking the browser to delete the auth cookie).

ok?

M  gotwebd/auth.c                             |  65+  0-
M  gotwebd/fcgi.c                             |   6+  0-
M  gotwebd/files/htdocs/gotwebd/gotweb.css    |   9+  0-
M  gotwebd/gotweb.c                           |   6+  0-
M  gotwebd/gotwebd.h                          |   3+  0-
M  gotwebd/pages.tmpl                         |  12+  1-
M  gotwebd/sockets.c                          |   3+  0-
M  include/got_error.h                        |   1+  0-
M  lib/error.c                                |   1+  0-
M  regress/gotsysd/test_gotwebd.sh            |   6+  0-
M  regress/gotsysd/test_gotwebd_repos_www.sh  |   1+  0-

11 files changed, 113 insertions(+), 1 deletion(-)

commit - cc8eaf00fec74bd43bc96a5daedef4c8540964bf
commit + 0b654469a1c4fc80c19dd929a6ff463be03c24ea
blob - 3e9cee3c275c4af7cd66b09401c857dac8177c95
blob + e2dbe8fa26b762709ae7cfc950d4c8fe3dab6c38
--- gotwebd/auth.c
+++ gotwebd/auth.c
@@ -420,6 +420,69 @@ err:
 	}
 }
 
+static void
+do_logout(struct request *c)
+{
+	const struct got_error *error = NULL;
+	struct gotwebd *env = gotwebd_env;
+	uid_t uid;
+	char *hostname = NULL;
+	const char *identifier = NULL;
+	struct gotweb_url url;
+
+	int r;
+
+	if (login_check_token(&uid, &hostname, c->fcgi_params.auth_cookie,
+	    auth_token_secret, sizeof(auth_token_secret),
+	    "authentication") == -1) {
+		error = got_error(GOT_ERR_LOGOUT_FAILED);
+		goto err;
+	}
+
+	/*
+	 * The www user ID represents the case where no authentication
+	 * occurred. This user must not be allowed to log in.
+	 */
+	if (uid == env->www_uid) {
+		error = got_error(GOT_ERR_LOGOUT_FAILED);
+		goto err;
+	}
+
+	c->client_uid = uid;
+	if (strcmp(hostname, c->fcgi_params.server_name) != 0) {
+		error = got_error_msg(GOT_ERR_LOGOUT_FAILED,
+		    "wrong server name in authentication cookie");
+		goto err;
+	}
+
+	if (gotwebd_env->gotwebd_verbose > 0) {
+		log_info("logging out uid %u as %s for server \"%s\"",
+		    uid, identifier, hostname);
+	}
+
+	/* Ask the browser to delete the authentication cookie.  */
+	r = tp_writef(c->tp, "Clear-Site-Data: \"cookies\"\r\n");
+	if (r == -1) {
+		error = got_error_from_errno("tp_writef");
+		goto err;
+	}
+
+	memset(&url, 0, sizeof(url));
+	url.action = INDEX;
+	gotweb_reply(c, 307, "text/html", &url);
+	return;
+
+err:
+	free(hostname);
+	hostname = NULL;
+
+	log_warnx("%s: %s", __func__, error->msg);
+	c->t->error = error;
+	if (gotweb_reply(c, 400, "text/html", NULL) == -1)
+		return;
+	gotweb_render_page(c->tp, gotweb_render_error);
+}
+
 static const struct got_error *
 login_error_hint(struct request *c)
 {
@@ -760,6 +823,8 @@ auth_dispatch_sockets(int fd, short event, void *arg)
 
 			if (c->fcgi_params.qs.login[0] != '\0')
 				do_login(c);
+			else if (c->fcgi_params.qs.logout)
+				do_logout(c);
 			else
 				process_request(c);
 
blob - 2a81e7f5e7e6523aa37d8d9e15a0ab325483eb37
blob + 3aa0806a14d767bd4f2ead5eea1fec881a7c5e9c
--- gotwebd/fcgi.c
+++ gotwebd/fcgi.c
@@ -192,6 +192,7 @@ static const struct querystring_keys querystring_keys[
 	{ "index_page",	GOTWEBD_QS_INDEX_PAGE },
 	{ "path",		GOTWEBD_QS_PATH },
 	{ "login",		GOTWEBD_QS_LOGIN },
+	{ "logout",		GOTWEBD_QS_LOGOUT },
 };
 
 static const struct action_keys action_keys[] = {
@@ -380,6 +381,11 @@ assign_querystring(struct querystring *qs, char *key, 
 				goto done;
 			}
 			break;
+		case GOTWEBD_QS_LOGOUT:
+			if (strcmp(value, "1") != 0)
+				break;
+			qs->logout = 1;
+			break;
  		}
 
 		/* entry found */
blob - ea4cdedb51cb3f930bbc483b0f82bc9590a180b6
blob + 15e6ffef2c615352d5e23d6ef94fe1e37fb9dff1
--- gotwebd/files/htdocs/gotwebd/gotweb.css
+++ gotwebd/files/htdocs/gotwebd/gotweb.css
@@ -101,6 +101,15 @@ hr {
 	padding-bottom: 10px;
 	padding-top: 10px;
 }
+#login_status {
+	color: lightgrey;
+	text-align: right;
+	padding-right: 10px;
+	padding-left: 10px;
+	padding-top: 5px;
+	padding-bottom: 5px;
+	font-weight: monospace;
+}
 #np_wrapper {
 	width: 100%;
 	border-bottom: 1px dotted #444444;
blob - 2dcdf486c002a5a2a948db88611b6912fc1de984
blob + 21603a5b4142f0537aca3e2741d0930cb22ef6b8
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -1469,6 +1469,12 @@ gotweb_render_url(struct request *c, struct gotweb_url
 		sep = "&";
 	}
 
+	if (url->logout == 1) {
+		if (tp_writef(c->tp, "%slogout=1", sep) == -1)
+			return -1;
+		sep = "&";
+	}
+
 	return 0;
 }
 
blob - 69c2896f72735357062dc4f09af598567a4aa339
blob + e308c7906f34668208667e5f2742f917d90a6df7
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -311,6 +311,7 @@ struct querystring {
 	int		 index_page;
 	char		 path[PATH_MAX];
 	char		 login[MAX_AUTH_COOKIE];
+	int		 logout;
 };
 
 struct gotwebd_fcgi_params {
@@ -556,6 +557,7 @@ struct gotweb_url {
 	const char	*folder;
 	const char	*headref;
 	const char	*path;
+	int		 logout;
 };
 
 struct querystring_keys {
@@ -577,6 +579,7 @@ enum querystring_elements {
 	GOTWEBD_QS_INDEX_PAGE,
 	GOTWEBD_QS_PATH,
 	GOTWEBD_QS_LOGIN,
+	GOTWEBD_QS_LOGOUT,
 };
 
 extern struct gotwebd	*gotwebd_env;
blob - 4fb768f84e038e9bdfc9ee78a9e9c615084cb757
blob + 620eae4ecfde34d2e50eb56fa4eb94374d73fe0d
--- gotwebd/pages.tmpl
+++ gotwebd/pages.tmpl
@@ -169,7 +169,7 @@ nextsep(char *s, char **t)
 	struct request		*c = tp->tp_arg;
 	struct server		*srv = c->srv;
 	const struct querystring *qs = c->t->qs;
-	struct gotweb_url	 u_path;
+	struct gotweb_url	 u_path, u_logout;
 	char			 prefix[MAX_DOCUMENT_URI * 2];
 	const char		*css = srv->custom_css;
 	const char		*url_root, *url_path;
@@ -198,6 +198,11 @@ nextsep(char *s, char **t)
 	memset(&u_path, 0, sizeof(u_path));
 	u_path.index_page = -1;
 	u_path.action = SUMMARY;
+
+	memset(&u_logout, 0, sizeof(u_logout));
+	u_logout.index_page = -1;
+	u_logout.action = INDEX;
+	u_logout.logout = 1;
 !}
 <!doctype html>
 <html>
@@ -221,6 +226,12 @@ nextsep(char *s, char **t)
           <img src="{{ prefix }}/{{ srv->logo }}" />
         </a>
       </div>
+      {{ if (c->access_identifier[0]) }}	
+      <div id="login_status">
+        Logged in as: <b>{{ c->access_identifier }}</b> &nbsp;
+	(<a href="{{ render gotweb_render_url(c, &u_logout) }}">Logout</a>)
+      </div>
+      {{ end }}
     </header>
     <nav id="site_path">
       <div id="site_link">
blob - 387e6c57b105661f9840264b1240b2880aba9517
blob + 6d106f0a375678d96ece6c28b54c08e49d0cdecd
--- gotwebd/sockets.c
+++ gotwebd/sockets.c
@@ -640,6 +640,9 @@ recv_parsed_params(struct imsg *imsg)
 		goto fail;
 	}
 
+	if (params.qs.logout)
+		p->qs.logout = 1;
+
 	if (params.document_uri[0] != '\0' &&
 	    strlcpy(p->document_uri, params.document_uri,
 	    sizeof(p->document_uri)) >= sizeof(p->document_uri)) {
blob - 1868cf47328cce63d6b5b9fec9cd8152af55ed29
blob + 301dcaa124592fd188562af5f52a713b9138d4b9
--- include/got_error.h
+++ include/got_error.h
@@ -201,6 +201,7 @@
 #define GOT_ERR_UNKNOWN_COMMAND	193
 #define GOT_ERR_NOT_FOUND	194
 #define GOT_ERR_MEDIA_TYPE	195
+#define GOT_ERR_LOGOUT_FAILED	196
 
 struct got_error {
         int code;
blob - 6d25b5840fd9283ff9543c6405e6dca52a0825f8
blob + dcca96bf17d67b78004204c30d6fdf6ecd2153f5
--- lib/error.c
+++ lib/error.c
@@ -252,6 +252,7 @@ static const struct got_error got_errors[] = {
 	{ GOT_ERR_UNKNOWN_COMMAND, "command not found" },
 	{ GOT_ERR_NOT_FOUND, "not found" },
 	{ GOT_ERR_MEDIA_TYPE,	"malformed media type" },
+	{ GOT_ERR_LOGOUT_FAILED, "logout failed" },
 };
 
 static struct got_custom_error {
blob - b8c0500e50a21e234c5a8ec5f7dd2688296be03e
blob + eb1b65c4fa32ec243b7e7e8e4f29b25d9e336c16
--- regress/gotsysd/test_gotwebd.sh
+++ regress/gotsysd/test_gotwebd.sh
@@ -173,6 +173,7 @@ EOF
 	w3m -cookie-jar "$testroot/cookies" "$url" -dump > $testroot/stdout
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_TEST_USER}  (Logout)
 Repositories
 Repository
 testrepo.git
@@ -202,6 +203,7 @@ EOF
 
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_TEST_USER}  (Logout)
 Repositories / testrepo.git / tree /
 
 Tree
@@ -401,6 +403,7 @@ EOF
 	w3m -cookie-jar "$testroot/cookies" "$url" -dump > $testroot/stdout
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_TEST_USER}  (Logout)
 Repositories
 Repository
 gottest.git
@@ -435,6 +438,7 @@ EOF
 	w3m -cookie-jar "$testroot/cookies" "$url" -dump > $testroot/stdout
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_DEV_USER}  (Logout)
 Repositories
 Repository
 gottestdev.git
@@ -601,6 +605,7 @@ EOF
 	w3m -cookie-jar "$testroot/cookies" "$url" -dump > $testroot/stdout
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_TEST_USER}  (Logout)
 Repositories
 Repository
 gottest.git
@@ -626,6 +631,7 @@ EOF
 
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_TEST_USER}  (Logout)
 Repositories / gottest.git / tree /
 reference refs/heads/main not found
 
blob - f649b0aa2de90feea176fb354a2986697d9e5826
blob + 56fb4c9aa170331283950ac27c8fdeb2db863c0b
--- regress/gotsysd/test_gotwebd_repos_www.sh
+++ regress/gotsysd/test_gotwebd_repos_www.sh
@@ -193,6 +193,7 @@ EOF
 	w3m -cookie-jar "$testroot/cookies" "$url" -dump > $testroot/stdout
 	cat > $testroot/stdout.expected <<EOF
 [got]
+Logged in as: ${GOTSYSD_TEST_USER}  (Logout)
 Repositories
 Repository
 www.git