Remove token from client

This commit is contained in:
2026-01-06 21:40:58 +01:00
parent 4282fed13c
commit 25cda1026b
6 changed files with 13 additions and 147 deletions

View File

@@ -28,12 +28,9 @@ go build -o luxtools-client .
./luxtools-client ./luxtools-client
``` ```
On startup, if you dont pass `-token`, the server generates a random token and prints it to the logs. Put that token into the Dokuwiki plugin configuration.
### Flags ### Flags
- `-listen` (default `127.0.0.1:8765`): listen address (`host:port`). Must be loopback. - `-listen` (default `127.0.0.1:8765`): listen address (`host:port`). Must be loopback.
- `-token` (default empty): shared secret token. If empty, a token is generated and logged at startup.
- `-allow` (repeatable): allowed path prefix. If you specify one or more, the requested path must start with one of them. - `-allow` (repeatable): allowed path prefix. If you specify one or more, the requested path must start with one of them.
Examples: Examples:
@@ -42,9 +39,6 @@ Examples:
# Listen on a different local port # Listen on a different local port
./luxtools-client -listen 127.0.0.1:9000 ./luxtools-client -listen 127.0.0.1:9000
# Use an explicit token (recommended for stable config)
./luxtools-client -token "your-shared-secret"
# Restrict opens to your home directory (repeat -allow as needed) # Restrict opens to your home directory (repeat -allow as needed)
./luxtools-client -allow "$HOME" ./luxtools-client -allow "$HOME"
``` ```
@@ -53,7 +47,6 @@ Examples:
The scripts below install (or update) the client as a service that starts automatically with the system. The scripts below install (or update) the client as a service that starts automatically with the system.
They assume the client binary already exists in the same folder as the scripts. They assume the client binary already exists in the same folder as the scripts.
During install/update, the scripts prompt you for the shared token (press Enter to keep the current token, if already configured).
### Linux (systemd) ### Linux (systemd)
@@ -79,7 +72,7 @@ Uninstall:
./uninstall-linux.sh ./uninstall-linux.sh
``` ```
Keep token/config on uninstall: Keep config on uninstall:
```bash ```bash
./uninstall-linux.sh --keep-config ./uninstall-linux.sh --keep-config
@@ -89,7 +82,7 @@ Notes:
- Installs to `~/.local/share/luxtools-client/luxtools-client` - Installs to `~/.local/share/luxtools-client/luxtools-client`
- Creates a systemd *user* unit at `~/.config/systemd/user/luxtools-client.service` - Creates a systemd *user* unit at `~/.config/systemd/user/luxtools-client.service`
- Stores config (including the token) in `~/.config/luxtools-client/luxtools-client.env` - Stores config in `~/.config/luxtools-client/luxtools-client.env`
### Windows (Scheduled Task at logon) ### Windows (Scheduled Task at logon)
@@ -116,7 +109,7 @@ Uninstall:
uninstall-windows-task.bat uninstall-windows-task.bat
``` ```
Keep token/config on uninstall: Keep config on uninstall:
```bat ```bat
uninstall-windows-task.bat --keep-config uninstall-windows-task.bat --keep-config
@@ -125,18 +118,11 @@ uninstall-windows-task.bat --keep-config
Notes: Notes:
- Installs to `%LOCALAPPDATA%\luxtools-client\luxtools-client.exe` - Installs to `%LOCALAPPDATA%\luxtools-client\luxtools-client.exe`
- Stores config (including the token) in `%LOCALAPPDATA%\luxtools-client\config.json` - Stores config in `%LOCALAPPDATA%\luxtools-client\config.json`
- Re-running the install script updates the EXE in place and restarts the task. - Re-running the install script updates the EXE in place and restarts the task.
## API ## API
### Auth
Requests must include the token using the `X-Luxtools-Token` header.
- Header: `X-Luxtools-Token: <token>`
- For `GET /open`, a `token=...` query parameter is also accepted as a fallback.
### `GET /health` ### `GET /health`
Returns JSON: Returns JSON:
@@ -164,7 +150,6 @@ Example:
```bash ```bash
curl -sS -X POST \ curl -sS -X POST \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-H 'X-Luxtools-Token: your-shared-secret' \
--data '{"path":"/tmp"}' \ --data '{"path":"/tmp"}' \
http://127.0.0.1:8765/open http://127.0.0.1:8765/open
``` ```
@@ -177,7 +162,6 @@ Example:
```bash ```bash
curl -i \ curl -i \
-H 'X-Luxtools-Token: your-shared-secret' \
'http://127.0.0.1:8765/open?path=/tmp' 'http://127.0.0.1:8765/open?path=/tmp'
``` ```

View File

@@ -17,7 +17,7 @@ Usage: $0 [--listen host:port] [--allow <path>]...
Installs/updates ${SERVICE_NAME} as a systemd *user* service (runs under your current user). Installs/updates ${SERVICE_NAME} as a systemd *user* service (runs under your current user).
- Re-running updates the installed binary and restarts the service. - Re-running updates the installed binary and restarts the service.
- A stable token is stored in ${ENV_FILE} (created on first install). - Config is stored in ${ENV_FILE} (created on first install).
Options: Options:
--listen host:port Listen address (default: ${DEFAULT_LISTEN}) --listen host:port Listen address (default: ${DEFAULT_LISTEN})
@@ -74,48 +74,19 @@ cp "$SRC_BIN" "$TMP_BIN"
chmod 0755 "$TMP_BIN" || true chmod 0755 "$TMP_BIN" || true
if [[ -f "$ENV_FILE" ]]; then if [[ -f "$ENV_FILE" ]]; then
# Preserve existing config (especially TOKEN). # Preserve existing config.
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "$ENV_FILE" || true source "$ENV_FILE" || true
fi fi
CURRENT_TOKEN="${TOKEN:-}"
SUGGESTED_TOKEN=""
if [[ -z "${CURRENT_TOKEN}" ]]; then
if command -v openssl >/dev/null 2>&1; then
SUGGESTED_TOKEN="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n\r')"
else
SUGGESTED_TOKEN="$(head -c 32 /dev/urandom | base64 | tr '+/' '-_' | tr -d '=\n\r')"
fi
fi
echo
if [[ -n "${CURRENT_TOKEN}" ]]; then
echo "A token is already configured. Press Enter to keep it, or paste a new one."
else
echo "No token configured yet. Press Enter to use a generated token, or paste your own."
fi
read -r -s -p "Token: " TOKEN_INPUT
echo
if [[ -n "${TOKEN_INPUT}" ]]; then
TOKEN="${TOKEN_INPUT}"
elif [[ -n "${CURRENT_TOKEN}" ]]; then
TOKEN="${CURRENT_TOKEN}"
else
TOKEN="${SUGGESTED_TOKEN}"
fi
cat >"$ENV_FILE" <<EOF cat >"$ENV_FILE" <<EOF
# ${SERVICE_NAME} environment # ${SERVICE_NAME} environment
# Keep this file to preserve your shared token across updates.
LISTEN="${LISTEN}" LISTEN="${LISTEN}"
TOKEN="${TOKEN}"
ALLOW_ARGS="${ALLOW_ARGS}" ALLOW_ARGS="${ALLOW_ARGS}"
EOF EOF
chmod 0640 "$ENV_FILE" chmod 0640 "$ENV_FILE"
# Best-effort tighten config perms for single-user token storage. # Best-effort tighten config perms.
chmod 0600 "$ENV_FILE" || true chmod 0600 "$ENV_FILE" || true
install -m 0755 -D "$TMP_BIN" "$BIN_PATH" install -m 0755 -D "$TMP_BIN" "$BIN_PATH"
@@ -127,7 +98,7 @@ Description=luxtools-client (local folder opener helper)
[Service] [Service]
Type=simple Type=simple
EnvironmentFile=%h/.config/luxtools-client/luxtools-client.env EnvironmentFile=%h/.config/luxtools-client/luxtools-client.env
ExecStart=/bin/sh -lc '%h/.local/share/luxtools-client/luxtools-client -listen "$LISTEN" -token "$TOKEN" $ALLOW_ARGS' ExecStart=/bin/sh -lc '%h/.local/share/luxtools-client/luxtools-client -listen "$LISTEN" $ALLOW_ARGS'
Restart=on-failure Restart=on-failure
RestartSec=1 RestartSec=1
@@ -147,7 +118,6 @@ echo "Installed/updated ${SERVICE_NAME}."
echo "- Binary: ${BIN_PATH}" echo "- Binary: ${BIN_PATH}"
echo "- Unit: ${UNIT_FILE}" echo "- Unit: ${UNIT_FILE}"
echo "- Config: ${ENV_FILE}" echo "- Config: ${ENV_FILE}"
echo "Token (set this in the plugin config): ${TOKEN}"
echo echo
echo "View logs with: journalctl --user -u ${SERVICE_NAME} -f" echo "View logs with: journalctl --user -u ${SERVICE_NAME} -f"

View File

@@ -16,7 +16,7 @@ Usage:
Installs/updates $ServiceName as a Windows Scheduled Task (per-user, runs at logon). Installs/updates $ServiceName as a Windows Scheduled Task (per-user, runs at logon).
- Re-running updates the installed binary and restarts the task. - Re-running updates the installed binary and restarts the task.
- A stable token is stored in: %LOCALAPPDATA%\$ServiceName\config.json - Config is stored in: %LOCALAPPDATA%\$ServiceName\config.json
Options: Options:
--listen host:port Listen address (default: 127.0.0.1:8765) --listen host:port Listen address (default: 127.0.0.1:8765)
@@ -34,14 +34,6 @@ function Quote-Arg([string]$s) {
return $s return $s
} }
function New-Base64UrlToken([int]$numBytes = 32) {
$bytes = New-Object byte[] $numBytes
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
$b64 = [Convert]::ToBase64String($bytes)
# Base64URL without padding
return ($b64.TrimEnd('=') -replace '\+','-' -replace '/','_')
}
function Stop-ExistingInstance([string]$exePath) { function Stop-ExistingInstance([string]$exePath) {
try { try {
if (Get-Command Stop-ScheduledTask -ErrorAction SilentlyContinue) { if (Get-Command Stop-ScheduledTask -ErrorAction SilentlyContinue) {
@@ -106,37 +98,9 @@ $configPath = Join-Path $installDir "config.json"
New-Item -ItemType Directory -Force -Path $installDir | Out-Null New-Item -ItemType Directory -Force -Path $installDir | Out-Null
# Load existing config if present (preserve TOKEN across updates).
$existingToken = $null
if (Test-Path -LiteralPath $configPath) {
try {
$cfg = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
if ($cfg -and $cfg.Token) { $existingToken = [string]$cfg.Token }
} catch {
# ignore malformed config; will regenerate
}
}
$tokenSuggested = if ($existingToken) { "" } else { New-Base64UrlToken }
Write-Host ""
if ($existingToken) {
Write-Host "A token is already configured. Press Enter to keep it, or paste a new one."
} else {
Write-Host "No token configured yet. Press Enter to use a generated token, or paste your own."
}
$tokenInput = Read-Host -Prompt "Token" # not secure, but matches Linux behavior
$tokenInput = ("" + $tokenInput).Trim()
$token = if ($tokenInput.Length -gt 0) { $tokenInput } elseif ($existingToken) { $existingToken } else { $tokenSuggested }
if (-not $token -or $token.Trim().Length -eq 0) {
throw "Failed to determine token"
}
# Persist config. # Persist config.
$config = [ordered]@{ $config = [ordered]@{
Listen = $Listen Listen = $Listen
Token = $token
Allow = @($Allow) Allow = @($Allow)
} }
($config | ConvertTo-Json -Depth 4) | Set-Content -LiteralPath $configPath -Encoding UTF8 ($config | ConvertTo-Json -Depth 4) | Set-Content -LiteralPath $configPath -Encoding UTF8
@@ -156,7 +120,7 @@ try {
} }
# Build the argument string for the scheduled task. # Build the argument string for the scheduled task.
$argList = @('-listen', $Listen, '-token', $token) $argList = @('-listen', $Listen)
foreach ($p in $Allow) { foreach ($p in $Allow) {
if ($p -and $p.Trim().Length -gt 0) { if ($p -and $p.Trim().Length -gt 0) {
$argList += @('-allow', $p) $argList += @('-allow', $p)
@@ -188,7 +152,6 @@ Write-Host "Installed/updated $ServiceName (Scheduled Task)."
Write-Host "- Binary: $exePath" Write-Host "- Binary: $exePath"
Write-Host "- Task: $TaskName" Write-Host "- Task: $TaskName"
Write-Host "- Config: $configPath" Write-Host "- Config: $configPath"
Write-Host "Token (set this in the plugin config): $token"
Write-Host "" Write-Host ""
Write-Host "To view task status: schtasks /Query /TN $TaskName /V" Write-Host "To view task status: schtasks /Query /TN $TaskName /V"
Write-Host "To run it manually: schtasks /Run /TN $TaskName" Write-Host "To run it manually: schtasks /Run /TN $TaskName"

View File

@@ -39,7 +39,7 @@ func isKDESession() bool {
// tabs. // tabs.
// //
// Note: On Plasma Wayland, reliably forcing the window to the foreground is // Note: On Plasma Wayland, reliably forcing the window to the foreground is
// gated by XDG activation tokens; we do a best-effort activation call but it may // gated by XDG activation mechanisms; we do a best-effort activation call but it may
// be ignored by the compositor. // be ignored by the compositor.
func openFolderKDEDBus(path string) error { func openFolderKDEDBus(path string) error {
conn, err := dbus.SessionBus() conn, err := dbus.SessionBus()

53
main.go
View File

@@ -1,8 +1,6 @@
package main package main
import ( import (
"crypto/rand"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"flag" "flag"
@@ -45,7 +43,6 @@ func main() {
errLog := log.New(os.Stderr, "ERROR: ", log.LstdFlags) errLog := log.New(os.Stderr, "ERROR: ", log.LstdFlags)
listen := flag.String("listen", "127.0.0.1:8765", "listen address (host:port), should be loopback") listen := flag.String("listen", "127.0.0.1:8765", "listen address (host:port), should be loopback")
token := flag.String("token", "", "shared secret token; if empty, requests are allowed without authentication")
var allowed allowList var allowed allowList
flag.Var(&allowed, "allow", "allowed path prefix (repeatable); if none, any path is allowed") flag.Var(&allowed, "allow", "allowed path prefix (repeatable); if none, any path is allowed")
flag.Parse() flag.Parse()
@@ -54,15 +51,6 @@ func main() {
errLog.Fatalf("refusing to listen on non-loopback address: %s", *listen) errLog.Fatalf("refusing to listen on non-loopback address: %s", *listen)
} }
if strings.TrimSpace(*token) == "" {
generated, err := generateToken()
if err != nil {
errLog.Fatalf("failed to generate token: %v", err)
}
*token = generated
infoLog.Printf("generated token (set this in the plugin config): %s", *token)
}
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
@@ -100,16 +88,6 @@ func main() {
rawPath = req.Path rawPath = req.Path
if !checkToken(r, *token) {
// Allow token to be supplied via query string for GET fallback.
qt := strings.TrimSpace(r.URL.Query().Get("token"))
if qt == "" || !subtleStringEqual(qt, strings.TrimSpace(*token)) {
errLog.Printf("/open unauthorized method=%s path=%q headerToken=%t queryToken=%t dur=%s", r.Method, rawPath, strings.TrimSpace(r.Header.Get("X-Luxtools-Token")) != "", qt != "", time.Since(start))
writeJSON(w, http.StatusUnauthorized, openResponse{OK: false, Message: "unauthorized"})
return
}
}
target, err := normalizePath(req.Path) target, err := normalizePath(req.Path)
if err != nil { if err != nil {
errLog.Printf("/open bad-path method=%s path=%q err=%v dur=%s", r.Method, rawPath, err, time.Since(start)) errLog.Printf("/open bad-path method=%s path=%q err=%v dur=%s", r.Method, rawPath, err, time.Since(start))
@@ -165,14 +143,6 @@ func isLoopbackListenAddr(addr string) bool {
return ip.IsLoopback() return ip.IsLoopback()
} }
func generateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
func withCORS(w http.ResponseWriter, r *http.Request) { func withCORS(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin") origin := r.Header.Get("Origin")
if origin != "" { if origin != "" {
@@ -182,28 +152,7 @@ func withCORS(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
} }
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Luxtools-Token") w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
}
func checkToken(r *http.Request, required string) bool {
required = strings.TrimSpace(required)
if required == "" {
return true
}
got := r.Header.Get("X-Luxtools-Token")
got = strings.TrimSpace(got)
return got != "" && subtleStringEqual(got, required)
}
func subtleStringEqual(a, b string) bool {
if len(a) != len(b) {
return false
}
var v byte
for i := 0; i < len(a); i++ {
v |= a[i] ^ b[i]
}
return v == 0
} }
func normalizePath(input string) (string, error) { func normalizePath(input string) (string, error) {

View File

@@ -13,7 +13,7 @@ Usage: $0 [--keep-config]
Uninstalls ${SERVICE_NAME} systemd *user* service and removes installed files. Uninstalls ${SERVICE_NAME} systemd *user* service and removes installed files.
Options: Options:
--keep-config Keep ${CONFIG_DIR} (token/config) on disk. --keep-config Keep ${CONFIG_DIR} (config) on disk.
EOF EOF
} }