- Go 81.1%
- HTML 18.5%
- JavaScript 0.2%
- CSS 0.2%
- serve static assets from /v/static - update the stylesheet URL - document /v-only Apache proxying - add coverage for the scoped asset path |
||
|---|---|---|
| cmd | ||
| directory | ||
| http | ||
| store | ||
| tmpl | ||
| utils | ||
| voting | ||
| .gitignore | ||
| global.css | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| package.json | ||
| pnpm-lock.yaml | ||
| README.md | ||
| tailwind.config.js | ||
GoVote
Referendums & Consensus
A voting tool for the web, written in Go.
Develop
yarn install
go generate
go run ./main
Run behind Apache
The server trusts an authenticated username supplied by Apache. It listens on
127.0.0.1:3000 by default so that clients cannot bypass Apache and forge that
header. Keep the backend private even when using a different bind address.
Configuration:
BASE_URLis the public URL used in receipt QR codes. Set it to the HTTPS URL in production; it also enables theSecureflag on the CSRF cookie.REMOTE_USER_HEADERselects the trusted username header and defaults toX-Remote-User.GOVOTE_DBselects the SQLite database path and defaults to./govote.db.
Apache must replace any client-supplied username header with its authenticated identity before proxying the request. Do not use a "set if empty" rule. The app also rejects requests for which this header is empty.
On Debian 13, enable the required modules with:
sudo a2enmod authnz_ldap headers proxy proxy_http ssl
Use the LDAP attribute authorization provider for the user's direct memberOf
value. A broad Require ldap-filter memberOf=... search returns every member of
the group rather than the single authenticated entry and therefore fails
authorization:
<Location /v>
AuthType Basic
AuthBasicProvider ldap
AuthName "Crew"
AuthLDAPURL "ldap://meridian.c-base.org:389/ou=crew,dc=c-base,dc=org?uid,memberOf" STARTTLS
AuthLDAPRemoteUserAttribute uid
Require ldap-attribute memberOf="cn=crew,ou=groups,dc=c-base,dc=org"
RequestHeader unset X-Remote-User
RequestHeader set X-Remote-User "%{AUTHENTICATE_UID}e"
</Location>
ProxyPass /v http://127.0.0.1:3000/v
ProxyPassReverse /v http://127.0.0.1:3000/v
Keep certificate verification enabled for LDAP STARTTLS. If the LDAP certificate is issued by a private CA, install that CA in Debian's trust store for the Go application and configure Apache's LDAP trust settings as needed.
An anonymous voting hides elector names in published results. Votes remain linked to elector names in the database because the final vote from each elector supersedes their earlier votes.
LDAP group selection
Set these environment variables to enable the group selector on the new-voting form:
LDAP_URL=ldap://meridian.c-base.org:389
LDAP_STARTTLS=true
LDAP_USER_BASE_DN=ou=crew,dc=c-base,dc=org
LDAP_GROUP_BASE_DN=ou=groups,dc=c-base,dc=org
The defaults match this directory layout: user names are read from uid, group
names from cn, and membership from the users' memberOf attribute. The app
uses anonymous LDAP access unless both LDAP_BIND_DN and
LDAP_BIND_PASSWORD are set. Optional settings are:
LDAP_USER_ATTRIBUTE, defaultuidLDAP_GROUP_ATTRIBUTE, defaultcnLDAP_MEMBERSHIP_ATTRIBUTE, defaultmemberOfLDAP_TLS_SERVER_NAME, useful whenLDAP_URLcontains an IP address but the TLS certificate identifies a hostname
The group list is derived from memberOf values on user entries. This works
with the current directory ACLs even though some entries below ou=groups are
not anonymously visible. The selected group's current members are expanded
once, when the voting is created, and stored as individual electors. Manually
entered electors are merged with the group and duplicates are removed
case-insensitively. This snapshot is intentional: later LDAP membership changes
do not silently alter an active voting's electorate or quorum.
Adding memberOf to AuthLDAPURL does retrieve that attribute in Apache's
existing lookup, as AUTHENTICATE_MEMBEROF; AuthLDAPRemoteUserAttribute uid
also makes REMOTE_USER use the canonical directory value. That information
only describes the user currently signing in, however. It cannot populate the
selector or expand an arbitrary selected group to all its members, so the
separate, read-only LDAP query is still needed.
User systemd service
The example user unit assumes the checkout is at
~/src/code.c-base.org/baccenfutter/govote and the installed executable is at
~/go/bin/govote. Install and start it with:
mkdir -p ~/.config/systemd/user
cp contrib/systemd/user/govote.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now govote.service
Inspect it with systemctl --user status govote.service and
journalctl --user -u govote.service. To start user services at boot without
an interactive login, enable lingering with loginctl enable-linger.