Skip to content

v1.4.0-dev05

Pre-release
Pre-release

Choose a tag to compare

@thebigjoe1 thebigjoe1 released this 09 Oct 22:13
· 13 commits to main since this release

Fifth dev pre-release of the 1.4.0 minor

Android sync rebuilt so accounts, offline edits and deletes stay right, programs that stay with the account that made them, SSO fixes, and security updates, a critical one in the Android app among them.

Changed

  • A program your coach assigned opens read-only. Its workouts can be followed but not changed; your coach changes them.
  • Trace's attach button offers Camera or Gallery on phones and in the Android app, the same as the other Trace apps. Each choice goes straight to the camera or the photo picker; on a computer the button opens the file picker. You can still pick several photos at once.

Fixed

  • A program deleted on the web disappears from the Android app too (#139, reported by @surfingbytes). The app kept showing deleted programs, the starter ones included, because its sync never heard of a deletion. Programs deleted before this update leave the phone on its next sync, with no app update needed. The same goes for a deleted custom exercise, and for workouts, body stats and a Trace chat cleared on the web. The starter programs also no longer come back after a server restart once you've deleted them all.
  • The Android app's local mode can import the exercise library from Settings (#133, reported by @daniel-bernardino747). Settings, Exercise Catalog was hidden there, so a library skipped or failed in the setup wizard could only come back by wiping the app. Switching a source off now hides it on the phone too, Clear and Delete all say how many, the JSON template downloads on Android, and an empty library's Go to Settings button opens the catalog.
  • The installed app works when LiftTrace is served from a subpath (#129, by @kgenerozov). With BASE_URL set (say /lifttrace), every visit after the first sent the app's requests to the site root instead, online and offline.
  • The Body Measurements chart puts each reading on its own day (#131, reported by @Scorch-Light). Body fat and the other overlays drifted off their dates, and with none picked the chart had no key and no numbers. The chart now runs by date, so a long break between weigh-ins shows as one. Dots on every line chart are round again.
  • Signing in through SSO with an email that already has an account no longer creates a second account. When the identity provider doesn't mark the email verified (Authentik's default since 2025.10), the sign-in is refused with a pointer to link the provider from your profile, instead of landing you in a new, empty account.
  • SSO works with Authelia 4.39 and later out of the box. Email, username and groups are read from the provider's userinfo when the ID token leaves them out.
  • The SSO callback also works without the provider number, and at /api/oidc/callback, the address older docs gave. Both used to end on a blank page.
  • OIDC_ENABLE_EMAIL_PASSWORD_LOGIN works for providers added in Settings. It was ignored unless a provider was also defined through env vars.
  • Signing in on a plain-HTTP address says what's wrong instead of looping. The sign-in cookie only works over HTTPS unless INSECURE_COOKIES=1 is set, so signing in from an http:// address dropped you back on the login page with no error. The sign-in and setup screens now explain it and link to the fix, the app no longer flashes before sending you back, and the server log says so too.
  • The app no longer loads behind the sign-in screen. Opened signed out, it asked the server for your data and was refused before the sign-in screen replaced it. It now waits to learn who is signed in.
  • Trace set up through the server's environment variables shows as set up in the Android app. The app asked without its sign-in, so the server refused and Trace looked unconfigured.
  • Trace works with OpenAI-compatible endpoints that stream unless told not to (nutritrace#258, reported by @jsapede). Chat answers failed with "Unexpected non-whitespace character after JSON"; every request now asks for a single answer.
  • Trace's tools work with OpenAI-compatible endpoints that check every field (nutritrace#259, reported by @jsapede). With AI set up on the server, a chat that used a tool could fail with "name" is not supported by this endpoint.
  • Programs, program workouts, exercises and cardio made offline in the Android app reach the server as made. A program workout added to a program made offline, or a logged workout using an exercise made offline, could arrive pointing at another account's, and a program made on the web meanwhile could stay missing from the phone. This also holds for changes made right after reconnecting, before the app had synced, and for anything made while the answer was lost on the way back, online or offline, which used to be made twice (on the phone too, when it was edited before the answer came). One deleted on the web in between isn't made again. Programs, program workouts and exercises an earlier version made offline are no longer duplicated after updating, and an edit queued by it never goes to another row with the same name.
  • An offline edit in the Android app no longer stops later changes from reaching it. A program, program workout, exercise or body stat edited offline ignored every later change made on the web.
  • Settings reset on the web, and programs a coach unassigns, leave the Android app too. So do other accounts' program workouts that older versions sent to every phone, and what's deleted on the server reaches a phone that pulls everything again.
  • What only the Android app has is never written over or deleted by a sync: programs, program workouts, exercises, logged workouts and body stats made before it was connected, kept after Disconnect, or left by an upload that failed. Body stats on a day the server also has are merged. Changing one of these rows sends it to the server first. A change made offline also survives the row being deleted on the web before it goes up.
  • The Android app says when the server refuses a change made offline, and shows the server's version again. It used to drop the change without a word and keep showing it. Deleting something the web already deleted is done quietly.
  • A sync in the Android app waits for the one before it, and a request that never answers no longer holds it up, along with sign-in. A program you started offline no longer flips back. The first sync of a long history finishes on a slow connection: the server compresses it, and a sync no longer starts a second download beside one still running.
  • Signing in to the Android app as another account keeps each account's data apart. The first account's data stayed on screen with only the second's recent changes on top, a sync still running landed in the second account's data, and changes it made offline were sent as the second account's. Settings the app held for the first account (notification tokens, the radio password, favorites, rest times) carried over and could be saved as the second's. Now the phone shows the second account's data, fetched fresh. If the first account left changes that haven't gone up, you're asked first: cancel to keep them for that account, or sign in anyway and they're discarded. The same account at another address of the same server is still the same account; when a server can't say whether it's the same one (an older server, or one that doesn't answer), you're asked, instead of the phone's data going to the same account number on another server. Data kept by an earlier version belongs to the account last signed in. If the app can't tell whose data the phone holds, it shows none of it and offers Retry or Sign Out. Signing out saves a setting changed just before, ends the session (the app went on syncing that account in the background, and opened it again on the next launch), and clears the account's settings from the app; reminders on the phone follow the account signed in.
  • An edit made offline no longer overwrites a later one made elsewhere, in the Android app and the web app. A program renamed offline replaced a rename made on the web a minute later, once it went up. Now the newer edit of each field stays, for programs, program workouts, exercises, workouts (their exercises and sets still merge one by one), body stats and settings, and the app shows what stayed. A delete made offline doesn't remove something changed elsewhere after it, a program whose workout was edited after it included: that stays, comes back, and the refused delete is reported.
  • Signing out of the Android app with no connection goes straight to the sign-in screen, also when the app is opened again offline. It showed a blank screen, then the account's data, and changes still waiting (and the sign-out itself) went up later with no one signed in. They now wait for that account's next sign-in.
  • The first-run welcome screen no longer comes back after Disconnect.
  • A workout your coach prescribes opens in the Android app, online or offline, even from a program you don't follow, and logging it keeps its program and week.
  • The Android app shows your active program for every account, not only the first one made on the server. On a server without user accounts, the program you follow now syncs both ways.
  • A program or exercise made offline in the Android app is yours right away. For any account but the server's first, a new program showed as someone else's, without Rename, Delete or editing its workouts, until it synced. Rest times, load types and favorites set for a new exercise offline stay with it.
  • On a server without user accounts, your programs offer Rename, Duration and Delete. Only the starter programs stay read-only.
  • On a server without user accounts, Delete all custom exercises works. It was refused as "auth required" on the web, and in the Android app, done offline, it signed the app out and held every change made after it.
  • Connecting the Android app to a server: Upload sends each logged workout as its own session, so two on one day stay two and a workout already on the server keeps its own name, notes and time. Body stats now go up (they never did), plans keep their length, the program being followed stays started, uploaded workouts and settings keep their exercises and programs instead of pointing at the server's rows with the same number, and the phone ends up with one copy of each. Uploading again sends nothing twice, and the counts shown before an Upload are what it sends. Upload now also sends cardio; progress photos and Trace chat aren't uploaded. A program workout that fails to upload goes up with the next sync. Replace with Server, and Download Server to Phone in Settings, now delete the phone's data as they say; changes that never reached a server are asked about first, never dropped silently. Disconnect sends changes still waiting first, and asks before dropping any it can't send, instead of leaving them to go up later under the next account. Everything else the account had on the phone stays: an Upload to another server sends all of it, and connecting back to the same account puts it back in place, with what changed meanwhile sent as changes and only what's new created (if the server can't say whether it's the one you disconnected from, you're asked). Disconnect is all or nothing (stopped part way, the phone stays connected with its data as it was), takes moments on a big history, and no sync runs while it happens.
  • Trace shows your newest messages when you open it, on the web and in the Android app, also offline. It loaded the oldest ones, so once a chat passed 100 messages the latest never appeared.
  • Exercise pictures and progress photos sent inline to the sync API are stored as files. The server kept them inline, and a progress photo sent that way didn't open. Pictures already stored inline are converted when the server starts.

Security

  • The server reads the session from a request's sign-in header before its cookie, when the header carries one of its own sessions. On Android a cookie left from an earlier sign-in went with every request, and answered it as that account: after one account signed out and another signed in, the phone showed and synced the first account's programs, workouts and exercises. The Android app also deletes that cookie whenever the account changes, leaving other cookies (a sign-in gate's) alone. A reverse proxy's own Authorization header still leaves web sign-in to the cookie.
  • A logged workout only points at a program, program workout and exercises its account has access to. Any account could save one naming another account's program and read that program's length back.
  • Programs and their workouts stay with the account that made them. Any signed-in account could open, rename or delete someone else's program and add, change or delete its workouts, and every phone received every account's workouts. Now only the maker changes a program; athletes see the ones assigned to them, and a workout a coach prescribes to them.
  • A radio link can't run script as LiftTrace. The radio proxy passed a station's content through as-is, so a link to a web page instead of a stream ran that page as the app for whoever opened it. It now passes audio only, and the music server proxy can't serve a page either.
  • A password reset link can no longer be pointed at someone else's site. The link took its address from the request, so anyone could ask for another person's reset with a forged host and have the real email, token included, send them to it. Reset, invite and coach feedback emails now link to PUBLIC_URL when it's set, or to an address an admin uses. Links also keep the BASE_URL subpath now.
  • Names in emails can no longer carry markup. A name, or a title someone shared, went into the email as-is, so HTML or a link typed into it became real markup in the recipient's inbox. Affected the SMTP test, invite and password reset emails, and the reps of a PR in the weekly summary. Everything an email shows is escaped now.
  • The radio proxy requires signing in. Anyone who could reach the server could use it, with a station address that redirected inside your network, to read pages there.
  • Station addresses are checked on every redirect. An admin can now play stations on your own network without ALLOW_PRIVATE_RADIO_URLS; other accounts, and a single-user install (which has no sign-in), still need it set.
  • The music server proxy forwards only the Subsonic, Jellyfin, Plex and Emby requests the app makes, and passes back only music data (JSON, XML, audio, images), never a web page. It follows no redirect to another server and no longer sends this app's sign-in token on to the music server. It used to pass any path through to the address an account saved.
  • Push notifications and the NutriTrace link never reach cloud-metadata addresses and follow a redirect only on the same server. A failed push test or link check no longer shows the other server's raw reply, and the open /api/proxy checks every redirect.
  • fast-uri bumped 3.1.7 to 3.1.8, closes GHSA-hrr3-gc8f-f4qj (moderate: inconsistent host normalization). It comes in through the MCP library's schema checks.
  • ip-address bumped 10.7.0 to 10.7.2, closes GHSA-j6r3-76f7-8jcv and GHSA-h3mg-xc3c-68pw (moderate: a subnet check across address families, and a slow parse of a long address). It comes in through the MCP library, which LiftTrace doesn't use it for.
  • brace-expansion bumped 5.0.9 to 5.0.12, closes GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7 and GHSA-6j4f-fj2g-mc7p (high: slow or crashing expansion of crafted patterns). Build tooling only; nothing in the app uses it.
  • @capacitor/android, @capacitor/ios and @capacitor/core bumped 8.3.4 to 8.5.3, closes GHSA-rvm3-566m-v7fv (critical: a link to the app's internal HTTP proxy path loads any remote page at the app's own origin, with its storage and native plugins). The Android app was exposed: it renders links in Trace replies, so a crafted link tapped there could have used it. Update the Android app.
  • source-map-js bumped 1.2.1 to 1.2.2, closes GHSA-68fv-2mgg-jv7q (high: a crafted indexed source map stalls the process). Build tooling only; LiftTrace never parses a source map it didn't build.
  • @modelcontextprotocol/sdk bumped 1.30.0 to 1.32.1, closes GHSA-6qxp-vccf-f47h (high: the OAuth client could send credentials to an authorization server the MCP server picks). Not exposed: LiftTrace only runs an MCP server, never the client.
  • proxy-addr bumped 2.0.7 to 2.0.8, closes GHSA-jqcg-44mw-7w3h (critical: a client can spoof its IP through an IPv4-mapped IPv6 trusted subnet). Not exposed: the server doesn't set trust proxy, so Express never reads forwarded addresses.
  • The Android app's SSO sign-in no longer passes the session token through the lifttrace:// link, which another app could intercept. The link carries a single-use code that only the app that started the sign-in can redeem.
  • Uploaded files get unguessable names. The random part of the name was made with Math.random(), which can be predicted from its own output.
  • npm audit reports 0 vulnerabilities for the app and the server.

LiftTrace is free and always will be. Support on Ko-fi goes toward a Mac and an iPhone so my apps can come to Apple devices, and GitHub Sponsors helps with the monthly costs.