diff --git a/.github/drone.yml b/.github/drone.yml index 6b1410b3e..4fda0657e 100644 --- a/.github/drone.yml +++ b/.github/drone.yml @@ -105,7 +105,7 @@ steps: - name: docker-socket path: /var/run/docker.sock commands: - - apk add --no-cache docker-cli-compose curl bash + - apk add bash - echo "========================================" - echo "本地部署测试 全新部署验证" - echo "========================================" @@ -159,7 +159,7 @@ steps: - name: docker-socket path: /var/run/docker.sock commands: - - apk add --no-cache docker-cli-compose curl bash + - apk add bash - echo "========================================" - echo "本地升级测试 旧版 → 新版数据库迁移验证" - echo "========================================" @@ -292,6 +292,7 @@ trigger: branch: - main - release + - ci/test-drone # 临时添加,测试完成后删除 event: - push - pull_request diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index e943dee70..091cdeb8a 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -175,6 +175,13 @@ async def register_init( detail="Username already taken. Please choose a different username.", ) + # Reject registration if the identity exists but has no password set (SSO/synced users) + if identity.password_hash is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email already registered via SSO/sync. Please use password reset to set a password, or log in via SSO.", + ) + # Verify password outside transaction if identity.password_hash and not await verify_password_async(data.password, identity.password_hash): raise HTTPException( @@ -223,6 +230,8 @@ async def register_init( user.is_active = is_first_user # Active immediately if first user user.email_verified = identity.email_verified await session.flush() + else: + user.identity = identity # 5. Generate token outside transaction token = create_access_token(str(user.id), user.role) diff --git a/backend/app/services/registration_service.py b/backend/app/services/registration_service.py index a999d5fac..b4bdc4302 100644 --- a/backend/app/services/registration_service.py +++ b/backend/app/services/registration_service.py @@ -182,6 +182,7 @@ async def create_user_with_identity( "registration_source": registration_source, "is_active": is_active or identity.is_platform_admin, }) + user.identity = identity # Link to OrgMember if exists await self.bind_org_member(user) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 000000000..a3aac3c19 --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,65 @@ +# ============================================================ +# CI 专用 Docker Compose +# 用于 Drone CI 的本地部署测试和升级测试 +# 使用预构建镜像,不构建、不挂载持久化卷 +# ============================================================ + +services: + postgres: + image: postgres:15-alpine + networks: + - default + environment: + POSTGRES_USER: clawith + POSTGRES_PASSWORD: clawith + POSTGRES_DB: clawith + healthcheck: + test: ["CMD-SHELL", "pg_isready -U clawith"] + interval: 5s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + networks: + - default + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + + backend: + image: clawith-backend:${IMAGE_TAG:-new} + environment: + DATABASE_URL: postgresql+asyncpg://clawith:clawith@postgres:5432/clawith + REDIS_URL: redis://redis:6379/0 + AGENT_DATA_DIR: /data/agents + AGENT_TEMPLATE_DIR: /app/agent_template + SECRET_KEY: ci-test-secret + JWT_SECRET_KEY: ci-test-jwt-secret + PROCESS_ROLE: all + CORS_ORIGINS: '["*"]' + networks: + - default + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + frontend: + image: clawith-frontend:${IMAGE_TAG:-new} + ports: + - "3008:3000" + environment: + VITE_API_URL: http://localhost:8000 + API_UPSTREAM: backend:8000 + networks: + - default + depends_on: + - backend + +networks: + default: + name: clawith_network