-
Notifications
You must be signed in to change notification settings - Fork 4
feat: support 3D-RBAC stateful token authentication #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| .git | ||
| .github | ||
| __pycache__ | ||
| *.pyc | ||
| tests/ | ||
| doc/ | ||
| vault/ | ||
| config/ | ||
| .fns_state.json | ||
| *.md | ||
| !fns_cli/*.md |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| FROM python:3.13-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Install dependencies first for better layer caching | ||
| COPY requirements.txt . | ||
| RUN pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| # Copy application code | ||
| COPY fns_cli/ ./fns_cli/ | ||
|
|
||
| # Default config mount point | ||
| VOLUME ["/app/config", "/app/vault"] | ||
|
|
||
| ENV PYTHONUNBUFFERED=1 | ||
|
|
||
| ENTRYPOINT ["python", "-m", "fns_cli.main"] | ||
| CMD ["run", "-c", "/app/config/config.yaml"] | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -67,7 +67,7 @@ Edit `config.yaml`: | |||||
| ```yaml | ||||||
| server: | ||||||
| api: "https://your-server-address" # Fast Note Sync Service base URL | ||||||
| token: "your_api_token" # API token from the admin panel | ||||||
| token: "your_api_token" # JWT token from the Token Management panel | ||||||
| vault: "notes" # Vault name; must match the Obsidian plugin | ||||||
|
|
||||||
| sync: | ||||||
|
|
@@ -85,6 +85,7 @@ client: | |||||
| reconnect_max_retries: 15 | ||||||
| reconnect_base_delay: 3 | ||||||
| heartbeat_interval: 30 | ||||||
| client_type: "fns-cli" # Client identifier for 3D-RBAC auth | ||||||
|
|
||||||
| logging: | ||||||
| level: "INFO" | ||||||
|
|
@@ -93,16 +94,32 @@ logging: | |||||
|
|
||||||
| **How to obtain a token** | ||||||
|
|
||||||
| > **Note:** Since service version with 3D-RBAC, tokens are now JWT-based and managed via the web UI. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (typo): Consider adding an article before "service version" for grammatical correctness. The wording "Since service version with 3D-RBAC" is ungrammatical. Consider revising to "Since the service version with 3D-RBAC" or "Since the 3D-RBAC-enabled service version" to improve clarity.
Suggested change
|
||||||
|
|
||||||
| 1. Open the Fast Note Sync Service web UI (e.g. `https://your-server-address`) | ||||||
| 2. Sign in | ||||||
| 3. Click **"Copy API Config"** | ||||||
| 4. Copy `api`, `apiToken`, and `vault` from the JSON into `config.yaml` | ||||||
| 3. Navigate to **Settings → Token Management** | ||||||
| 4. Click **"Create Token"** with these settings: | ||||||
| - **Client Type**: `fns-cli` (or a descriptive name like `my-server-cli`) | ||||||
| - **Scope**: `p:ws c:fns-cli* f:note_rw,file_rw,config_rw` (full read/write access via WebSocket) | ||||||
| - **Expired Days**: set as needed (e.g. `365`) | ||||||
| 5. Copy the generated JWT token into `config.yaml`'s `token` field | ||||||
|
|
||||||
| **Scope examples:** | ||||||
|
|
||||||
| | Use case | Scope | | ||||||
| |----------|-------| | ||||||
| | Full sync (read + write) | `p:ws c:fns-cli* f:note_rw,file_rw,config_rw` | | ||||||
| | Read-only (pull only) | `p:ws c:fns-cli* f:note_r,file_r,config_r` | | ||||||
| | Notes only | `p:ws c:fns-cli* f:note_rw` | | ||||||
|
|
||||||
| > **Fallback:** If your server version still supports legacy tokens, the old "Copy API Config" method also works — the CLI sends the token as-is. | ||||||
|
|
||||||
| Optional environment variables (override when not set in the file): | ||||||
|
|
||||||
| ```bash | ||||||
| export FNS_API="https://your-server-address" | ||||||
| export FNS_TOKEN="your_api_token" | ||||||
| export FNS_TOKEN="your_jwt_token" | ||||||
| ``` | ||||||
|
|
||||||
| ### 4. Run | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| services: | ||
| fns-cli: | ||
| build: . | ||
| container_name: fns-cli | ||
| restart: unless-stopped | ||
| volumes: | ||
| - ./config:/app/config # Mount config directory | ||
| - ./vault:/app/vault # Mount vault directory for sync | ||
| environment: | ||
| - PYTHONUNBUFFERED=1 | ||
| # Optional: override config values via env vars | ||
| # - FNS_API=https://your-server-address | ||
| # - FNS_TOKEN=your_jwt_token |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| """FastNodeSync CLI - A command-line client for Fast Note Sync Service.""" | ||
|
|
||
| __version__ = "0.1.0" | ||
| __version__ = "0.2.0" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ def _make_config() -> MagicMock: | |
| config = MagicMock() | ||
| config.client.reconnect_base_delay = 1 | ||
| config.client.reconnect_max_retries = 3 | ||
| config.client.client_type = "fns-cli" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): Add tests to verify the new WebSocket URL query parameters ( Right now the tests only set
This will guard against regressions in URL construction for the new query parameters. Suggested implementation: config = MagicMock()
config.client.reconnect_base_delay = 1
config.client.reconnect_max_retries = 3
config.client.client_type = "fns-cli"
config.server.token = "token"
config.ws_api = "wss://example.com"
return config
def test_websocket_url_includes_client_query_params(mocker):
from urllib.parse import urlparse, parse_qs
import fns_cli
# Arrange
config = _make_config() # adjust to the actual helper/factory name if different
client = FastNodeSyncClient(config) # adjust to the actual client class under test
# Patch the websocket connect function used by the client to initiate connections
mock_connect = mocker.patch(
"fns_cli.client.websocket.connect", autospec=True
) # adjust import path to match implementation
# Act: trigger a connection attempt (use the public entrypoint if available)
client._connect() # or client.connect(), depending on what is exposed
# Assert: inspect the URL used for the WebSocket connection
called_url = mock_connect.call_args[0][0]
parsed = urlparse(called_url)
qs = parse_qs(parsed.query)
assert qs["client"] == [config.client.client_type]
assert qs["clientName"] == ["FastNodeSync-CLI"]
assert qs["clientVersion"] == [fns_cli.__version__]To integrate this test with the existing codebase, you will likely need to:
|
||
| config.server.token = "token" | ||
| config.ws_api = "wss://example.com" | ||
| return config | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 issue (security): The container runs as root; consider adding a non-root user for improved security.
Running the container as
rootamplifies the impact of any compromise of the CLI or its dependencies. Please add a dedicated non-root user (e.g.,fns),chown/app,/app/config, and/app/vaultto that user, and switch to it withUSER. This usually needs no code changes and significantly reduces risk.