Remove token from client
This commit is contained in:
24
README.md
24
README.md
@@ -28,12 +28,9 @@ go build -o luxtools-client .
|
||||
./luxtools-client
|
||||
```
|
||||
|
||||
On startup, if you don’t 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'
|
||||
```
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
53
main.go
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user