diff --git a/contracts/examples/E-12-create-invite/response.json b/contracts/examples/E-12-create-invite/response.json index 867322b..2fd8019 100644 --- a/contracts/examples/E-12-create-invite/response.json +++ b/contracts/examples/E-12-create-invite/response.json @@ -3,7 +3,7 @@ "kind": "partner", "target_user_id": null, "code": "4N8RVT2KW6ZC", - "link": "https://kvitt.example.invalid:8443/i/4N8RVT2KW6ZC?from=Jacob", + "link": "https://kvitt.example.invalid/i/4N8RVT2KW6ZC?from=Jacob", "created_at": "2026-10-17T06:30:02.004Z", "expires_at": "2026-10-20T06:30:02.004Z" } diff --git a/contracts/examples/E-7-me/response.json b/contracts/examples/E-7-me/response.json index 58c6491..a629d79 100644 --- a/contracts/examples/E-7-me/response.json +++ b/contracts/examples/E-7-me/response.json @@ -39,5 +39,5 @@ "server_version": 16 } ], - "public_url": "https://kvitt.example.invalid:8443" + "public_url": "https://kvitt.example.invalid" } diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml index c51af65..667771f 100644 --- a/contracts/openapi.yaml +++ b/contracts/openapi.yaml @@ -23,7 +23,7 @@ info: `protocol_unsupported`, `rate_limited`, `internal`, `unavailable`; unauthenticated ones `rate_limited`, `internal`, `unavailable`. `not_implemented` (501) exists only during milestones M2a to M4. servers: - - url: https://{host}:8443 + - url: https://{host} variables: host: default: kvitt.example.invalid diff --git a/server/README.md b/server/README.md index 0ecdb41..aa2b977 100644 --- a/server/README.md +++ b/server/README.md @@ -30,7 +30,7 @@ Configuration is environment variables only (SPEC §10.4, exhaustive): |---|---|---| | `KVITT_DATA_DIR` | `/data` | Holds `kvitt.sqlite` (+ `-wal`, `-shm`), `pictures/`, `apk/`. Created if missing. | | `KVITT_BIND` | `0.0.0.0:8080` | Listen address. | -| `KVITT_PUBLIC_URL` | none: required | Base URL in invite links and the landing page, `https://:8443` on the Pi (set by compose). Every command refuses to start without it. | +| `KVITT_PUBLIC_URL` | none: required | Base URL in invite links and the landing page, `https://` on the Pi (set by compose). Every command refuses to start without it. | | `KVITT_FCM_CREDENTIALS` | `/secrets/fcm-service-account.json` | FCM key; missing file = push disabled (warning at start). | | `KVITT_FCM_PROJECT_ID` | `kvitt-1337` | FCM project. | | `KVITT_TRUSTED_PROXY` | `none` | IP literal or host name allowed to set `X-Forwarded-For` (on the Pi `HA_HOST`, NPM's LAN address); `none` or a name that does not resolve means no proxy. | diff --git a/server/crates/kvitt-server/src/config.rs b/server/crates/kvitt-server/src/config.rs index 49119fb..bdb265b 100644 --- a/server/crates/kvitt-server/src/config.rs +++ b/server/crates/kvitt-server/src/config.rs @@ -69,7 +69,7 @@ impl Config { AppError::new( crate::error::ErrorCode::Internal, "KVITT_PUBLIC_URL is not set: it must be the Kvitt base URL, \ - https://:8443 (SPEC §10.4)", + https:// (SPEC §10.4)", ) })?; Ok(Self { @@ -102,7 +102,7 @@ impl Config { mod tests { use super::*; - const PUBLIC_URL: &str = "https://kvitt.example.invalid:8443"; + const PUBLIC_URL: &str = "https://kvitt.example.invalid"; #[test] fn defaults_when_unset_or_empty() { diff --git a/server/crates/kvitt-server/src/openapi.rs b/server/crates/kvitt-server/src/openapi.rs index e40b80d..a639db2 100644 --- a/server/crates/kvitt-server/src/openapi.rs +++ b/server/crates/kvitt-server/src/openapi.rs @@ -37,7 +37,7 @@ strings. Common rules: SPEC §14.3.1."; /// The production base URL (SPEC §7, §14.3): the host is the server variable [`HOST_VARIABLE`], because the Kvitt /// host name is owner-chosen (SPEC §10.5). -pub const PUBLIC_SERVER_URL: &str = "https://{host}:8443"; +pub const PUBLIC_SERVER_URL: &str = "https://{host}"; /// The server variable in [`PUBLIC_SERVER_URL`]. pub const HOST_VARIABLE: &str = "host"; /// The default of [`HOST_VARIABLE`]: a reserved name that never resolves (RFC 6761), not a real host. diff --git a/server/crates/kvitt-server/tests/common/mod.rs b/server/crates/kvitt-server/tests/common/mod.rs index 4f3a2b7..1258252 100644 --- a/server/crates/kvitt-server/tests/common/mod.rs +++ b/server/crates/kvitt-server/tests/common/mod.rs @@ -23,7 +23,7 @@ use tower::ServiceExt; use uuid::Uuid; /// `KVITT_PUBLIC_URL` of the harness, on the reserved `.invalid` name of the examples (E-7, E-12). -pub const PUBLIC_URL: &str = "https://kvitt.example.invalid:8443"; +pub const PUBLIC_URL: &str = "https://kvitt.example.invalid"; pub const ADMIN_ID: &str = "0192a000-0000-7000-8000-000000000001"; pub const MEMBER_ID: &str = "0192a000-0000-7000-8000-000000000002"; pub const ADMIN_DEVICE_ID: &str = "0192a001-0000-7000-8000-000000000001"; diff --git a/server/crates/kvitt-server/tests/core_api/cli.rs b/server/crates/kvitt-server/tests/core_api/cli.rs index b4c29fe..f809f4a 100644 --- a/server/crates/kvitt-server/tests/core_api/cli.rs +++ b/server/crates/kvitt-server/tests/core_api/cli.rs @@ -12,7 +12,7 @@ fn run(dir: &std::path::Path, args: &[&str]) -> Output { Command::new(env!("CARGO_BIN_EXE_kvitt-server")) .args(args) .env("KVITT_DATA_DIR", dir) - .env("KVITT_PUBLIC_URL", "https://kvitt.example:8443") + .env("KVITT_PUBLIC_URL", "https://kvitt.example") .env("RUST_LOG", "warn") .output() .unwrap() @@ -26,7 +26,7 @@ fn printed_code(output: &Output) -> String { .find_map(|l| l.strip_prefix("Link:")) .unwrap() .trim(); - let code = link.strip_prefix("https://kvitt.example:8443/i/").unwrap(); + let code = link.strip_prefix("https://kvitt.example/i/").unwrap(); assert_eq!(code.len(), 12); let display = stdout .lines() @@ -131,7 +131,7 @@ fn printed_partner_code(output: &Output, from: &str) -> String { .find_map(|l| l.strip_prefix("Link:")) .unwrap() .trim(); - let rest = link.strip_prefix("https://kvitt.example:8443/i/").unwrap(); + let rest = link.strip_prefix("https://kvitt.example/i/").unwrap(); let (code, query) = rest.split_once('?').unwrap(); assert_eq!(query, format!("from={from}")); assert_eq!(code.len(), 12); diff --git a/server/crates/kvitt-server/tests/core_api/landing.rs b/server/crates/kvitt-server/tests/core_api/landing.rs index 0c4b628..2dd20ea 100644 --- a/server/crates/kvitt-server/tests/core_api/landing.rs +++ b/server/crates/kvitt-server/tests/core_api/landing.rs @@ -34,9 +34,9 @@ async fn e4_english_by_default_with_both_links_and_the_grouped_code() { assert!(html.contains(">Open in Kvitt")); assert!(html.contains("7K3M-QX9D-2HPA")); assert!(html.contains( - "href=\"kvitt://invite?server=https%3A%2F%2Fkvitt%2Eexample%2Einvalid%3A8443&code=7K3MQX9D2HPA\"" + "href=\"kvitt://invite?server=https%3A%2F%2Fkvitt%2Eexample%2Einvalid&code=7K3MQX9D2HPA\"" )); - assert!(html.contains("href=\"https://kvitt.example.invalid:8443/apk/files/kvitt-412.apk\"")); + assert!(html.contains("href=\"https://kvitt.example.invalid/apk/files/kvitt-412.apk\"")); } #[tokio::test] @@ -90,7 +90,7 @@ async fn e4_passes_the_admin_name_of_an_e12_link_into_the_app_link_only() { assert_eq!(reply.status, 200); let html = reply.text(); assert!(html.contains( - "href=\"kvitt://invite?server=https%3A%2F%2Fkvitt%2Eexample%2Einvalid%3A8443&code=7K3MQX9D2HPA&from=%C3%85sa%20Berg\"" + "href=\"kvitt://invite?server=https%3A%2F%2Fkvitt%2Eexample%2Einvalid&code=7K3MQX9D2HPA&from=%C3%85sa%20Berg\"" ), "{html}"); // The name is not shown on the page itself. assert!(!html.contains("Åsa")); diff --git a/server/crates/kvitt-server/tests/skeleton/support.rs b/server/crates/kvitt-server/tests/skeleton/support.rs index cd0b254..cf9e1c6 100644 --- a/server/crates/kvitt-server/tests/skeleton/support.rs +++ b/server/crates/kvitt-server/tests/skeleton/support.rs @@ -49,7 +49,7 @@ impl TestApp { let dir = TempDir::new(); let config = Config::from_lookup(|k| match k { "KVITT_DATA_DIR" => Some(dir.0.to_string_lossy().into_owned()), - "KVITT_PUBLIC_URL" => Some("https://kvitt.example.invalid:8443".to_owned()), + "KVITT_PUBLIC_URL" => Some("https://kvitt.example.invalid".to_owned()), _ => None, }) .unwrap(); diff --git a/tools/contracts-check.py b/tools/contracts-check.py index 4c13a8c..7729358 100755 --- a/tools/contracts-check.py +++ b/tools/contracts-check.py @@ -1471,7 +1471,7 @@ def check_openapi(): lines = f.read().splitlines() text = "\n".join(lines) head_ok = (re.search(r"^openapi: 3\.1\.0$", text, re.M) and re.search(r"^ title: Kvitt$", text, re.M) - and re.search(r"^ version: '1'$", text, re.M) and re.search(r"^ - url: https://\{host\}:8443\n variables:\n host:\n default: kvitt\.example\.invalid$", text, re.M)) + and re.search(r"^ version: '1'$", text, re.M) and re.search(r"^ - url: https://\{host\}\n variables:\n host:\n default: kvitt\.example\.invalid$", text, re.M)) report(bool(head_ok), "openapi header (3.1.0, title Kvitt, version 1, one server)", "" if head_ok else "header fields differ from §14.3") sec_ok = re.search(r"^ device:\n type: http\n scheme: bearer$", text, re.M) is not None report(sec_ok, "openapi security scheme device = HTTP bearer (§8.2)", "" if sec_ok else "components.securitySchemes.device not found")