chore(release): v1.9.34-alpha1
Some checks are pending
CI / Unit tests (node --test) (push) Waiting to run
CI / OpenAPI spec lint (push) Waiting to run
CI / Android unit tests (Kotlin schedule evaluator vectors) (push) Waiting to run
CI / Boot smoke + version check (push) Waiting to run

This commit is contained in:
ScreenTinker 2026-08-11 18:03:30 -05:00
parent 78a403d35c
commit 6830fe58ea
7 changed files with 87 additions and 8 deletions

View file

@ -1,5 +1,84 @@
# Changelog
## 1.9.34-alpha1
**A prerelease, for the alpha instance.** It is not a production build and no display should be
pulled onto it except deliberately: a prerelease sorts *below* its own release in semver, so a player
that takes `1.9.34-alpha1` without being opted in will see `1.9.33` as newer and roll itself back.
Opting a display in is what stops that.
The headline is single sign-on, rebuilt from nothing — and the reason it was rebuilt rather than
extended is a vulnerability in what was there before.
### Fixed — the old sign-in path could be replayed by any site you had signed into
What shipped as "OAuth" verified almost nothing. The Google path asked `tokeninfo` whether an
**access** token was valid and then trusted the email address in the reply. The Microsoft path handed
a bearer token to Graph `/me` and trusted that. Neither asked the only question that matters: *who
was this token issued for?*
So any other site a user had signed into — anything that had requested `email` or `User.Read` — held
a token it could replay against ScreenTinker and receive a session as that user. No password, no
interaction from the victim.
Identity now comes from an **ID token only**, with signature checked against the provider's JWKS and
`iss`, `aud`, `azp`, `exp` and `nonce` all verified. One flow for every provider: Authorization Code
with PKCE, completed server-side. Google and Microsoft became ordinary entries rather than special
cases, which is what removed the two hand-written paths that were wrong.
### Added — organizations bring their own identity provider
Instance-wide providers stay the default and are now unlimited in number. On top of that an
organization may configure its own provider, but only for domains it has **proved it controls** — a
TXT record at `_screentinker-verify.<domain>`. An unverified claim lapses after eight hours and
releases the domain, so a typo cannot park someone else's domain indefinitely.
Proof by delegated name (CNAME) is refused outright. It would have required a wildcard zone we do not
operate, and worse, it would turn a subdomain takeover into an apex takeover.
**SSO-only** is available per organization: passwords refused, other providers refused, the instance
Google button refused. Turning it *off* again needs a platform admin to approve the request, so one
compromised org admin cannot quietly reopen password login. Break-glass for a platform admin is the
correct password and nothing else — and a wrong password returns the same 403 everyone else gets, so
it cannot be used to discover whether an account exists.
With no SSO environment variables set, the product behaves exactly as it did before. That was
verified in a browser, not merely reasoned about.
### Added — organizations may re-enable same-origin widgets, deliberately
Widget isolation removed `allow-same-origin`, which also broke embedding for sites that enforce strict
CORS. There is now an org-level switch to put it back, behind a modal that requires the operator to
type an acknowledgement, with a persistent banner while it is on. Enabling it needs an organization
owner or admin — a workspace admin is deliberately not enough — and the change is written to the
activity log. Contributed by @ChrisChrome.
The **widget editor's Preview is excluded** from that switch. Preview renders inside the dashboard,
where the admin's session token lives, so honouring the setting there would have let anyone who can
author a widget lift the session of whichever admin clicked Preview. The setting exists so *displays*
can embed origin-strict sites; a display holds a device token, an admin's browser does not.
### Fixed — RSS tickers ran at a speed that depended on how much news there was
`scroll_speed` was wired straight into `animation-duration`, so it set a fixed total time for the
whole strip to cross the screen regardless of length. A feed with twenty items was dragged past in
the same seconds as a feed with one — too fast to read, and it appeared to jump back to the start.
It now calibrates a constant pixels-per-second rate, so more items simply take proportionally longer
and every item scrolls fully into and out of view. Contributed by @ChrisChrome.
### Fixed — user-controlled text is escaped where it actually reaches HTML
An audit pass over the frontend's HTML sinks, escaping the ones that receive user-controlled data.
Also in this release: dashboard banners no longer overlap the sidebar, shift the layout or vanish
when switching views, and the main content no longer collapses to a narrow column.
### Known limitations in this alpha
Deliberately not resolved yet, and worth knowing before testing against them:
- Enabling SSO-only **clears the passwords** of members at verified domains. That is irreversible
without a reset.
- The SSO-only removal queue is an availability dependency on the operator: if nobody approves, the
organization stays SSO-only.
- `landing.html` still interpolates plan names into HTML without escaping. Those values come from
the plans table rather than from end users, so it is a loose end rather than an exposure.
- `/api/provision` is limited to 5/min, so a twenty-display install day takes four minutes of waiting.
Pre-existing, unchanged by this release.
## 1.9.33
A patch off 1.9.32. The headline is a boot-time crash that could brick a display permanently — a

View file

@ -1 +1 @@
1.9.33
1.9.34-alpha1

View file

@ -13,8 +13,8 @@ android {
targetSdk = 34
// Env-overridable so device-owner reinstalls (which require an ever-increasing
// versionCode — downgrades are blocked) don't churn this file each build.
versionCode = (System.getenv("VERSION_CODE") ?: findProperty("VERSION_CODE") as String? ?: "106").toInt()
versionName = System.getenv("VERSION_NAME") ?: findProperty("VERSION_NAME") as String? ?: "1.9.33"
versionCode = (System.getenv("VERSION_CODE") ?: findProperty("VERSION_CODE") as String? ?: "107").toInt()
versionName = System.getenv("VERSION_NAME") ?: findProperty("VERSION_NAME") as String? ?: "1.9.34-alpha1"
}
signingConfigs {

View file

@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: ScreenTinker Public API
version: 1.9.33
version: 1.9.34
description: |
Public, token-scoped REST API for ScreenTinker digital signage.

View file

@ -1,12 +1,12 @@
{
"name": "screentinker",
"version": "1.9.33",
"version": "1.9.34-alpha1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "screentinker",
"version": "1.9.33",
"version": "1.9.34-alpha1",
"dependencies": {
"@azure/msal-node": "^5.2.1",
"archiver": "^7.0.1",

View file

@ -1,6 +1,6 @@
{
"name": "screentinker",
"version": "1.9.33",
"version": "1.9.34-alpha1",
"description": "ScreenTinker - Digital Signage Management Server",
"main": "server.js",
"scripts": {

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
id="http://screentinker.com/player" version="1.9.33" viewmodes="maximized">
id="http://screentinker.com/player" version="1.9.34" viewmodes="maximized">
<tizen:application id="ScrnTinkr1.ScreenTinker" package="ScrnTinkr1" required_version="2.4"/>
<tizen:profile name="tv"/>
<name>ScreenTinker</name>