Compare commits
2 Commits
0893c713e4
...
bfc10c5a96
| Author | SHA1 | Date | |
|---|---|---|---|
| bfc10c5a96 | |||
|
|
724dada3f4 |
@ -136,5 +136,20 @@ def logout():
|
|||||||
if g.member is not None:
|
if g.member is not None:
|
||||||
tokens.forget(g.member["id"])
|
tokens.forget(g.member["id"])
|
||||||
session.clear()
|
session.clear()
|
||||||
flash("Sesión cerrada.", "ok")
|
|
||||||
return redirect(url_for("auth.login"))
|
# Deliberately NOT a redirect back to the login page.
|
||||||
|
#
|
||||||
|
# Clearing this session does not touch the Gitea session in the same
|
||||||
|
# browser, and Gitea remembers that this app was authorised. So the next
|
||||||
|
# click on "Entrar con Gitea" gets a code back immediately and signs the
|
||||||
|
# person straight back in without a password — which on a laptop shared
|
||||||
|
# around an association means "Salir" was telling them something untrue.
|
||||||
|
#
|
||||||
|
# Gitea cannot be signed out from here: its logout has been POST-only since
|
||||||
|
# 1.11.2, and a cross-site POST would need Gitea's CSRF token. `prompt=login`
|
||||||
|
# would be the other way round it, and is undocumented in every released
|
||||||
|
# version of Gitea's OAuth2 provider — not something to rest this on.
|
||||||
|
#
|
||||||
|
# So the honest thing is to say so and point at the one place that can
|
||||||
|
# finish the job.
|
||||||
|
return render_template("logged_out.html", gitea_url=current_app.config["GITEA_URL"].rstrip("/"))
|
||||||
|
|||||||
32
apps/board/templates/logged_out.html
Normal file
32
apps/board/templates/logged_out.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{# Shown instead of bouncing back to the login page, because bouncing back would
|
||||||
|
hide the fact that one click gets you straight in again. #}
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Sesión cerrada{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<article class="card card--center">
|
||||||
|
<h1>Saliste del área de la comunidad</h1>
|
||||||
|
|
||||||
|
<p class="muted">
|
||||||
|
Tu sesión aquí está cerrada. Pero este navegador <strong>sigue conectado a
|
||||||
|
Gitea</strong>, que es donde se guardan las cuentas — así que quien use
|
||||||
|
este ordenador después podría volver a entrar sin contraseña.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a class="btn" href="{{ gitea_url }}/user/logout">Cerrar sesión también en Gitea</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="muted small">
|
||||||
|
Si esa página no te desconecta, abre el menú de tu perfil en
|
||||||
|
<a href="{{ gitea_url }}">Gitea</a> y elige <em>Sign Out</em>.
|
||||||
|
Cerrar el navegador también sirve.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="muted small">
|
||||||
|
En tu propio ordenador no hace falta: puedes volver a entrar cuando quieras.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><a class="linkish" href="{{ url_for('auth.login') }}">Volver a entrar</a></p>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
||||||
@ -106,3 +106,41 @@ def test_login_redirect_cannot_be_pointed_offsite(client, monkeypatch, make_memb
|
|||||||
def test_responses_say_do_not_index(client):
|
def test_responses_say_do_not_index(client):
|
||||||
response = client.get("/comunidad/login")
|
response = client.get("/comunidad/login")
|
||||||
assert response.headers["X-Robots-Tag"] == "noindex, nofollow"
|
assert response.headers["X-Robots-Tag"] == "noindex, nofollow"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logout_says_the_gitea_session_is_still_open(client, db, make_member, sign_in, post):
|
||||||
|
"""Redirecting to the login page would hide the problem: one click on
|
||||||
|
"Entrar con Gitea" signs you straight back in, because Gitea's session and
|
||||||
|
its record of the authorisation both survive."""
|
||||||
|
member_id = make_member("maria")
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO gitea_tokens (member_id, access_token) VALUES (?, 'tok')",
|
||||||
|
(member_id,),
|
||||||
|
)
|
||||||
|
sign_in(member_id)
|
||||||
|
|
||||||
|
response = post("/comunidad/logout")
|
||||||
|
page = response.get_data(as_text=True)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "sigue conectado" in page # the warning, not a redirect
|
||||||
|
assert "/user/logout" in page
|
||||||
|
|
||||||
|
with client.session_transaction() as session:
|
||||||
|
assert "member_id" not in session
|
||||||
|
assert db.execute("SELECT 1 FROM gitea_tokens WHERE member_id = ?",
|
||||||
|
(member_id,)).fetchone() is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_logout_leaves_nothing_the_server_can_act_with(client, db, make_member, sign_in, post):
|
||||||
|
"""The token is what lets this server commit as the member. Clearing the
|
||||||
|
cookie without dropping it would end the browser's access but not ours."""
|
||||||
|
member_id = make_member("maria")
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO gitea_tokens (member_id, access_token) VALUES (?, 'tok')",
|
||||||
|
(member_id,),
|
||||||
|
)
|
||||||
|
sign_in(member_id)
|
||||||
|
post("/comunidad/logout")
|
||||||
|
|
||||||
|
assert db.execute("SELECT COUNT(*) AS n FROM gitea_tokens").fetchone()["n"] == 0
|
||||||
|
|||||||
@ -501,3 +501,76 @@ Not urgent — leaving it costs a folder and one `wget` in the pipeline:
|
|||||||
1. `rm -rf static/admin/`
|
1. `rm -rf static/admin/`
|
||||||
2. Delete the `wget … decap-cms.js` line from `.woodpecker.yml`
|
2. Delete the `wget … decap-cms.js` line from `.woodpecker.yml`
|
||||||
3. Delete the `decap-cms` OAuth application in Gitea
|
3. Delete the `decap-cms` OAuth application in Gitea
|
||||||
|
|
||||||
|
## 12. Make Gitea look like the site
|
||||||
|
|
||||||
|
Members sign in to `/comunidad/` through Gitea, so Gitea's sign-in form and its
|
||||||
|
authorize dialog are part of the journey for everyone — not just for you, and
|
||||||
|
not just for people who open a repository. Unthemed they are two dark screens in
|
||||||
|
the middle of a cream-coloured site.
|
||||||
|
|
||||||
|
How often anyone sees them is worth knowing before judging the result: the
|
||||||
|
**authorize dialog appears once per person, ever** — Gitea remembers the grant —
|
||||||
|
and the **sign-in form only when their Gitea session has lapsed**, which
|
||||||
|
"Remember This Device" pushes out to weeks. This is a first-impression fix.
|
||||||
|
|
||||||
|
Unlike Decap, Gitea supports this properly: a theme is a CSS file in a directory
|
||||||
|
it already reads.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/vienalatina
|
||||||
|
git pull --no-rebase --no-edit gitea main
|
||||||
|
bash scripts/gitea-theme.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add the line the script prints to `/srv/gitea/docker-compose.yml` (it is
|
||||||
|
already in `infra/gitea/docker-compose.yml`):
|
||||||
|
|
||||||
|
```
|
||||||
|
- GITEA__ui__DEFAULT_THEME=vienalatina
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd /srv/gitea && sudo docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Check it in a private window at **https://git.vienalatina.com/user/login** —
|
||||||
|
cream background, the Viena Latina wordmark, `#c0391c` buttons. Then browse a
|
||||||
|
repository and open a commit: a theme that only looks right on the login page is
|
||||||
|
half done.
|
||||||
|
|
||||||
|
### Re-run it after every Gitea upgrade
|
||||||
|
|
||||||
|
The theme is Gitea's own light theme with our colours appended, and the base is
|
||||||
|
read out of the running container so it matches the installed version. A new
|
||||||
|
Gitea release can introduce variables our overrides do not mention, and a base
|
||||||
|
frozen in the repository would drift out of date in ways nobody notices until a
|
||||||
|
page looks wrong.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash scripts/gitea-theme.sh
|
||||||
|
cd /srv/gitea && sudo docker compose restart gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing breaks if you forget — an unknown variable is a declaration nobody
|
||||||
|
reads, so the worst case is a corner that stays grey.
|
||||||
|
|
||||||
|
### Signing out is two steps, and the app says so
|
||||||
|
|
||||||
|
Clicking **Salir** in the members area closes that session and deletes the
|
||||||
|
stored Gitea token. It cannot close the **Gitea** session in the same browser,
|
||||||
|
and Gitea remembers that the app was authorised — so without saying anything,
|
||||||
|
the next click on *Entrar con Gitea* would sign the person straight back in with
|
||||||
|
no password. On a laptop shared around the association, that is a button that
|
||||||
|
lies.
|
||||||
|
|
||||||
|
Gitea cannot be signed out from another site: its logout has been POST-only
|
||||||
|
since 1.11.2, so a link cannot trigger it and a cross-site POST would need
|
||||||
|
Gitea's CSRF token. The `prompt=login` parameter that would force
|
||||||
|
re-authentication is undocumented in every released version of Gitea's OAuth2
|
||||||
|
provider, and a security control should not rest on that.
|
||||||
|
|
||||||
|
So the logout page says plainly what is and is not closed, and offers the link
|
||||||
|
that finishes the job. On a shared computer, use it — or close the browser,
|
||||||
|
which also works. The members-area cookie is already a browser-session cookie,
|
||||||
|
so it does not survive that either way.
|
||||||
|
|||||||
24
infra/gitea/assets/logo.svg
Normal file
24
infra/gitea/assets/logo.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!-- Viena Latina wordmark, for Gitea's header and favicon.
|
||||||
|
|
||||||
|
The site has no logo image: its brand is the words "Viena Latina" set in
|
||||||
|
Montserrat 700 at #c0391c
|
||||||
|
(themes/vienalatina/layouts/partials/header.html).
|
||||||
|
|
||||||
|
Set as live text with the same font stack as main.css, rather than traced
|
||||||
|
into paths. That looks like a shortcut and is actually the faithful
|
||||||
|
choice: the site loads no webfont at all — deliberately, so no request
|
||||||
|
leaves the server (GDPR) — so visitors already see this wordmark in
|
||||||
|
whatever sans-serif their machine substitutes. Paths would render a
|
||||||
|
Montserrat that most people never see on the site itself.
|
||||||
|
|
||||||
|
Stacked on two lines because this is also the favicon, where a wide
|
||||||
|
wordmark shrinks to an unreadable smear. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 64" role="img"
|
||||||
|
aria-label="Viena Latina" width="160" height="64">
|
||||||
|
<title>Viena Latina</title>
|
||||||
|
<g fill="#c0391c" font-family="Montserrat, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
|
||||||
|
font-weight="700" font-size="27" letter-spacing="-0.4">
|
||||||
|
<text x="4" y="27">Viena</text>
|
||||||
|
<text x="4" y="56">Latina</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -23,6 +23,13 @@ services:
|
|||||||
- GITEA__cors__ALLOW_DOMAIN=https://vienalatina.com
|
- GITEA__cors__ALLOW_DOMAIN=https://vienalatina.com
|
||||||
- GITEA__cors__METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
|
- GITEA__cors__METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
|
||||||
- GITEA__cors__HEADERS=Content-Type,User-Agent,Authorization
|
- GITEA__cors__HEADERS=Content-Type,User-Agent,Authorization
|
||||||
|
|
||||||
|
# Members reach /comunidad/ through Gitea's sign-in and authorize
|
||||||
|
# screens, so those are part of the site's journey even for people
|
||||||
|
# who never open a repository. DEFAULT_THEME is what anonymous
|
||||||
|
# visitors get, which is exactly those two pages.
|
||||||
|
# Install the theme first: bash scripts/gitea-theme.sh
|
||||||
|
- GITEA__ui__DEFAULT_THEME=vienalatina
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- /etc/timezone:/etc/timezone:ro
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
|||||||
98
infra/gitea/theme-vienalatina.overrides.css
Normal file
98
infra/gitea/theme-vienalatina.overrides.css
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/* Viena Latina colours for Gitea.
|
||||||
|
*
|
||||||
|
* Deltas only. scripts/gitea-theme.sh concatenates Gitea's own light theme —
|
||||||
|
* read out of the running container, so it matches the installed version — and
|
||||||
|
* then this file. Shipping the full theme instead would mean a copy of
|
||||||
|
* somebody else's stylesheet going stale in our repository, and an upgrade
|
||||||
|
* turning into a merge.
|
||||||
|
*
|
||||||
|
* Why this exists: members sign in to /comunidad/ through Gitea, so Gitea's
|
||||||
|
* sign-in form and authorize dialog are part of the journey whether or not
|
||||||
|
* anyone ever browses a repository. Unthemed, they are two dark screens in the
|
||||||
|
* middle of a cream-coloured site, and the platform stops feeling like one
|
||||||
|
* thing.
|
||||||
|
*
|
||||||
|
* The values come from themes/vienalatina/assets/css/main.css. If the brand
|
||||||
|
* colours change there, change them here too — Hugo fingerprints its CSS and
|
||||||
|
* Gitea serves this as a static file, so there is no way to share one source.
|
||||||
|
*
|
||||||
|
* Safe failure mode: anything Gitea renames is simply a declaration nobody
|
||||||
|
* reads, and that part keeps the light theme's value. The result of getting a
|
||||||
|
* variable name wrong is a corner that stays grey, not a broken page.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Brand — #c0391c, with the darker and lighter steps Gitea expects for
|
||||||
|
hover, active and focus states. */
|
||||||
|
--color-primary: #c0391c;
|
||||||
|
--color-primary-contrast: #ffffff;
|
||||||
|
--color-primary-dark-1: #ad331a;
|
||||||
|
--color-primary-dark-2: #9a2a0f;
|
||||||
|
--color-primary-dark-3: #86240d;
|
||||||
|
--color-primary-dark-4: #731f0b;
|
||||||
|
--color-primary-dark-5: #5f1a09;
|
||||||
|
--color-primary-dark-6: #4c1507;
|
||||||
|
--color-primary-dark-7: #380f05;
|
||||||
|
--color-primary-light-1: #cd5137;
|
||||||
|
--color-primary-light-2: #d66a53;
|
||||||
|
--color-primary-light-3: #de836f;
|
||||||
|
--color-primary-light-4: #e69c8b;
|
||||||
|
--color-primary-light-5: #eeb5a7;
|
||||||
|
--color-primary-light-6: #f6cec3;
|
||||||
|
--color-primary-light-7: #fbe2db;
|
||||||
|
--color-primary-alpha-10: #c0391c1a;
|
||||||
|
--color-primary-alpha-20: #c0391c33;
|
||||||
|
--color-primary-alpha-30: #c0391c4d;
|
||||||
|
--color-primary-alpha-40: #c0391c66;
|
||||||
|
--color-primary-alpha-50: #c0391c80;
|
||||||
|
--color-primary-alpha-60: #c0391c99;
|
||||||
|
--color-primary-alpha-70: #c0391cb3;
|
||||||
|
--color-primary-alpha-80: #c0391ccc;
|
||||||
|
--color-primary-alpha-90: #c0391ce6;
|
||||||
|
--color-primary-hover: #ad331a;
|
||||||
|
--color-primary-active: #9a2a0f;
|
||||||
|
|
||||||
|
/* Page and panels — the site's cream background and white cards. */
|
||||||
|
--color-body: #f8f3ef;
|
||||||
|
--color-box-body: #ffffff;
|
||||||
|
--color-box-body-highlight: #fdf1ed;
|
||||||
|
--color-box-header: #f2ebe6;
|
||||||
|
--color-secondary-bg: #f2ebe6;
|
||||||
|
--color-menu: #ffffff;
|
||||||
|
--color-card: #ffffff;
|
||||||
|
--color-markup-code-block: #f2ebe6;
|
||||||
|
|
||||||
|
/* Type — the same near-black and muted brown the site uses. */
|
||||||
|
--color-text: #1a0d09;
|
||||||
|
--color-text-dark: #1a0d09;
|
||||||
|
--color-text-light: #5c3d37;
|
||||||
|
--color-text-light-1: #6b4a43;
|
||||||
|
--color-text-light-2: #7d5a52;
|
||||||
|
--color-text-light-3: #9a7870;
|
||||||
|
|
||||||
|
/* Borders — warm rather than the default neutral grey, which is most of
|
||||||
|
what makes an unthemed Gitea read as "a different website". */
|
||||||
|
--color-secondary: #e0cec8;
|
||||||
|
--color-secondary-dark-1: #d3bdb6;
|
||||||
|
--color-secondary-light-1: #ebdcd7;
|
||||||
|
--color-light-border: #e0cec8;
|
||||||
|
--color-input-border: #c2a89f;
|
||||||
|
--color-input-background: #ffffff;
|
||||||
|
--color-input-text: #1a0d09;
|
||||||
|
|
||||||
|
/* The top bar, which is the first thing anyone sees. */
|
||||||
|
--color-nav-bg: #ffffff;
|
||||||
|
--color-nav-hover-bg: #fdf1ed;
|
||||||
|
--color-nav-text: #1a0d09;
|
||||||
|
--color-header-wrapper: #ffffff;
|
||||||
|
--color-footer: #f8f3ef;
|
||||||
|
|
||||||
|
--color-accent: #c0391c;
|
||||||
|
--color-small-accent: #fbe2db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The wordmark is wide, unlike Gitea's square cup, so it needs room. */
|
||||||
|
.logo {
|
||||||
|
height: 26px !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
71
scripts/gitea-theme.sh
Executable file
71
scripts/gitea-theme.sh
Executable file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install (or reinstall) the Viena Latina theme for Gitea.
|
||||||
|
#
|
||||||
|
# Members sign in to /comunidad/ through Gitea, so Gitea's sign-in form and
|
||||||
|
# authorize dialog are part of the journey whether or not anyone ever browses a
|
||||||
|
# repository. This makes those two screens look like the rest of the platform.
|
||||||
|
#
|
||||||
|
# bash scripts/gitea-theme.sh
|
||||||
|
#
|
||||||
|
# Run it again after every Gitea upgrade. The theme is built by concatenating
|
||||||
|
# Gitea's own light theme with our overrides, so the base has to come from the
|
||||||
|
# version that is actually installed — a new release can add variables, and a
|
||||||
|
# copy frozen in this repository would slowly drift out of date in ways nobody
|
||||||
|
# would notice until a page looked wrong.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_DIR="${GITEA_DIR:-/srv/gitea}"
|
||||||
|
REPO_DIR="${REPO_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
SERVICE="${SERVICE:-gitea}"
|
||||||
|
|
||||||
|
OVERRIDES="$REPO_DIR/infra/gitea/theme-vienalatina.overrides.css"
|
||||||
|
LOGO="$REPO_DIR/infra/gitea/assets/logo.svg"
|
||||||
|
|
||||||
|
CUSTOM="$GITEA_DIR/data/gitea" # GITEA_CUSTOM inside the container is /data/gitea
|
||||||
|
CSS_DIR="$CUSTOM/public/assets/css"
|
||||||
|
IMG_DIR="$CUSTOM/public/assets/img"
|
||||||
|
|
||||||
|
for file in "$OVERRIDES" "$LOGO"; do
|
||||||
|
[ -f "$file" ] || { echo "Missing $file" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "== reading the installed Gitea's light theme"
|
||||||
|
cd "$GITEA_DIR"
|
||||||
|
BASE="$(docker compose exec -T "$SERVICE" \
|
||||||
|
cat /app/gitea/public/assets/css/theme-gitea-light.css)"
|
||||||
|
|
||||||
|
if [ -z "$BASE" ]; then
|
||||||
|
echo "Could not read Gitea's own theme — is the container running?" >&2
|
||||||
|
echo "Check with: cd $GITEA_DIR && docker compose ps" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo mkdir -p "$CSS_DIR" "$IMG_DIR"
|
||||||
|
|
||||||
|
echo "== writing theme-vienalatina.css"
|
||||||
|
{
|
||||||
|
printf '/* Built by scripts/gitea-theme.sh on %s.\n' "$(date -u +%FT%TZ)"
|
||||||
|
printf ' Gitea theme-gitea-light.css + infra/gitea/theme-vienalatina.overrides.css\n'
|
||||||
|
printf ' Do not edit here: rerun the script. */\n'
|
||||||
|
printf '%s\n' "$BASE"
|
||||||
|
cat "$OVERRIDES"
|
||||||
|
} | sudo tee "$CSS_DIR/theme-vienalatina.css" > /dev/null
|
||||||
|
|
||||||
|
echo "== writing the logo and favicon"
|
||||||
|
# logo.svg is the header mark, favicon.svg the tab icon. Gitea also looks for
|
||||||
|
# PNG fallbacks; without them it falls back to its own, which is why the tab
|
||||||
|
# can still show a cup in older browsers. Converting needs a tool this box does
|
||||||
|
# not have, and an SVG favicon covers everything current.
|
||||||
|
sudo cp "$LOGO" "$IMG_DIR/logo.svg"
|
||||||
|
sudo cp "$LOGO" "$IMG_DIR/favicon.svg"
|
||||||
|
|
||||||
|
sudo chown -R 1000:1000 "$CUSTOM/public"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Installed. Now make it the default (once):"
|
||||||
|
echo " add GITEA__ui__DEFAULT_THEME=vienalatina to $GITEA_DIR/docker-compose.yml"
|
||||||
|
echo " then cd $GITEA_DIR && sudo docker compose up -d"
|
||||||
|
echo
|
||||||
|
echo "Already set? A restart is enough to pick up the new file:"
|
||||||
|
echo " cd $GITEA_DIR && sudo docker compose restart $SERVICE"
|
||||||
Loading…
Reference in New Issue
Block a user