Files

17 KiB
Raw Permalink Blame History

Synology SSO and local recovery

First setup on this workstation

  1. Start the local stack and apply migrations. The existing ignored .env already contains random TOKEN_ENCRYPTION_KEY and LOCAL_ADMIN_SETUP_TOKEN values; preserve them.
  2. Open http://localhost:5173/local-admin. This URL is intentionally absent from normal navigation.
  3. Read LOCAL_ADMIN_SETUP_TOKEN locally from .env and enter it in the setup form. Choose your own username, display name and password (12–128 characters). No account or default password is shipped. Only one password-based account can be created; setup closes atomically after it exists. The setup token can then be removed from .env and the API restarted.
  4. Save the recovery credentials in your password manager. This account can configure SSO and open the app even when the identity provider is unavailable, provided the Model Delivery API is reachable.
  5. Complete the SSO form using your actual Synology values. Save, then use בדיקת ההגדרות השמורות to test discovery. This checks metadata connectivity and issuer matching, not the full interactive sign-in. Log out of the app and use its normal SSO button to verify the real login.

For a fresh installation elsewhere, generate a 32-byte random base64 encryption key and a separate long random setup token as described in .env.example. Preserve the encryption key with backups; losing it prevents decrypting saved SSO settings and provider tokens. No secret is returned by the settings API after saving.

Synology settings

In Synology SSO Server, enable OIDC and register an OIDC application for Model Delivery. Record its Application ID, Application Secret and exact Redirect URI. Synology's application documentation describes these fields. Obtain the Well-known URL from your installed SSO Server's OIDC service settings; copy the issuer value from that discovery document without altering its path or trailing slash.

Model Delivery setting Value to use
Issuer Exact issuer from your server's discovery document
Well-known URL Synology's full discovery URL; optional if discovery from the issuer works
Application ID / Secret Values from the OIDC application registered in Synology
Redirect URI http://localhost:5173/api/auth/callback for current local development; http://localhost:4173/api/auth/callback for built preview; your HTTPS application URL plus /api/auth/callback after deployment
Client authentication Match the provider's advertised method: client_secret_post or client_secret_basic; none is available for explicitly registered public clients
Scopes Always openid; Model Delivery adds groups to each authorization request. Add only other scopes supported by the installed server
Display-name claim Default name; configure username if preferred. Fallbacks include given name, preferred username, Synology's username, email and subject
Group claim Default groups, matching Synology's documented OIDC group claim
Allowed groups One exact Synology/AD group name per line; a user may belong to any one of them
Restrict by group Leave off until the allowlist is ready and a test SSO login has shown the expected group in החשבון שלי
offline_access Optional, off by default. Enable only if your Synology version/application supports refresh tokens and this scope
prompt=consent Optional explicit consent request if required by the provider

The server validates issuer, metadata endpoints and signatures using openid-client, with Authorization Code + PKCE S256, random state and nonce. Do not weaken verification to accommodate mismatched Synology URLs. Correct the server's externally advertised URLs and trusted certificate. This workstation's Synology sign-in and UserInfo lookup are now working; installed-version refresh-token support remains to be confirmed.

APP_ORIGINS is an allowlist in the server's .env, independent of the settings form. Callback origins must be in it. On deployment set the real HTTPS app origin, COOKIE_SECURE=true, and leave OIDC_ALLOW_HTTP=false. The latter is only for the loopback mock provider in automated tests, never a workaround for NAS certificates. Browser origins must stay consistent between app entry and callback.

Sessions and offline work

The browser receives an opaque HttpOnly SameSite=Strict session cookie. A separate short-lived SameSite=Lax cookie binds the cross-site callback to the browser that initiated sign-in. Pending state/nonce/PKCE verifier are encrypted in one-use server transactions with a ten-minute expiry. The callback is excluded from service-worker navigation fallback and request logs omit its query string.

Access and refresh tokens stay AES-256-GCM encrypted in MariaDB, using the server-only encryption key. App session identifiers are stored as hashes. OIDC users are identified by a hash of (issuer, subject), never linked to the recovery account by a matching username/email. Successful first SSO login provisions a staff profile only after group authorization succeeds; subsequent logins update the greeting and group snapshot from validated claims/UserInfo. Admitted users share the lab's operational data in this slice.

Synology group access

Synology's SSO Server documentation describes groups as the ID-token claim containing the groups to which the user belongs. Model Delivery always requests the groups scope and reads the configured claim from the validated ID token plus UserInfo response. In /local-admin, enter one group per line under קבוצות מורשות, then enable הגבלת הכניסה לפי קבוצות Synology / AD. A user is admitted when at least one returned group exactly matches an allowlist entry; comparison is Unicode-normalized and case-insensitive. Substrings and partial domain names do not match.

Keep the restriction disabled for the first diagnostic login. Open החשבון שלי after SSO login to see the group names actually returned by this Synology installation, copy the intended name into the allowlist, and then enable the restriction from the local recovery account. If Synology returns no group claim, group-restricted login is denied. The local recovery administrator is always exempt and can disable or repair the settings.

An unmatched user is rejected before a new app account or session is created. Saving changed SSO/group settings causes existing SSO sessions to fail their next online session check and require login. The server also checks each existing session against its last verified group snapshot. Provider-side membership changes with otherwise unchanged settings are learned during the next SSO login; an already-open app session can last up to seven days, and a fully offline device cannot learn a revocation. For immediate removal, change the allowlist (which invalidates existing SSO sessions) or disable the app user in the database until a dedicated user-management screen is built.

The app session has its own absolute seven-day lifetime from login, with no shorter idle timeout. If the provider issues no refresh token, expiration of its access token does not end this app session; no expired token is sent to a provider API. This supports Synology SSO configurations that return short-lived access tokens without refresh tokens. A fresh login is still required after the app session expires.

When an access token approaches expiry and a refresh token is present, the API renews it. A database lock prevents simultaneous requests from reusing a rotated refresh token. Network/provider errors keep the session for retry. An invalid refresh grant, disabled account, disabled SSO, changed SSO configuration or rejected stored group membership requires a fresh sign-in. Sync receives 401 and leaves its outbox untouched. Provider-side account/group changes are not polled for sessions without refresh tokens: the verified last-login group snapshot is checked against the current app allowlist on each authenticated request; changed provider membership is learned at a fresh login.

The local profile cache contains only user information and an absolute seven-day app-session expiry, never provider tokens or passwords. After an online login and app-shell installation, that unexpired profile permits offline work in the user's IndexedDB store. offline_access does not grant offline server access and is not needed for these saved local edits. After the local allowance expires, sign-in requires connectivity; pending data remains. Revocation cannot be learned while completely offline. Cached data is not encrypted or erased at logout; this is a trusted-device prototype, not a shared-device security boundary.

Logging out revokes this Model Delivery session, clears its cookie and cached profile, and retains account-specific outbox/data. It does not sign out of Synology or other SSO applications; the next SSO login may therefore be immediate. Provider-wide logout/revocation and recovery-password management UI are future work.

Personal display name

Some Synology installations return only sub, username and email from UserInfo, with no full-name claim. Requesting profile cannot supply a field the provider does not support. In that case the initial greeting falls back to the username.

On 2026-09-04, a fresh live sign-in explicitly requested name (essential), given_name and family_name in both id_token and userinfo through the standard OIDC claims parameter. The validated ID token still contained only aud, auth_time, email, exp, iat, iss, nonce, sub and username; UserInfo still returned only email, sub and username. No name fields or Hebrew name were returned. The temporary request/capture code and encrypted snapshot were removed afterwards. This confirms the installed service did not supply those fields in that experiment; the protocol's claims parameter is optional (OIDC Core). Synology's documented custom attribute mappings are under SAML, rather than its OIDC application setup (Synology application documentation).

Open החשבון שלי and set שם לתצוגה to choose the name used in the greeting. This online account setting is stored separately in app_users.display_name_override; the provider's original name remains in display_name. The preference takes precedence on session reads and future SSO logins. The username, issuer/subject identity, roles and operational records do not change. Successful saves update the cached profile immediately, including its next offline launch. POST /auth/profile accepts only {displayName}, requires a valid session, an allowed request origin/custom header and an x-model-user matching the session.

Choose שימוש בשם הארגוני to clear the personal override (POST /auth/profile with {displayName:null}) and use the directory/provider name again. This also refreshes the offline profile cache. It never edits the directory.

Optional names from REZNIK Active Directory

The deployment target is the same NAS that hosts Synology SSO and Synology Directory Server (AD). Since the live OIDC responses omit name fields, the API can optionally read AD's displayName after a verified SSO login. Authentication, user identity and roles continue to come from the existing OIDC flow/app account; LDAP is used only for the greeting.

In /local-admin, open שמות מתוך Active Directory and configure:

Setting Value to supply
LDAPS URL An internal NAS/domain-controller hostname reachable by the API, such as ldaps://nas.example.internal:636; the hostname must match the directory TLS certificate
NetBIOS domain REZNIK, matching the verified SSO subject prefix
Base DN The actual AD search root, for example DC=example,DC=internal; this example is not the discovered REZNIK domain
Bind DN / UPN A dedicated account permitted to read the relevant AD user attributes
Password Enter locally in the masked field; do not put it in source control or chat
CA certificate Optional public PEM certificate chain if the directory uses an internal CA

Save, enable when the connection is ready, and test a full SSO subject such as REZNIK\tom.ruff. The test reads the saved configuration and returns only the resolved name or a safe failure code. It does not update accounts. Sign in through SSO again to apply the directory name. A personal override still takes priority; use שימוש בשם הארגוני to clear it when desired.

Co-location alone does not establish connectivity: a container's localhost points to the container, not the NAS. Choose a reachable internal NAS address and verify that Synology Directory Server's LDAPS service and TLS certificate are configured. The DSM web certificate/SSO HTTPS endpoint is not evidence that LDAP TLS is configured. No directory ports were opened, NAS services changed, or production deployment performed. The development PC did not discover the REZNIK domain and the earlier public-host LDAPS connection timed out. The full AD DNS domain, internal connection details and read credentials have not been supplied; the working feature remains disabled.

Implementation uses ldapts 9.0.0 with direct LDAPS, TLS 1.2 minimum, certificate/hostname verification, and two-second connection and operation timeouts. Plain LDAP and StartTLS are not offered in this slice. A successful bind precedes one bounded search for an exact sAMAccountName under the configured base DN, with literal equality filters, user/person constraints and a two-entry limit. The verified issuer must match the one bound to the directory configuration and the subject must have the configured DOMAIN\username form. Bare usernames, other domains, duplicate entries and referrals are not accepted. Only displayName, givenName, sn and sAMAccountName are requested; first name plus surname is a fallback if displayName is empty. No groups, roles or directory writes are involved.

Migration 005 stores encrypted directory settings separately from OIDC settings and caches the resolved name with its configuration fingerprint. The precedence is personal override, cached name from the active matching directory configuration, then OIDC name/username. A directory failure does not prevent a valid OIDC sign-in; the last known name remains available. Changing or disabling directory settings makes old cached directory names ineligible on the next online session read. Offline devices retain their last saved profile until online refresh. Saving directory settings does not invalidate SSO sessions. Blank password preserves the existing credential only while the server URL and bind account remain unchanged. LDAP library errors and credentials are never returned or logged.

The first local administrator claims a copy of the pre-authentication IndexedDB cache once, preserving existing UUIDs/outbox entries. The original database remains as a backup. Open the app with that recovery account and synchronize any earlier unsent work before switching to SSO. Other accounts get separate device caches and pull the common server records. An account-ID request header prevents one account's queued mutations being sent under another account's cookie.

Hidden page protection and API

The hidden path is only a convenience. All recovery endpoints reject non-loopback connections; settings/discovery additionally require an authenticated local administrator. Initial setup requires the random setup token. Login/setup use rate limits and passwords use salted scrypt. State-changing endpoints require a custom request header and reject disallowed browser origins.

At a future public reverse proxy, deny /local-admin and /api/local-admin/* on the public listener and provide a separate local/admin access path. The API sees the proxy's loopback address, so its loopback check alone cannot distinguish public clients behind that proxy. Do not expose this development proxy as a production access boundary.

API path (browser adds /api) Method / purpose
/auth/session GET current session or SSO availability
/auth/login POST begin OIDC; returns authorization URL
/auth/callback GET process one-use code callback and redirect to app
/auth/logout POST revoke current app session
/local-admin/status GET whether recovery account setup remains open
/local-admin/setup POST {username,password,displayName,setupToken} once
/local-admin/login POST {username,password} for recovery account
/local-admin/settings GET masked config; POST shared SsoSettings contract
/local-admin/discover POST validate saved issuer/discovery metadata
/local-admin/directory GET masked directory config; POST shared DirectorySettings contract
/local-admin/directory/test POST {subject} to read one name using saved settings

Blank client-secret input preserves the saved secret; choosing public-client authentication clears it. Saving changed SSO configuration invalidates existing SSO sessions on their next server request while leaving the recovery account usable.