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
```
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
- `-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.
Examples:
@@ -42,9 +39,6 @@ Examples:
# Listen on a different local port
./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)
./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.
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)
@@ -79,7 +72,7 @@ Uninstall:
./uninstall-linux.sh
```
Keep token/config on uninstall:
Keep config on uninstall:
```bash
./uninstall-linux.sh --keep-config
@@ -89,7 +82,7 @@ Notes:
- Installs to `~/.local/share/luxtools-client/luxtools-client`
- 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)
@@ -116,7 +109,7 @@ Uninstall:
uninstall-windows-task.bat
```
Keep token/config on uninstall:
Keep config on uninstall:
```bat
uninstall-windows-task.bat --keep-config
@@ -125,18 +118,11 @@ uninstall-windows-task.bat --keep-config
Notes:
- 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.
## 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`
Returns JSON:
@@ -164,7 +150,6 @@ Example:
```bash
curl -sS -X POST \
-H 'Content-Type: application/json' \
-H 'X-Luxtools-Token: your-shared-secret' \
--data '{"path":"/tmp"}' \
http://127.0.0.1:8765/open
```
@@ -177,7 +162,6 @@ Example:
```bash
curl -i \
-H 'X-Luxtools-Token: your-shared-secret' \
'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).
- 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:
--listen host:port Listen address (default: ${DEFAULT_LISTEN})
@@ -74,48 +74,19 @@ cp "$SRC_BIN" "$TMP_BIN"
chmod 0755 "$TMP_BIN" || true
if [[ -f "$ENV_FILE" ]]; then
# Preserve existing config (especially TOKEN).
# Preserve existing config.
# shellcheck disable=SC1090
source "$ENV_FILE" || true
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
# ${SERVICE_NAME} environment
# Keep this file to preserve your shared token across updates.
LISTEN="${LISTEN}"
TOKEN="${TOKEN}"
ALLOW_ARGS="${ALLOW_ARGS}"
EOF
chmod 0640 "$ENV_FILE"
# Best-effort tighten config perms for single-user token storage.
# Best-effort tighten config perms.
chmod 0600 "$ENV_FILE" || true
install -m 0755 -D "$TMP_BIN" "$BIN_PATH"
@@ -127,7 +98,7 @@ Description=luxtools-client (local folder opener helper)
[Service]
Type=simple
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
RestartSec=1
@@ -147,7 +118,6 @@ echo "Installed/updated ${SERVICE_NAME}."
echo "- Binary: ${BIN_PATH}"
echo "- Unit: ${UNIT_FILE}"
echo "- Config: ${ENV_FILE}"
echo "Token (set this in the plugin config): ${TOKEN}"
echo
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).
- 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:
--listen host:port Listen address (default: 127.0.0.1:8765)
@@ -34,14 +34,6 @@ function Quote-Arg([string]$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) {
try {
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
# 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.
$config = [ordered]@{
Listen = $Listen
Token = $token
Allow = @($Allow)
}
($config | ConvertTo-Json -Depth 4) | Set-Content -LiteralPath $configPath -Encoding UTF8
@@ -156,7 +120,7 @@ try {
}
# Build the argument string for the scheduled task.
$argList = @('-listen', $Listen, '-token', $token)
$argList = @('-listen', $Listen)
foreach ($p in $Allow) {
if ($p -and $p.Trim().Length -gt 0) {
$argList += @('-allow', $p)
@@ -188,7 +152,6 @@ Write-Host "Installed/updated $ServiceName (Scheduled Task)."
Write-Host "- Binary: $exePath"
Write-Host "- Task: $TaskName"
Write-Host "- Config: $configPath"
Write-Host "Token (set this in the plugin config): $token"
Write-Host ""
Write-Host "To view task status: schtasks /Query /TN $TaskName /V"
Write-Host "To run it manually: schtasks /Run /TN $TaskName"

View File

@@ -39,7 +39,7 @@ func isKDESession() bool {
// tabs.
//
// 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.
func openFolderKDEDBus(path string) error {
conn, err := dbus.SessionBus()

53
main.go
View File

@@ -1,8 +1,6 @@
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"flag"
@@ -45,7 +43,6 @@ func main() {
errLog := log.New(os.Stderr, "ERROR: ", log.LstdFlags)
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
flag.Var(&allowed, "allow", "allowed path prefix (repeatable); if none, any path is allowed")
flag.Parse()
@@ -54,15 +51,6 @@ func main() {
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.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
@@ -100,16 +88,6 @@ func main() {
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)
if err != nil {
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()
}
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) {
origin := r.Header.Get("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-Methods", "POST, GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Luxtools-Token")
}
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
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
}
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.
Options:
--keep-config Keep ${CONFIG_DIR} (token/config) on disk.
--keep-config Keep ${CONFIG_DIR} (config) on disk.
EOF
}