fix: 修复身份认证绕过问题
Refs GHSA-6959-99pq-c56x
This commit is contained in:
parent
943592c654
commit
1e8b20e30b
@ -5,6 +5,11 @@ SAVE_DIR=./saves
|
||||
SILICONFLOW_API_KEY= # 推荐使用硅基流动免费服务 https://cloud.siliconflow.cn/i/Eo5yTHGJ
|
||||
TAVILY_API_KEY= # 获取搜索服务的 api key 请访问 https://app.tavily.com/
|
||||
|
||||
# JWT 安全配置:首次部署请生成随机值,初始化脚本会在留空时自动生成
|
||||
YUXI_ENV=development
|
||||
JWT_SECRET_KEY=
|
||||
YUXI_INSTANCE_ID=
|
||||
|
||||
# # 其余可选配置
|
||||
# OPENAI_API_KEY=
|
||||
# OPENAI_API_BASE=
|
||||
|
||||
@ -4,14 +4,14 @@ import re
|
||||
from fastapi import Depends, Header, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from jose import JWTError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.storage.postgres.manager import pg_manager
|
||||
from yuxi.storage.postgres.models_business import User, APIKey
|
||||
from server.utils.auth_utils import AuthUtils
|
||||
from yuxi.storage.postgres.models_business import APIKey, User
|
||||
from yuxi.utils.datetime_utils import utc_now_naive
|
||||
|
||||
from server.utils.auth_utils import AuthUtils
|
||||
|
||||
# 定义OAuth2密码承载器,指定token URL
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token", auto_error=False)
|
||||
|
||||
@ -115,10 +115,16 @@ async def get_current_user(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
result = await db.execute(select(User).filter(User.id == int(user_id)))
|
||||
result = await db.execute(select(User).filter(User.id == int(user_id), User.is_deleted == 0))
|
||||
user = result.scalar_one_or_none()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
if user.is_login_locked():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_423_LOCKED,
|
||||
detail="登录被锁定,请稍后重试",
|
||||
headers={"X-Lock-Remaining": str(user.get_remaining_lock_time())},
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@ -1,22 +1,52 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import secrets
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
import jwt
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import InvalidHash, VerificationError, VerifyMismatchError
|
||||
|
||||
from yuxi.utils.datetime_utils import utc_now
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "yuxi_know_secure_key")
|
||||
LEGACY_JWT_SECRET_KEY = "yuxi_know_secure_key"
|
||||
JWT_ALGORITHM = "HS256"
|
||||
JWT_EXPIRATION = 7 * 24 * 60 * 60 # 7天过期
|
||||
JWT_AUDIENCE = "yuxi-know-api"
|
||||
PASSWORD_HASHER = PasswordHasher()
|
||||
|
||||
|
||||
def _is_production_env() -> bool:
|
||||
return os.environ.get("YUXI_ENV", "development").strip().lower() in {"prod", "production"}
|
||||
|
||||
|
||||
def _get_or_create_dev_env(name: str, value_factory) -> str:
|
||||
value = os.environ.get(name, "").strip()
|
||||
if value:
|
||||
return value
|
||||
if _is_production_env():
|
||||
raise ValueError(f"{name} 未配置,请在生产环境的 .env.prod 中设置持久化随机值")
|
||||
|
||||
value = value_factory()
|
||||
os.environ[name] = value
|
||||
print(f"{name} 未配置,开发环境已自动生成临时随机值,服务重启后会重新生成。")
|
||||
return value
|
||||
|
||||
|
||||
def _get_jwt_secret_key() -> str:
|
||||
secret_key = _get_or_create_dev_env("JWT_SECRET_KEY", lambda: secrets.token_hex(32))
|
||||
if secret_key == LEGACY_JWT_SECRET_KEY:
|
||||
raise ValueError("JWT_SECRET_KEY 不能使用历史默认密钥,请重新生成随机强密钥")
|
||||
return secret_key
|
||||
|
||||
|
||||
def _get_jwt_issuer() -> str:
|
||||
instance_id = _get_or_create_dev_env("YUXI_INSTANCE_ID", lambda: f"instance-{secrets.token_hex(8)}")
|
||||
return f"yuxi-know:{instance_id}"
|
||||
|
||||
|
||||
class AuthUtils:
|
||||
"""认证工具类"""
|
||||
|
||||
@ -53,26 +83,40 @@ class AuthUtils:
|
||||
else:
|
||||
expire = utc_now() + timedelta(seconds=JWT_EXPIRATION)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
to_encode.update({"exp": expire, "iss": _get_jwt_issuer(), "aud": JWT_AUDIENCE})
|
||||
|
||||
# 编码JWT
|
||||
encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
|
||||
encoded_jwt = jwt.encode(to_encode, _get_jwt_secret_key(), algorithm=JWT_ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@staticmethod
|
||||
def decode_token(token: str) -> dict[str, Any] | None:
|
||||
"""解码验证JWT令牌"""
|
||||
try:
|
||||
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
_get_jwt_secret_key(),
|
||||
algorithms=[JWT_ALGORITHM],
|
||||
issuer=_get_jwt_issuer(),
|
||||
audience=JWT_AUDIENCE,
|
||||
options={"require": ["exp", "sub", "iss", "aud"]},
|
||||
)
|
||||
return payload
|
||||
except jwt.PyJWTError:
|
||||
except (jwt.PyJWTError, ValueError):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def verify_access_token(token: str) -> dict[str, Any]:
|
||||
"""验证访问令牌,如果无效则抛出异常"""
|
||||
try:
|
||||
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
_get_jwt_secret_key(),
|
||||
algorithms=[JWT_ALGORITHM],
|
||||
issuer=_get_jwt_issuer(),
|
||||
audience=JWT_AUDIENCE,
|
||||
options={"require": ["exp", "sub", "iss", "aud"]},
|
||||
)
|
||||
return payload
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise ValueError("令牌已过期")
|
||||
|
||||
@ -25,7 +25,9 @@ async def test_user_is_locked_after_repeated_failed_logins(test_client, standard
|
||||
assert response.status_code == 401, response.text
|
||||
assert response.json()["detail"] == "用户名或密码错误"
|
||||
|
||||
locked_response = await test_client.post("/api/auth/token", data={"username": user_id, "password": "wrong-password"})
|
||||
locked_response = await test_client.post(
|
||||
"/api/auth/token", data={"username": user_id, "password": "wrong-password"}
|
||||
)
|
||||
assert locked_response.status_code == 423, locked_response.text
|
||||
assert "X-Lock-Remaining" in locked_response.headers
|
||||
assert "账户已被锁定" in locked_response.json()["detail"]
|
||||
@ -73,3 +75,24 @@ async def test_invalid_token_is_rejected(test_client):
|
||||
headers = {"Authorization": "Bearer not-a-real-token"}
|
||||
response = await test_client.get("/api/auth/me", headers=headers)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
async def test_deleted_user_token_is_rejected(test_client, admin_headers, standard_user):
|
||||
user_id = standard_user["user"]["id"]
|
||||
|
||||
delete_response = await test_client.delete(f"/api/auth/users/{user_id}", headers=admin_headers)
|
||||
assert delete_response.status_code == 200, delete_response.text
|
||||
|
||||
profile_response = await test_client.get("/api/auth/me", headers=standard_user["headers"])
|
||||
assert profile_response.status_code == 401
|
||||
|
||||
|
||||
async def test_locked_user_token_is_rejected(test_client, standard_user):
|
||||
user_id = standard_user["user"]["user_id"]
|
||||
|
||||
for _ in range(5):
|
||||
await test_client.post("/api/auth/token", data={"username": user_id, "password": "wrong-password"})
|
||||
|
||||
profile_response = await test_client.get("/api/auth/me", headers=standard_user["headers"])
|
||||
assert profile_response.status_code == 423
|
||||
assert "X-Lock-Remaining" in profile_response.headers
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from server.utils.auth_utils import AuthUtils
|
||||
import jwt
|
||||
import pytest
|
||||
from yuxi.utils.datetime_utils import utc_now
|
||||
|
||||
from server.utils.auth_utils import JWT_ALGORITHM, JWT_AUDIENCE, LEGACY_JWT_SECRET_KEY, AuthUtils
|
||||
|
||||
|
||||
def test_hash_password_uses_argon2():
|
||||
@ -18,3 +24,101 @@ def test_verify_password_accepts_legacy_sha256_format():
|
||||
|
||||
assert AuthUtils.verify_password(f"{legacy_hash}:salt", "secret-password") is True
|
||||
assert AuthUtils.verify_password(f"{legacy_hash}:salt", "wrong-password") is False
|
||||
|
||||
|
||||
def test_access_token_contains_instance_claims(monkeypatch):
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
|
||||
token = AuthUtils.create_access_token({"sub": "1"})
|
||||
payload = AuthUtils.verify_access_token(token)
|
||||
|
||||
assert payload["sub"] == "1"
|
||||
assert payload["iss"] == "yuxi-know:pytest-instance"
|
||||
assert payload["aud"] == JWT_AUDIENCE
|
||||
|
||||
|
||||
def test_access_token_auto_generates_dev_secret(monkeypatch):
|
||||
monkeypatch.setenv("YUXI_ENV", "development")
|
||||
monkeypatch.delenv("JWT_SECRET_KEY", raising=False)
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
|
||||
token = AuthUtils.create_access_token({"sub": "1"})
|
||||
|
||||
assert AuthUtils.verify_access_token(token)["sub"] == "1"
|
||||
assert len(os.environ["JWT_SECRET_KEY"]) == 64
|
||||
|
||||
|
||||
def test_access_token_requires_configured_secret_in_production(monkeypatch):
|
||||
monkeypatch.setenv("YUXI_ENV", "production")
|
||||
monkeypatch.delenv("JWT_SECRET_KEY", raising=False)
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
|
||||
with pytest.raises(ValueError, match="JWT_SECRET_KEY"):
|
||||
AuthUtils.create_access_token({"sub": "1"})
|
||||
|
||||
|
||||
def test_access_token_requires_non_default_secret(monkeypatch):
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", LEGACY_JWT_SECRET_KEY)
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
|
||||
with pytest.raises(ValueError, match="历史默认密钥"):
|
||||
AuthUtils.create_access_token({"sub": "1"})
|
||||
|
||||
|
||||
def test_access_token_auto_generates_dev_instance_id(monkeypatch):
|
||||
monkeypatch.setenv("YUXI_ENV", "development")
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
|
||||
monkeypatch.delenv("YUXI_INSTANCE_ID", raising=False)
|
||||
|
||||
token = AuthUtils.create_access_token({"sub": "1"})
|
||||
|
||||
assert AuthUtils.verify_access_token(token)["iss"].startswith("yuxi-know:instance-")
|
||||
assert os.environ["YUXI_INSTANCE_ID"].startswith("instance-")
|
||||
|
||||
|
||||
def test_access_token_requires_instance_id_in_production(monkeypatch):
|
||||
monkeypatch.setenv("YUXI_ENV", "production")
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
|
||||
monkeypatch.delenv("YUXI_INSTANCE_ID", raising=False)
|
||||
|
||||
with pytest.raises(ValueError, match="YUXI_INSTANCE_ID"):
|
||||
AuthUtils.create_access_token({"sub": "1"})
|
||||
|
||||
|
||||
def test_verify_access_token_rejects_wrong_issuer(monkeypatch):
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
token = jwt.encode(
|
||||
{"sub": "1", "exp": utc_now() + timedelta(minutes=5), "iss": "yuxi-know:other", "aud": JWT_AUDIENCE},
|
||||
"test-secret-key-with-enough-randomness",
|
||||
algorithm=JWT_ALGORITHM,
|
||||
)
|
||||
|
||||
assert AuthUtils.decode_token(token) is None
|
||||
|
||||
|
||||
def test_verify_access_token_rejects_wrong_audience(monkeypatch):
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
token = jwt.encode(
|
||||
{"sub": "1", "exp": utc_now() + timedelta(minutes=5), "iss": "yuxi-know:pytest-instance", "aud": "other-api"},
|
||||
"test-secret-key-with-enough-randomness",
|
||||
algorithm=JWT_ALGORITHM,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="无效的令牌"):
|
||||
AuthUtils.verify_access_token(token)
|
||||
|
||||
|
||||
def test_verify_access_token_requires_claims(monkeypatch):
|
||||
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
|
||||
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
|
||||
token = jwt.encode(
|
||||
{"sub": "1", "exp": utc_now() + timedelta(minutes=5)},
|
||||
"test-secret-key-with-enough-randomness",
|
||||
algorithm=JWT_ALGORITHM,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="无效的令牌"):
|
||||
AuthUtils.verify_access_token(token)
|
||||
|
||||
@ -2,6 +2,7 @@ x-api-worker-env: &api-worker-env
|
||||
HOST_IP: ${HOST_IP:-}
|
||||
LITE_MODE: ${LITE_MODE:-}
|
||||
RUNNING_IN_DOCKER: "true"
|
||||
YUXI_ENV: production
|
||||
MODEL_DIR_IN_DOCKER: /models
|
||||
PYTHONPATH: /app/package
|
||||
POSTGRES_URL: ${POSTGRES_URL:-postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-yuxi_know}}
|
||||
|
||||
@ -2,6 +2,7 @@ x-api-worker-env: &api-worker-env
|
||||
HOST_IP: ${HOST_IP:-}
|
||||
LITE_MODE: ${LITE_MODE:-false}
|
||||
RUNNING_IN_DOCKER: "true"
|
||||
YUXI_ENV: development
|
||||
MODEL_DIR_IN_DOCKER: /models
|
||||
# DBs and other services
|
||||
POSTGRES_URL: ${POSTGRES_URL:-postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-yuxi_know}}
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
### 0.6.2 开发记录
|
||||
|
||||
<!-- 0.6.2 的内容请放在这里 -->
|
||||
- 加固 JWT 鉴权安全:移除历史默认密钥回退,初始化脚本支持生成并持久化 `JWT_SECRET_KEY` 与 `YUXI_INSTANCE_ID`,签发和验证令牌时校验 `iss/aud`,并在鉴权阶段拒绝已删除或登录锁定用户继续使用旧令牌访问系统。
|
||||
- 扩展管理界面交互逻辑重构:将 MCP / Subagents / Skills 三个标签页从「左侧边栏 + 右侧详情面板」布局重构为「卡片式网格布局 + 路由跳转二级页面」布局,工具标签页改为卡片网格布局 + 弹窗详情(保持弹窗内容不变)。新增共享组件 `ExtensionCard`、`ExtensionCardGrid`、`ExtensionToolbar`、`ExtensionDetailLayout`,详情页(`McpDetailView`、`SubagentDetailView`、`SkillDetailView`)使用居中宽度限制,路由规划为 `/extensions/mcp/:name`、`/extensions/subagent/:name`、`/extensions/skill/:slug`。
|
||||
- 统一卡片样式:`ExtensionCard` 新增 `tags` prop 支持传入 `[{label, color}]` 数组,内部使用 `<a-tag bordered=false size=small>` 渲染,与知识库卡片标签风格统一;知识库列表页 `DataBaseView` 改用 `ExtensionCard` + `ExtensionCardGrid` 替代原有自定义卡片,移除冗余 card 样式。
|
||||
- 调整应用主导航:`AppLayout` 从默认窄栏升级为默认展开的侧边栏,保留折叠态图标导航;侧边栏样式收敛为 14px 文本 + 18px 图标的标准紧凑密度,并统一导航项、任务中心、GitHub、用户信息的图标与文字对齐。折叠态改为仅通过显式按钮展开,避免空白区域误触发。
|
||||
|
||||
@ -2,12 +2,49 @@
|
||||
# This script helps set up the environment for the Yuxi project
|
||||
# Note: API keys will be visible during input - use with care
|
||||
|
||||
function New-RandomHex($ByteCount) {
|
||||
$bytes = [byte[]]::new($ByteCount)
|
||||
[System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
|
||||
return -join ($bytes | ForEach-Object { $_.ToString("x2") })
|
||||
}
|
||||
|
||||
function Test-EnvValue($Name) {
|
||||
return [bool](Select-String -Path ".env" -Pattern "^$Name=.+" -Quiet)
|
||||
}
|
||||
|
||||
function Ensure-JwtEnv {
|
||||
if ((Test-EnvValue "JWT_SECRET_KEY") -and (Test-EnvValue "YUXI_INSTANCE_ID")) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "JWT security settings are missing in .env." -ForegroundColor Yellow
|
||||
$JWT_SECRET_KEY = Read-Host "Please enter your JWT_SECRET_KEY (press Enter to auto-generate)"
|
||||
if ([string]::IsNullOrEmpty($JWT_SECRET_KEY)) {
|
||||
$JWT_SECRET_KEY = New-RandomHex 32
|
||||
Write-Host "Generated JWT_SECRET_KEY and saved it to .env." -ForegroundColor Green
|
||||
}
|
||||
|
||||
$YUXI_INSTANCE_ID = Read-Host "Please enter your YUXI_INSTANCE_ID (press Enter to auto-generate)"
|
||||
if ([string]::IsNullOrEmpty($YUXI_INSTANCE_ID)) {
|
||||
$YUXI_INSTANCE_ID = "instance-$(New-RandomHex 8)"
|
||||
Write-Host "Generated YUXI_INSTANCE_ID and saved it to .env." -ForegroundColor Green
|
||||
}
|
||||
|
||||
@"
|
||||
|
||||
# JWT security settings
|
||||
JWT_SECRET_KEY=$JWT_SECRET_KEY
|
||||
YUXI_INSTANCE_ID=$YUXI_INSTANCE_ID
|
||||
"@ | Add-Content -Path ".env" -Encoding UTF8
|
||||
}
|
||||
|
||||
Write-Host "🚀 Initializing Yuxi project..." -ForegroundColor Cyan
|
||||
Write-Host "==================================" -ForegroundColor Cyan
|
||||
|
||||
# Check if .env file exists
|
||||
if (Test-Path ".env") {
|
||||
Write-Host "✅ .env file already exists. Skipping environment setup." -ForegroundColor Green
|
||||
Ensure-JwtEnv
|
||||
} else {
|
||||
Write-Host "📝 .env file not found. Let's set up your environment variables." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
@ -32,6 +69,20 @@ if (Test-Path ".env") {
|
||||
|
||||
$TAVILY_API_KEY = Read-Host "Please enter your TAVILY_API_KEY (press Enter to skip)"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "JWT security settings" -ForegroundColor Yellow
|
||||
$JWT_SECRET_KEY = Read-Host "Please enter your JWT_SECRET_KEY (press Enter to auto-generate)"
|
||||
if ([string]::IsNullOrEmpty($JWT_SECRET_KEY)) {
|
||||
$JWT_SECRET_KEY = New-RandomHex 32
|
||||
Write-Host "Generated JWT_SECRET_KEY and saved it to .env." -ForegroundColor Green
|
||||
}
|
||||
|
||||
$YUXI_INSTANCE_ID = Read-Host "Please enter your YUXI_INSTANCE_ID (press Enter to auto-generate)"
|
||||
if ([string]::IsNullOrEmpty($YUXI_INSTANCE_ID)) {
|
||||
$YUXI_INSTANCE_ID = "instance-$(New-RandomHex 8)"
|
||||
Write-Host "Generated YUXI_INSTANCE_ID and saved it to .env." -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Create .env file
|
||||
$envContent = @"
|
||||
# SiliconFlow API Key (required)
|
||||
@ -44,12 +95,21 @@ SILICONFLOW_API_KEY=$apiKey
|
||||
$envContent += "`nTAVILY_API_KEY=$TAVILY_API_KEY"
|
||||
}
|
||||
|
||||
$envContent += @"
|
||||
|
||||
# JWT security settings
|
||||
JWT_SECRET_KEY=$JWT_SECRET_KEY
|
||||
YUXI_INSTANCE_ID=$YUXI_INSTANCE_ID
|
||||
"@
|
||||
|
||||
$envContent | Out-File -FilePath ".env" -Encoding UTF8
|
||||
Write-Host "✅ .env file created successfully!" -ForegroundColor Green
|
||||
|
||||
# Clear the variables from memory
|
||||
Remove-Variable -Name "apiKey" -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name "TAVILY_API_KEY" -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name "JWT_SECRET_KEY" -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name "YUXI_INSTANCE_ID" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
@ -5,12 +5,49 @@
|
||||
|
||||
set -e
|
||||
|
||||
generate_hex() {
|
||||
local length="$1"
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -hex "$length"
|
||||
else
|
||||
tr -dc 'a-f0-9' < /dev/urandom | head -c $((length * 2))
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_jwt_env() {
|
||||
if grep -Eq '^JWT_SECRET_KEY=.+' .env && grep -Eq '^YUXI_INSTANCE_ID=.+' .env; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo "JWT security settings are missing in .env."
|
||||
read -s -p "Please enter your JWT_SECRET_KEY (press Enter to auto-generate): " JWT_SECRET_KEY
|
||||
echo ""
|
||||
if [ -z "$JWT_SECRET_KEY" ]; then
|
||||
JWT_SECRET_KEY=$(generate_hex 32)
|
||||
echo "Generated JWT_SECRET_KEY and saved it to .env."
|
||||
fi
|
||||
|
||||
read -p "Please enter your YUXI_INSTANCE_ID (press Enter to auto-generate): " YUXI_INSTANCE_ID
|
||||
if [ -z "$YUXI_INSTANCE_ID" ]; then
|
||||
YUXI_INSTANCE_ID="instance-$(generate_hex 8)"
|
||||
echo "Generated YUXI_INSTANCE_ID and saved it to .env."
|
||||
fi
|
||||
|
||||
cat >> .env << EOF
|
||||
|
||||
# JWT security settings
|
||||
JWT_SECRET_KEY=${JWT_SECRET_KEY}
|
||||
YUXI_INSTANCE_ID=${YUXI_INSTANCE_ID}
|
||||
EOF
|
||||
}
|
||||
|
||||
echo "🚀 Initializing Yuxi project..."
|
||||
echo "=================================="
|
||||
|
||||
# Check if .env file exists
|
||||
if [ -f ".env" ]; then
|
||||
echo "✅ .env file already exists. Skipping environment setup."
|
||||
ensure_jwt_env
|
||||
else
|
||||
echo "📝 .env file not found. Let's set up your environment variables."
|
||||
echo ""
|
||||
@ -34,6 +71,21 @@ else
|
||||
echo "Get your API key from: https://app.tavily.com/"
|
||||
read -p "Please enter your TAVILY_API_KEY (press Enter to skip): " TAVILY_API_KEY
|
||||
|
||||
echo ""
|
||||
echo "JWT security settings"
|
||||
read -s -p "Please enter your JWT_SECRET_KEY (press Enter to auto-generate): " JWT_SECRET_KEY
|
||||
echo ""
|
||||
if [ -z "$JWT_SECRET_KEY" ]; then
|
||||
JWT_SECRET_KEY=$(generate_hex 32)
|
||||
echo "Generated JWT_SECRET_KEY and saved it to .env."
|
||||
fi
|
||||
|
||||
read -p "Please enter your YUXI_INSTANCE_ID (press Enter to auto-generate): " YUXI_INSTANCE_ID
|
||||
if [ -z "$YUXI_INSTANCE_ID" ]; then
|
||||
YUXI_INSTANCE_ID="instance-$(generate_hex 8)"
|
||||
echo "Generated YUXI_INSTANCE_ID and saved it to .env."
|
||||
fi
|
||||
|
||||
# Create .env file
|
||||
cat > .env << EOF
|
||||
# SiliconFlow API Key (required)
|
||||
@ -46,6 +98,13 @@ EOF
|
||||
echo "TAVILY_API_KEY=${TAVILY_API_KEY}" >> .env
|
||||
fi
|
||||
|
||||
cat >> .env << EOF
|
||||
|
||||
# JWT security settings
|
||||
JWT_SECRET_KEY=${JWT_SECRET_KEY}
|
||||
YUXI_INSTANCE_ID=${YUXI_INSTANCE_ID}
|
||||
EOF
|
||||
|
||||
echo "✅ .env file created successfully!"
|
||||
fi
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user