Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ with Quadlet (systemd-managed containers).
- [API observability](#api-observability)
- [Logs](#logs)
- [Redis counters](#redis-counters)
- [Account observability](#account-observability)
- [Finding duplicate accounts by IP](#finding-duplicate-accounts-by-ip)
- [Useful commands](#useful-commands)
- [File locations on the droplet](#file-locations-on-the-droplet)

Expand Down Expand Up @@ -1023,6 +1025,38 @@ flipped back to `is_active=True` in Django admin after review.

---

## Account observability

Signup and account-activation views log the username and client IP (see
`_client_ip` in `users/views.py`), in the same `(user=..., ip=...)` format used
by the `authenticated via` API auth logs (see `api/authentication.py`). Because
the format is shared, all three log lines can be mined together to spot one IP
behind multiple usernames.

### Finding duplicate accounts by IP

```bash
# IPs that signed up for more than one account
journalctl _SYSTEMD_USER_UNIT=metron-web.service | grep "signed up for an account" \
| grep -oP 'ip=\K\S+' | sort | uniq -c | sort -rn | awk '$1 > 1'

# IPs seen under more than one username, across signups, activations, and
# authenticated API requests
journalctl _SYSTEMD_USER_UNIT=metron-web.service \
| grep -E "signed up for an account|activated their account|authenticated via" \
| grep -oP '\(user=[^,]+, ip=[^)]+\)' \
| sed -E 's/\(user=([^,]+), ip=([^)]+)\)/\2 \1/' \
| sort -u \
| awk '{ users[$1] = users[$1]" "$2 } END { for (ip in users) if (split(users[ip], a, " ") > 1) print ip":"users[ip] }'

# All signup/activation/API-auth log lines for a specific IP
journalctl _SYSTEMD_USER_UNIT=metron-web.service \
| grep -E "signed up for an account|activated their account|authenticated via" \
| grep "ip=<ip-address>"
```

---

## Useful commands

```bash
Expand Down
16 changes: 14 additions & 2 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,13 @@ def activate(request, uidb64, token):
login(request, user)
# Send pushover notification tha user activated account
send_pushover(f"{user} activated their account on Metron.")
logger.info("%s activated their account on Metron", user)
ip = _client_ip(request)
logger.info(
"User activated their account on Metron (user=%s, ip=%s)",
user.username,
ip,
extra={"username": user.username, "ip": ip},
)
# Add a message asking the user to star the repository.
msg = mark_safe(
"If you are planning on adding new information to the database, please refer to the "
Expand Down Expand Up @@ -173,7 +179,13 @@ def signup(request): # sourcery skip: extract-method
email.send()
# Let's send a pushover notice that a user requested an account.
send_pushover(f"{user} signed up for an account on Metron.")
logger.info("%s signed up for an account on Metron", user)
ip = _client_ip(request)
logger.info(
"User signed up for an account on Metron (user=%s, ip=%s)",
user.username,
ip,
extra={"username": user.username, "ip": ip},
)

return redirect("account_activation_sent")
else:
Expand Down
Loading