From 96e633843ae474bad7a5333202cf48bcdf85468f Mon Sep 17 00:00:00 2001 From: Dinesh Pandiyan Date: Mon, 12 Oct 2026 23:47:42 +1100 Subject: [PATCH] feat: allow server tokens of any length --- README.md | 5 +++-- internal/remoteserver/server_test.go | 4 ++-- internal/reviewstore/authentication.go | 4 ++-- internal/reviewstore/authentication_test.go | 5 ++++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9404994d..db166694 100644 --- a/README.md +++ b/README.md @@ -177,8 +177,9 @@ and preserves the named SQLite volume when stopped or recreated. Set `DIFFX_SERVER_PORT=7979` to publish another host port. The project is named `diffx`, so its volume is shared by every checkout. Retrieve the initial credential with `docker compose cp server:/data/state.db.admin-token ./admin-token`. -To choose the credential instead, put `DIFFX_SERVER_TOKEN=…` (at least 32 -characters) in a git-ignored `.env` beside `compose.yaml` before the first start. +To choose the credential instead, put `DIFFX_SERVER_TOKEN=…` in a git-ignored +`.env` beside `compose.yaml` before the first start. Any non-empty token works; +prefer a long random one (`openssl rand -hex 32`) when exposing the server. It only applies to a new database; a different token for an existing account fails startup. See [server development](docs/development.md#server-development) for configuration diff --git a/internal/remoteserver/server_test.go b/internal/remoteserver/server_test.go index 9bea53c4..8828c0dc 100644 --- a/internal/remoteserver/server_test.go +++ b/internal/remoteserver/server_test.go @@ -329,13 +329,13 @@ func TestRetentionDurationValidation(t *testing.T) { } } -func TestRemoteRejectsMissingIdentityOrWeakCredentials(t *testing.T) { +func TestRemoteRejectsMissingIdentityOrInvalidCredentials(t *testing.T) { store, err := reviewstore.Open("") if err != nil { t.Fatal(err) } defer store.Close() - for _, tc := range []struct{ account, token string }{{"", testToken}, {" ", testToken}, {"account", "short"}, {"account\n", testToken}, {"account", testToken + "\r"}} { + for _, tc := range []struct{ account, token string }{{"", testToken}, {" ", testToken}, {"account\n", testToken}, {"account", testToken + "\r"}} { if _, _, err := Handler(t.Context(), store, tc.account, tc.token, fstest.MapFS{}); err == nil { t.Fatalf("invalid remote identity accepted: account=%q", tc.account) } diff --git a/internal/reviewstore/authentication.go b/internal/reviewstore/authentication.go index d7b29b34..ec7efe8f 100644 --- a/internal/reviewstore/authentication.go +++ b/internal/reviewstore/authentication.go @@ -21,7 +21,7 @@ func initializeAuthentication(transaction *sql.Tx) error { } func validCredentials(account, token string) bool { - return strings.TrimSpace(account) != "" && len(account) <= 256 && !strings.ContainsAny(account, "\r\n:") && len(token) >= 32 && !strings.ContainsAny(token, "\r\n") + return strings.TrimSpace(account) != "" && len(account) <= 256 && !strings.ContainsAny(account, "\r\n:") && token != "" && !strings.ContainsAny(token, "\r\n") } func tokenHash(token string) string { @@ -41,7 +41,7 @@ func (store *Store) ProvisionUser(account, token string) (User, error) { func (store *Store) provisionRemoteUser(account, token string, reuse bool) (User, error) { if !validCredentials(account, token) { - return User{}, errors.New("remote account requires a name and a token of at least 32 bytes") + return User{}, errors.New("remote account requires a name and a non-empty token") } transaction, err := store.db.Begin() if err != nil { diff --git a/internal/reviewstore/authentication_test.go b/internal/reviewstore/authentication_test.go index 77b6ee18..ad82e4b5 100644 --- a/internal/reviewstore/authentication_test.go +++ b/internal/reviewstore/authentication_test.go @@ -67,11 +67,14 @@ func TestRemoteCredentialValidation(t *testing.T) { } defer store.Close() for _, tc := range []struct{ name, token string }{ - {"", strings.Repeat("x", 32)}, {" ", strings.Repeat("x", 32)}, {"name", "short"}, + {"", strings.Repeat("x", 32)}, {" ", strings.Repeat("x", 32)}, {"name", ""}, {"name\n", strings.Repeat("x", 32)}, {"name", strings.Repeat("x", 32) + "\r"}, {"name:invalid", strings.Repeat("x", 32)}, } { if _, err := store.ProvisionUser(tc.name, tc.token); err == nil { t.Fatalf("invalid credential accepted for %q", tc.name) } } + if _, err := store.ProvisionUser("name", "helloworld"); err != nil { + t.Fatalf("short token rejected: %v", err) + } }