chore: auto release commit (2026-06-19 11:19:10)
All checks were successful
技能自动化发布 / release (push) Successful in 5s

This commit is contained in:
2026-06-19 11:19:10 +08:00
parent 75c7ea67d5
commit 71bef19514
9 changed files with 1607 additions and 1530 deletions

View File

@@ -0,0 +1,539 @@
# -*- coding: utf-8 -*-
"""Account creation CLI commands (add-web, add-secret)."""
import json
import os
import sys
import time
from typing import Optional
from db.accounts_repo import (
get_account_by_id,
insert_account,
insert_credential,
resolve_auth_strategy_for_platform,
)
from db.auth_strategy import (
ALL_AUTH_STRATEGIES,
AUTH_STRATEGY_DEVICE_BOUND,
)
from db.connection import get_conn, init_db
from db.credentials_repo import insert_credential_encrypted
from service._svc_common import (
PlatformCallerMeta,
_derive_session_persistent,
_err_json,
_insert_secret_holder_account,
_ok_json,
_resolve_or_auto_register,
_resolve_or_fail,
_say,
_say_err,
)
from service.crypto import CredentialDecryptError
from service.master_key import MasterKeyMissingError, is_master_key_available
from service.secret_store import (
mask_secret,
make_windows_credential_target,
write_secret,
)
from util.logging_config import get_skill_logger
from util.platforms import get_platform_spec, seed_platforms
from util.runtime_paths import get_default_profile_dir_for_web
def cmd_account_add_web(
platform_input: str,
login_id: Optional[str],
login_id_type: str,
label: Optional[str],
tenant_id: Optional[str],
environment: str,
role: str,
provider_code: Optional[str],
url: Optional[str],
extra_json_str: Optional[str],
profile_dir_override: Optional[str],
*,
auth_strategy: Optional[str] = None,
session_persistent: Optional[int] = None,
device_fingerprint: Optional[str] = None,
platform_meta: Optional[PlatformCallerMeta] = None,
) -> None:
"""account add-web — create a web/RPA account record."""
log = get_skill_logger()
log.info("account_add_web_attempt platform=%s login_id_type=%s env=%s role=%s",
platform_input, login_id_type, environment, role)
key = _resolve_or_auto_register(platform_input, platform_meta)
if not key:
return
init_db()
now = int(time.time())
conn = get_conn()
try:
seed_platforms(conn)
platform_spec = get_platform_spec(key, conn=conn)
finally:
conn.close()
safe_label = (label or "").strip() or f"{platform_spec.get('display_name', key)} {environment} {role}"
extra = {}
if extra_json_str:
try:
extra = json.loads(extra_json_str)
except json.JSONDecodeError:
_say(_err_json("ERROR:CLI_BAD_ARGS", "extra_json 不是合法的 JSON 字符串。"))
return
if profile_dir_override:
resolved_profile_dir = os.path.abspath(profile_dir_override)
else:
resolved_profile_dir = get_default_profile_dir_for_web(
platform_key=key,
label=safe_label,
login_id=login_id,
domain=platform_spec.get("domain", "generic"),
)
os.makedirs(resolved_profile_dir, exist_ok=True)
resolved_url = (url or "").strip() or platform_spec.get("default_url", "")
resolved_provider = provider_code or platform_spec.get("provider_code") or key
resolved_extra = json.dumps(extra, ensure_ascii=False)
conn = get_conn()
try:
seed_platforms(conn)
if auth_strategy is None:
resolved_auth = resolve_auth_strategy_for_platform(conn, key)
else:
if auth_strategy not in ALL_AUTH_STRATEGIES:
_say(_err_json(
"ERR_AUTH_STRATEGY_NOT_SUPPORTED",
f"不支持的 auth_strategy{auth_strategy}",
))
return
resolved_auth = auth_strategy
if session_persistent is None:
sp = _derive_session_persistent(resolved_auth)
else:
if session_persistent not in (0, 1):
_say(_err_json(
"ERR_INVALID_SESSION_PERSISTENT",
"session_persistent 必须是 0 或 1。",
))
return
sp = session_persistent
if device_fingerprint is not None and resolved_auth != AUTH_STRATEGY_DEVICE_BOUND:
_say(_err_json(
"ERR_DEVICE_FINGERPRINT_NOT_APPLICABLE",
"device_fingerprint 仅在 auth_strategy=device_bound 时可设置。",
))
return
new_id = insert_account(
conn=conn,
platform_key=key,
account_label=safe_label,
login_id=login_id or None,
login_id_type=login_id_type,
tenant_id=tenant_id or None,
environment=environment,
role=role,
provider_code=resolved_provider,
profile_dir=resolved_profile_dir,
url=resolved_url,
extra_json=resolved_extra,
now=now,
auth_strategy=resolved_auth,
session_persistent=sp,
device_fingerprint=device_fingerprint,
)
conn.commit()
except Exception:
conn.rollback()
log.exception("account_add_web_failure")
_say(_err_json("ERROR:DB_ERROR", "Database insert failed."))
return
finally:
conn.close()
log.info("account_add_web_success id=%s platform=%s profile_dir=%s", new_id, key, resolved_profile_dir)
_say(_ok_json({
"id": new_id,
"platform_key": key,
"account_label": safe_label,
"profile_dir": resolved_profile_dir,
"url": resolved_url,
"environment": environment,
"role": role,
"tenant_id": tenant_id or "",
"provider_code": resolved_provider,
"status": "active",
"session_status": "unknown",
"auth_strategy": resolved_auth,
"session_persistent": sp,
"device_fingerprint": device_fingerprint,
}))
def cmd_account_add_secret(
platform_input: str,
credential_type: str,
label: Optional[str],
secret_storage: str,
secret_stdin: bool,
secret_ref: Optional[str],
account_id: Optional[int],
environment: Optional[str],
role: Optional[str],
extra_json_str: Optional[str],
) -> None:
"""account add-secret — create a credential record with masked secret."""
log = get_skill_logger()
log.info("account_add_secret_attempt platform=%s type=%s storage=%s",
platform_input, credential_type, secret_storage)
key = _resolve_or_fail(platform_input)
if not key:
return
storage_n = secret_storage.strip().lower()
if storage_n not in ("none", "env", "windows_credential", "local_encrypted"):
_say(_err_json("ERROR:SECRET_STORAGE_UNSUPPORTED",
f"不支持的存储方式:{secret_storage}。支持none / env / windows_credential / local_encrypted"))
return
if storage_n == "none" and credential_type not in ("browser_profile", "note"):
_say(_err_json("ERROR:SECRET_STORAGE_UNSUPPORTED",
"storage=none 仅允许 credential_type=browser_profile 或 note。"))
return
if storage_n == "env" and not secret_ref:
_say(_err_json("ERROR:SECRET_REF_MISSING",
"env 存储时必须提供 --secret-ref 环境变量名。"))
return
init_db()
conn = get_conn()
try:
seed_platforms(conn)
platform_spec = get_platform_spec(key, conn=conn)
finally:
conn.close()
now = int(time.time())
safe_label = (label or "").strip() or f"{platform_spec.get('display_name', key)} {credential_type}"
if storage_n == "local_encrypted":
if not is_master_key_available():
_say(_err_json(
"ERR_MASTER_KEY_MISSING",
"master key 未初始化。先跑 `account init-master-key`。",
))
return
if not secret_stdin:
_say(_err_json(
"ERR_LOCAL_ENCRYPTED_REQUIRES_STDIN",
"local_encrypted 后端必须 --secret-stdin 输入密文,避免命令历史泄漏。",
))
return
if credential_type not in (
"password",
"api_key",
"api_token",
"bearer_token",
"refresh_token",
"oauth_client",
):
_say(_err_json(
"ERR_LOCAL_ENCRYPTED_TYPE_NOT_ALLOWED",
f"local_encrypted 不接受 credential_type={credential_type}(仅 secret 类)。",
))
return
if secret_ref:
_say_err("[add-secret] WARNING: secret_ref ignored for local_encrypted (secret comes from stdin).")
extra: dict = {}
if extra_json_str:
try:
extra = json.loads(extra_json_str)
except json.JSONDecodeError:
extra = {}
resolved_env = environment or "production"
resolved_role = role or "api"
extra["environment"] = resolved_env
extra["role"] = resolved_role
resolved_extra = json.dumps(extra, ensure_ascii=False)
resolved_url = (platform_spec.get("default_url") or "").strip() or None
resolved_provider = platform_spec.get("provider_code") or key
plaintext = sys.stdin.readline().rstrip("\r\n")
if not plaintext:
_say(_err_json("ERR_LOCAL_ENCRYPTED_EMPTY_STDIN", "stdin 没读到密码内容。"))
return
secret_mask_out = mask_secret(plaintext)
conn = get_conn()
cred_id: Optional[int] = None
holder_id: Optional[int] = None
try:
seed_platforms(conn)
holder_id = account_id
if holder_id is not None:
acc = get_account_by_id(holder_id)
if not acc:
_say(_err_json(
"ERROR:ACCOUNT_NOT_FOUND",
f"account_id={holder_id} 不存在。",
))
return
if holder_id is None:
holder_id = _insert_secret_holder_account(
conn,
platform_key=key,
account_label=safe_label,
environment=resolved_env,
role=resolved_role,
provider_code=resolved_provider,
url=resolved_url,
now=now,
)
cred_id = insert_credential_encrypted(
conn,
account_id=holder_id,
platform_key=key,
credential_label=safe_label,
credential_type=credential_type,
plaintext=plaintext,
expires_at=None,
extra_json=resolved_extra,
now=now,
)
conn.commit()
except MasterKeyMissingError as e:
conn.rollback()
_say(_err_json("ERR_MASTER_KEY_MISSING", str(e)))
return
except CredentialDecryptError as e:
conn.rollback()
_say(_err_json("ERR_CREDENTIAL_DECRYPT_FAILED", str(e)))
return
except Exception:
conn.rollback()
log.exception("account_add_secret_failure")
_say(_err_json("ERROR:DB_ERROR", "Database insert failed."))
return
finally:
conn.close()
log.info(
"account_add_secret_encrypted_success cred_id=%s account_id=%s",
cred_id,
holder_id,
)
_say(_ok_json({
"credential_id": cred_id,
"account_id": holder_id,
"platform_key": key,
"credential_type": credential_type,
"secret_storage": "local_encrypted",
"secret_mask": secret_mask_out,
}))
return
if storage_n == "env":
secret_ref_value = secret_ref.strip()
secret_mask_value = mask_secret(f"env:{secret_ref_value}")
extra = {}
if extra_json_str:
try:
extra = json.loads(extra_json_str)
except json.JSONDecodeError:
extra = {}
resolved_env = environment or "production"
resolved_role = role or "api"
extra["environment"] = resolved_env
extra["role"] = resolved_role
extra_json_final = json.dumps(extra, ensure_ascii=False)
resolved_url = (platform_spec.get("default_url") or "").strip() or None
resolved_provider = platform_spec.get("provider_code") or key
conn = get_conn()
try:
seed_platforms(conn)
holder_id = account_id
if holder_id is None:
holder_id = _insert_secret_holder_account(
conn,
platform_key=key,
account_label=safe_label,
environment=resolved_env,
role=resolved_role,
provider_code=resolved_provider,
url=resolved_url,
now=now,
)
cred_id = insert_credential(
conn=conn,
account_id=holder_id,
platform_key=key,
credential_label=safe_label,
credential_type=credential_type,
secret_storage=storage_n,
secret_ref=secret_ref_value,
secret_mask=secret_mask_value,
expires_at=None,
extra_json=extra_json_final,
now=now,
)
conn.commit()
except Exception:
conn.rollback()
log.exception("account_add_secret_failure")
_say(_err_json("ERROR:DB_ERROR", "Database insert failed."))
return
finally:
conn.close()
log.info("account_add_secret_success id=%s platform=%s storage=env ref=%s",
cred_id, key, secret_ref_value)
_say(_ok_json({
"account_id": holder_id,
"credential_id": cred_id,
"platform_key": key,
"credential_label": safe_label,
"credential_type": credential_type,
"secret_storage": storage_n,
"secret_ref": secret_ref_value,
"secret_mask": secret_mask_value,
"warning": "env 存储方式:请确保环境变量在运行时可用。",
}))
return
if storage_n == "windows_credential":
if not secret_stdin:
_say(_err_json("ERROR:CLI_BAD_ARGS",
"windows_credential 需要 --secret-stdin 从 stdin 读取 secret。"))
return
secret_value = sys.stdin.readline().strip()
if not secret_value:
_say(_err_json("ERROR:CLI_BAD_ARGS", "stdin 中未读到 secret 值。"))
return
secret_mask_value = mask_secret(secret_value)
cred_label_for_target = safe_label
target = make_windows_credential_target(key, credential_type, cred_label_for_target)
try:
write_secret("windows_credential", target, secret_value)
except (OSError, ValueError) as e:
log.error("secret_write_failed storage=windows_credential error=%s", str(e))
_say(_err_json("ERROR:SECRET_WRITE_FAILED", f"写 Windows Credential Manager 失败:{e}"))
return
extra = {}
if extra_json_str:
try:
extra = json.loads(extra_json_str)
except json.JSONDecodeError:
extra = {}
resolved_env = environment or "production"
resolved_role = role or "api"
extra["environment"] = resolved_env
extra["role"] = resolved_role
extra_json_final = json.dumps(extra, ensure_ascii=False)
resolved_url = (platform_spec.get("default_url") or "").strip() or None
resolved_provider = platform_spec.get("provider_code") or key
conn = get_conn()
try:
seed_platforms(conn)
holder_id = account_id
if holder_id is None:
holder_id = _insert_secret_holder_account(
conn,
platform_key=key,
account_label=safe_label,
environment=resolved_env,
role=resolved_role,
provider_code=resolved_provider,
url=resolved_url,
now=now,
)
cred_id = insert_credential(
conn=conn,
account_id=holder_id,
platform_key=key,
credential_label=safe_label,
credential_type=credential_type,
secret_storage=storage_n,
secret_ref=target,
secret_mask=secret_mask_value,
expires_at=None,
extra_json=extra_json_final,
now=now,
)
conn.commit()
except Exception:
conn.rollback()
log.exception("account_add_secret_failure")
_say(_err_json("ERROR:DB_ERROR", "Database insert failed."))
return
finally:
conn.close()
log.info("account_add_secret_success id=%s platform=%s storage=windows_credential",
cred_id, key)
secret_value = ""
_say(_ok_json({
"account_id": holder_id,
"credential_id": cred_id,
"platform_key": key,
"credential_label": safe_label,
"credential_type": credential_type,
"secret_storage": storage_n,
"secret_ref": target,
"secret_mask": secret_mask_value,
}))
return
conn = get_conn()
try:
seed_platforms(conn)
cred_id = insert_credential(
conn=conn,
account_id=account_id,
platform_key=key,
credential_label=safe_label,
credential_type=credential_type,
secret_storage=storage_n,
secret_ref=None,
secret_mask="",
expires_at=None,
extra_json=extra_json_str or "{}",
now=now,
)
conn.commit()
except Exception:
conn.rollback()
log.exception("account_add_secret_failure")
_say(_err_json("ERROR:DB_ERROR", "Database insert failed."))
return
finally:
conn.close()
log.info("account_add_secret_success id=%s platform=%s storage=none", cred_id, key)
_say(_ok_json({
"account_id": account_id,
"credential_id": cred_id,
"platform_key": key,
"credential_label": safe_label,
"credential_type": credential_type,
"secret_storage": storage_n,
"secret_ref": "",
"secret_mask": "",
}))