fix: allow vault info under path scope and clarify API errors
Build and Push Docker Container / build-and-push (push) Successful in 4m29s
Build and Push Docker Container / build-and-push (push) Successful in 4m29s
- Allow vault and vaults commands when a token has path restrictions - Treat vault/vaults as pathless metadata commands in policy checks - Add allowed commands, vaults and paths to 403 permission errors - Surface real JWT failure reason in 401 instead of generic Unauthorized - Report missing, malformed and empty Bearer tokens distinctly - Catch httpRequest failures in the n8n node to avoid circular JSON - Map API errors to clean NodeApiError with status code and detail - Support continueOnFail with per-item error output in the n8n node - Push a versioned image tag from pyproject in the CI workflow - Bump server to 1.0.2 and n8n node package to 0.2.1 - Add tests for vault info access and richer auth error messages
This commit is contained in:
+9
-4
@@ -73,12 +73,17 @@ def require_auth(request:Request) -> AuthContext|JSONResponse:
|
||||
return error(500, "OBSIDIAN_JWT_SECRET is required")
|
||||
|
||||
header = request.headers.get("authorization", "")
|
||||
if not header:
|
||||
return error(401, "missing Authorization header; expected 'Bearer <jwt>'")
|
||||
if not header.startswith("Bearer "):
|
||||
return error(401, "Unauthorized")
|
||||
return error(401, "invalid Authorization header; expected 'Bearer <jwt>'")
|
||||
|
||||
supplied = header.removeprefix("Bearer ").strip()
|
||||
if not supplied:
|
||||
return error(401, "empty Bearer token")
|
||||
|
||||
supplied = header.removeprefix("Bearer ")
|
||||
try:
|
||||
claims = decode_hs256_jwt(supplied, JWT_SECRET.encode())
|
||||
return AuthContext(policy=policy_from_jwt_claims(supplied, claims))
|
||||
except ValueError:
|
||||
return error(401, "Unauthorized")
|
||||
except ValueError as exc:
|
||||
return error(401, f"invalid token: {exc}")
|
||||
|
||||
+21
-6
@@ -36,7 +36,7 @@ PATH_REQUIRED_WHEN_RESTRICTED = {
|
||||
"property:set", "property:remove", "property:read", "properties",
|
||||
"aliases", "backlinks", "links", "outlinks", "tags", "tasks", "task",
|
||||
}
|
||||
PATHLESS_ALLOWED_WHEN_RESTRICTED = {"help", "version"}
|
||||
PATHLESS_ALLOWED_WHEN_RESTRICTED = {"help", "version", "vault", "vaults"}
|
||||
DAILY_NOTE_COMMANDS = {"daily:path", "daily:read", "daily:append", "daily:prepend"}
|
||||
|
||||
def tuple_claim(value:Any, default:tuple[str, ...]) -> tuple[str, ...]:
|
||||
@@ -178,12 +178,18 @@ def authorize_command(policy:TokenPolicy|None, args:list[str], cwd:Path) -> None
|
||||
command = target.command or ""
|
||||
|
||||
if not policy.allows_all_commands() and command not in policy.commands:
|
||||
raise PermissionError(f"token is not allowed to run command: {command}")
|
||||
allowed = ", ".join(sorted(policy.commands)) or "(none)"
|
||||
raise PermissionError(
|
||||
f"token is not allowed to run command: {command or '(empty)'}. allowed commands: {allowed}"
|
||||
)
|
||||
|
||||
if not policy.allows_all_vaults():
|
||||
vault = target.vault or vault_for_path(cwd) or DEFAULT_VAULT_NAME
|
||||
if vault not in policy.vaults:
|
||||
raise PermissionError(f"token is not allowed to access vault: {vault}")
|
||||
allowed = ", ".join(sorted(policy.vaults)) or "(none)"
|
||||
raise PermissionError(
|
||||
f"token is not allowed to access vault: {vault}. allowed vaults: {allowed}"
|
||||
)
|
||||
|
||||
if policy.allows_all_paths():
|
||||
return
|
||||
@@ -192,13 +198,22 @@ def authorize_command(policy:TokenPolicy|None, args:list[str], cwd:Path) -> None
|
||||
if not paths and command in DAILY_NOTE_COMMANDS:
|
||||
paths.append(daily_note_path_for_vault(vault_root_for_target(target, cwd)))
|
||||
|
||||
allowed_paths = ", ".join(policy.paths) or "(none)"
|
||||
if not paths:
|
||||
if command in PATHLESS_ALLOWED_WHEN_RESTRICTED:
|
||||
return
|
||||
if command in PATH_REQUIRED_WHEN_RESTRICTED:
|
||||
raise PermissionError("token has path restrictions; command must include an allowed path/file/folder/name argument")
|
||||
raise PermissionError(f"token has path restrictions; command is not allowed without an explicit path: {command}")
|
||||
raise PermissionError(
|
||||
f"token has path restrictions; command '{command}' must include an allowed "
|
||||
f"path/file/folder/name argument. allowed paths: {allowed_paths}"
|
||||
)
|
||||
raise PermissionError(
|
||||
f"token has path restrictions; command '{command}' is not allowed without an "
|
||||
f"explicit path. allowed paths: {allowed_paths}"
|
||||
)
|
||||
|
||||
denied = [path for path in paths if not is_path_allowed(path, policy.paths)]
|
||||
if denied:
|
||||
raise PermissionError(f"token is not allowed to access path: {denied[0]}")
|
||||
raise PermissionError(
|
||||
f"token is not allowed to access path: {denied[0]}. allowed paths: {allowed_paths}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user