Commit cccb7cfced6a7ecc4eab981512eb0421dd2d4d92

Authored by tangwang
0 parents

init

oauth_setup_guide.md 0 → 100644
  1 +++ a/oauth_setup_guide.md
... ... @@ -0,0 +1,120 @@
  1 +# Shoplazza OAuth2.0 认证设置指南
  2 +
  3 +## 第一步:获取应用凭证
  4 +
  5 +1. **访问Shoplazza开发者平台**
  6 + - 打开 https://partners.shoplazza.com/
  7 + - 登录您的账户
  8 +
  9 +2. **创建新应用**
  10 + - 点击"创建应用"
  11 + - 选择"公共应用"
  12 + - 填写应用信息
  13 +
  14 +3. **获取凭证**
  15 + - 复制 `CLIENT_ID`
  16 + - 复制 `CLIENT_SECRET`
  17 +
  18 +## 第二步:配置本地环境
  19 +
  20 +### 1. 安装依赖
  21 +```bash
  22 +pip install -r requirements.txt
  23 +```
  24 +
  25 +### 2. 创建环境配置文件
  26 +创建 `.env` 文件:
  27 +```env
  28 +# Shoplazza OAuth2.0 配置
  29 +CLIENT_ID=your_actual_client_id
  30 +CLIENT_SECRET=your_actual_client_secret
  31 +BASE_URL=https://your-ngrok-url.ngrok.io
  32 +REDIRECT_URI=/auth/shoplazza/callback
  33 +
  34 +# Flask 应用配置
  35 +SECRET_KEY=your-secret-key-here
  36 +FLASK_ENV=development
  37 +FLASK_DEBUG=True
  38 +PORT=3000
  39 +```
  40 +
  41 +### 3. 使用ngrok暴露本地服务
  42 +```bash
  43 +# 安装ngrok
  44 +npm install -g ngrok
  45 +
  46 +# 启动ngrok
  47 +ngrok http 3000
  48 +
  49 +# 复制ngrok提供的HTTPS URL到BASE_URL
  50 +```
  51 +
  52 +## 第三步:配置Shoplazza应用
  53 +
  54 +在Shoplazza开发者中心配置以下URL:
  55 +
  56 +1. **应用URL**: `https://your-ngrok-url.ngrok.io/auth/install`
  57 +2. **回调URL**: `https://your-ngrok-url.ngrok.io/auth/shoplazza/callback`
  58 +3. **Webhook URL**: `https://your-ngrok-url.ngrok.io/webhook/shoplazza`
  59 +
  60 +## 第四步:启动应用
  61 +
  62 +```bash
  63 +python run.py
  64 +```
  65 +
  66 +## 第五步:测试OAuth流程
  67 +
  68 +### 1. 开始认证
  69 +访问以下URL(替换为实际的商店域名):
  70 +```
  71 +https://your-ngrok-url.ngrok.io/auth/install?shop=your-shop.myshoplaza.com
  72 +```
  73 +
  74 +### 2. 完成授权
  75 +- 系统会重定向到Shoplazza授权页面
  76 +- 商家确认授权
  77 +- 系统自动处理回调并获取访问令牌
  78 +
  79 +### 3. 测试API调用
  80 +```bash
  81 +# 获取客户列表
  82 +curl https://your-ngrok-url.ngrok.io/api/customers/your-shop.myshoplaza.com
  83 +
  84 +# 获取产品列表
  85 +curl https://your-ngrok-url.ngrok.io/api/products/your-shop.myshoplaza.com
  86 +```
  87 +
  88 +## 故障排除
  89 +
  90 +### 常见问题
  91 +
  92 +1. **HMAC验证失败**
  93 + - 检查CLIENT_SECRET是否正确
  94 + - 确认请求来自Shoplazza
  95 +
  96 +2. **授权失败**
  97 + - 检查回调URL配置
  98 + - 确认BASE_URL使用HTTPS
  99 +
  100 +3. **API调用失败**
  101 + - 检查访问令牌是否有效
  102 + - 确认权限范围正确
  103 +
  104 +### 调试技巧
  105 +
  106 +1. **查看日志**
  107 + ```bash
  108 + # 应用会输出详细的日志信息
  109 + python run.py
  110 + ```
  111 +
  112 +2. **检查令牌状态**
  113 + ```bash
  114 + curl https://your-ngrok-url.ngrok.io/auth/tokens
  115 + ```
  116 +
  117 +3. **健康检查**
  118 + ```bash
  119 + curl https://your-ngrok-url.ngrok.io/health
  120 + ```
... ...
old/Develop your application service.md 0 → 100644
  1 +++ a/old/Develop your application service.md
... ...
old/PROJECT_STRUCTURE.md 0 → 100644
  1 +++ a/old/PROJECT_STRUCTURE.md
... ... @@ -0,0 +1,76 @@
  1 +# 项目结构说明
  2 +
  3 +## 文件组织
  4 +
  5 +```
  6 +shoplazza-oauth-backend/
  7 +├── app.py # 主应用入口
  8 +├── config.py # 配置管理
  9 +├── run.py # 启动脚本
  10 +├── requirements.txt # Python依赖
  11 +├── env_template.txt # 环境变量模板
  12 +├── README.md # 项目文档
  13 +├── PROJECT_STRUCTURE.md # 项目结构说明
  14 +├── middleware/
  15 +│ └── hmac_validator.py # HMAC验证中间件
  16 +└── routes/
  17 + ├── auth.py # OAuth2.0认证路由
  18 + ├── api.py # Shoplazza API调用
  19 + └── webhook.py # Webhook处理
  20 +```
  21 +
  22 +## 核心组件
  23 +
  24 +### 1. 配置管理 (config.py)
  25 +- 环境变量加载
  26 +- 应用配置管理
  27 +- 访问令牌存储
  28 +
  29 +### 2. HMAC验证 (middleware/hmac_validator.py)
  30 +- OAuth请求HMAC验证
  31 +- Webhook签名验证
  32 +- 安全比较函数
  33 +
  34 +### 3. 认证流程 (routes/auth.py)
  35 +- 应用安装处理
  36 +- OAuth回调处理
  37 +- 令牌交换和刷新
  38 +
  39 +### 4. API调用 (routes/api.py)
  40 +- 客户数据获取
  41 +- 产品数据获取
  42 +- 订单数据获取
  43 +- 商店信息获取
  44 +
  45 +### 5. Webhook处理 (routes/webhook.py)
  46 +- Webhook签名验证
  47 +- 事件处理逻辑
  48 +- 应用卸载处理
  49 +
  50 +## 数据流
  51 +
  52 +```
  53 +1. 商家安装应用
  54 + ↓
  55 +2. 重定向到Shoplazza授权页面
  56 + ↓
  57 +3. 商家确认授权
  58 + ↓
  59 +4. Shoplazza回调应用
  60 + ↓
  61 +5. HMAC验证
  62 + ↓
  63 +6. 交换访问令牌
  64 + ↓
  65 +7. 存储令牌
  66 + ↓
  67 +8. 调用Shoplazza API
  68 +```
  69 +
  70 +## 安全特性
  71 +
  72 +- HMAC签名验证
  73 +- CSRF攻击防护
  74 +- 安全的令牌存储
  75 +- Webhook签名验证
  76 +- 环境变量配置管理
... ...
old/README.md 0 → 100644
  1 +++ a/old/README.md
... ... @@ -0,0 +1,153 @@
  1 +# Shoplazza OAuth2.0 后端应用
  2 +
  3 +这是一个基于Python Flask的Shoplazza OAuth2.0认证后端应用,实现了完整的OAuth2.0认证流程和API调用功能。
  4 +
  5 +## 功能特性
  6 +
  7 +- ✅ OAuth2.0 认证流程
  8 +- ✅ HMAC 签名验证
  9 +- ✅ 访问令牌管理
  10 +- ✅ 令牌刷新功能
  11 +- ✅ Shoplazza API 调用
  12 +- ✅ Webhook 处理
  13 +- ✅ 安全验证中间件
  14 +
  15 +## 项目结构
  16 +
  17 +```
  18 +├── app.py # 主应用入口
  19 +├── config.py # 配置文件
  20 +├── requirements.txt # 依赖包
  21 +├── middleware/
  22 +│ └── hmac_validator.py # HMAC验证中间件
  23 +├── routes/
  24 +│ ├── auth.py # 认证路由
  25 +│ ├── api.py # API调用路由
  26 +│ └── webhook.py # Webhook处理路由
  27 +└── README.md # 项目说明
  28 +```
  29 +
  30 +## 安装和配置
  31 +
  32 +### 1. 安装依赖
  33 +
  34 +```bash
  35 +pip install -r requirements.txt
  36 +```
  37 +
  38 +### 2. 环境配置
  39 +
  40 +创建 `.env` 文件并配置以下参数:
  41 +
  42 +```env
  43 +# Shoplazza OAuth2.0 配置
  44 +CLIENT_ID=your_client_id_here
  45 +CLIENT_SECRET=your_client_secret_here
  46 +BASE_URL=https://your-domain.com
  47 +REDIRECT_URI=/auth/shoplazza/callback
  48 +
  49 +# 应用配置
  50 +FLASK_ENV=development
  51 +FLASK_DEBUG=True
  52 +PORT=3000
  53 +SECRET_KEY=your-secret-key-here
  54 +```
  55 +
  56 +### 3. 运行应用
  57 +
  58 +```bash
  59 +python app.py
  60 +```
  61 +
  62 +## API 端点
  63 +
  64 +### 认证端点
  65 +
  66 +- `GET /auth/install?shop=your-shop.myshoplaza.com` - 开始OAuth认证流程
  67 +- `GET /auth/shoplazza/callback` - OAuth回调处理
  68 +- `GET /auth/refresh_token/<shop>` - 刷新访问令牌
  69 +- `GET /auth/tokens` - 查看已授权的商店令牌
  70 +
  71 +### API调用端点
  72 +
  73 +- `GET /api/customers/<shop>` - 获取客户列表
  74 +- `GET /api/products/<shop>` - 获取产品列表
  75 +- `GET /api/orders/<shop>` - 获取订单列表
  76 +- `GET /api/shop_info/<shop>` - 获取商店信息
  77 +
  78 +### Webhook端点
  79 +
  80 +- `POST /webhook/shoplazza` - 处理Shoplazza Webhook
  81 +
  82 +## 使用示例
  83 +
  84 +### 1. 开始认证流程
  85 +
  86 +访问以下URL开始OAuth认证:
  87 +```
  88 +https://your-domain.com/auth/install?shop=your-shop.myshoplaza.com
  89 +```
  90 +
  91 +### 2. 调用API
  92 +
  93 +认证成功后,可以调用Shoplazza API:
  94 +```bash
  95 +curl https://your-domain.com/api/customers/your-shop.myshoplaza.com
  96 +```
  97 +
  98 +### 3. 处理Webhook
  99 +
  100 +在Shoplazza开发者中心配置Webhook URL:
  101 +```
  102 +https://your-domain.com/webhook/shoplazza
  103 +```
  104 +
  105 +## OAuth2.0 认证流程
  106 +
  107 +1. **应用安装**: 商家从应用商店安装应用
  108 +2. **授权请求**: 应用重定向到Shoplazza授权页面
  109 +3. **用户授权**: 商家确认授权
  110 +4. **授权回调**: Shoplazza重定向回应用并携带授权码
  111 +5. **令牌交换**: 应用使用授权码交换访问令牌
  112 +6. **API调用**: 使用访问令牌调用Shoplazza API
  113 +
  114 +## 安全特性
  115 +
  116 +- HMAC签名验证确保请求来自Shoplazza
  117 +- 安全的令牌存储和管理
  118 +- CSRF攻击防护
  119 +- Webhook签名验证
  120 +
  121 +## 开发说明
  122 +
  123 +### 本地开发
  124 +
  125 +1. 使用ngrok等工具将本地服务暴露到公网
  126 +2. 在Shoplazza开发者中心配置应用URL
  127 +3. 确保所有回调URL使用HTTPS
  128 +
  129 +### 生产部署
  130 +
  131 +1. 使用环境变量管理敏感配置
  132 +2. 使用数据库存储访问令牌
  133 +3. 配置适当的日志记录
  134 +4. 设置监控和告警
  135 +
  136 +## 故障排除
  137 +
  138 +### 常见问题
  139 +
  140 +1. **HMAC验证失败**: 检查CLIENT_SECRET配置
  141 +2. **授权失败**: 确认回调URL配置正确
  142 +3. **API调用失败**: 检查访问令牌是否有效
  143 +
  144 +### 日志查看
  145 +
  146 +应用会记录详细的日志信息,包括:
  147 +- 认证流程状态
  148 +- API调用结果
  149 +- 错误信息
  150 +
  151 +## 许可证
  152 +
  153 +MIT License
... ...
old/__pycache__/config.cpython-311.pyc 0 → 100644
No preview for this file type
old/app.py 0 → 100644
  1 +++ a/old/app.py
... ... @@ -0,0 +1,64 @@
  1 +from flask import Flask, jsonify
  2 +from config import Config
  3 +from routes.auth import auth_bp
  4 +from routes.api import api_bp
  5 +from routes.webhook import webhook_bp
  6 +import logging
  7 +
  8 +def create_app():
  9 + """创建Flask应用"""
  10 + app = Flask(__name__)
  11 +
  12 + # 配置应用
  13 + app.config.from_object(Config)
  14 +
  15 + # 配置日志
  16 + logging.basicConfig(level=logging.INFO)
  17 +
  18 + # 注册蓝图
  19 + app.register_blueprint(auth_bp)
  20 + app.register_blueprint(api_bp)
  21 + app.register_blueprint(webhook_bp)
  22 +
  23 + # 根路由
  24 + @app.route('/')
  25 + def index():
  26 + return jsonify({
  27 + 'message': 'Shoplazza OAuth2.0 Backend Service',
  28 + 'version': '1.0.0',
  29 + 'endpoints': {
  30 + 'auth': {
  31 + 'install': '/auth/install?shop=your-shop.myshoplaza.com',
  32 + 'callback': '/auth/shoplazza/callback',
  33 + 'refresh_token': '/auth/refresh_token/<shop>',
  34 + 'tokens': '/auth/tokens'
  35 + },
  36 + 'api': {
  37 + 'customers': '/api/customers/<shop>',
  38 + 'products': '/api/products/<shop>',
  39 + 'orders': '/api/orders/<shop>',
  40 + 'shop_info': '/api/shop_info/<shop>'
  41 + },
  42 + 'webhook': {
  43 + 'shoplazza': '/webhook/shoplazza'
  44 + }
  45 + }
  46 + })
  47 +
  48 + # 健康检查端点
  49 + @app.route('/health')
  50 + def health():
  51 + return jsonify({
  52 + 'status': 'healthy',
  53 + 'authorized_shops': len(Config.ACCESS_TOKENS)
  54 + })
  55 +
  56 + return app
  57 +
  58 +if __name__ == '__main__':
  59 + app = create_app()
  60 + app.run(
  61 + host='0.0.0.0',
  62 + port=Config.PORT,
  63 + debug=Config.DEBUG
  64 + )
... ...
old/config.py 0 → 100644
  1 +++ a/old/config.py
... ... @@ -0,0 +1,22 @@
  1 +import os
  2 +from dotenv import load_dotenv
  3 +
  4 +# 加载环境变量
  5 +load_dotenv()
  6 +
  7 +class Config:
  8 + """Shoplazza OAuth2.0 应用配置"""
  9 +
  10 + # Shoplazza OAuth2.0 配置
  11 + CLIENT_ID = os.getenv('CLIENT_ID', 'your_client_id_here')
  12 + CLIENT_SECRET = os.getenv('CLIENT_SECRET', 'your_client_secret_here')
  13 + BASE_URL = os.getenv('BASE_URL', 'https://your-domain.com')
  14 + REDIRECT_URI = os.getenv('REDIRECT_URI', '/auth/shoplazza/callback')
  15 +
  16 + # Flask 配置
  17 + SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-here')
  18 + DEBUG = os.getenv('FLASK_DEBUG', 'True').lower() == 'true'
  19 + PORT = int(os.getenv('PORT', 3000))
  20 +
  21 + # 存储访问令牌的字典(生产环境应使用数据库)
  22 + ACCESS_TOKENS = {}
... ...
old/env_template.txt 0 → 100644
  1 +++ a/old/env_template.txt
... ... @@ -0,0 +1,16 @@
  1 +# Shoplazza OAuth2.0 配置模板
  2 +# 复制此文件为 .env 并填入实际值
  3 +
  4 +# Shoplazza 应用配置 (从 https://partners.shoplazza.com/ 获取)
  5 +CLIENT_ID=your_client_id_here
  6 +CLIENT_SECRET=your_client_secret_here
  7 +
  8 +# 应用基础URL (使用ngrok等工具暴露本地服务)
  9 +BASE_URL=https://your-domain.com
  10 +REDIRECT_URI=/auth/shoplazza/callback
  11 +
  12 +# Flask 应用配置
  13 +SECRET_KEY=your-secret-key-here
  14 +FLASK_ENV=development
  15 +FLASK_DEBUG=True
  16 +PORT=3000
... ...
old/middleware/__pycache__/hmac_validator.cpython-311.pyc 0 → 100644
No preview for this file type
old/middleware/hmac_validator.py 0 → 100644
  1 +++ a/old/middleware/hmac_validator.py
... ... @@ -0,0 +1,59 @@
  1 +import hmac
  2 +import hashlib
  3 +from flask import request, jsonify
  4 +from functools import wraps
  5 +from config import Config
  6 +
  7 +def hmac_validator_required(f):
  8 + """
  9 + HMAC验证装饰器
  10 + 验证来自Shoplazza的请求是否有效
  11 + """
  12 + @wraps(f)
  13 + def decorated_function(*args, **kwargs):
  14 + # 获取查询参数
  15 + query_params = request.args.to_dict()
  16 + hmac_param = query_params.pop('hmac', None)
  17 +
  18 + if not hmac_param:
  19 + return jsonify({'error': 'Missing HMAC parameter'}), 400
  20 +
  21 + # 验证shop参数格式
  22 + shop = query_params.get('shop')
  23 + if not shop or not shop.endswith('.myshoplaza.com'):
  24 + return jsonify({'error': 'Invalid shop parameter'}), 400
  25 +
  26 + # 构建验证消息
  27 + sorted_keys = sorted(query_params.keys())
  28 + message = '&'.join([f"{key}={query_params[key]}" for key in sorted_keys])
  29 +
  30 + # 计算HMAC
  31 + calculated_hmac = hmac.new(
  32 + Config.CLIENT_SECRET.encode('utf-8'),
  33 + message.encode('utf-8'),
  34 + hashlib.sha256
  35 + ).hexdigest()
  36 +
  37 + # 安全比较HMAC
  38 + if not hmac.compare_digest(calculated_hmac, hmac_param):
  39 + return jsonify({'error': 'HMAC validation failed'}), 403
  40 +
  41 + return f(*args, **kwargs)
  42 +
  43 + return decorated_function
  44 +
  45 +def verify_webhook_hmac(data, hmac_header):
  46 + """
  47 + 验证Webhook的HMAC签名
  48 + """
  49 + import base64
  50 +
  51 + calculated_hmac = base64.b64encode(
  52 + hmac.new(
  53 + Config.CLIENT_SECRET.encode('utf-8'),
  54 + data,
  55 + hashlib.sha256
  56 + ).digest()
  57 + ).decode('utf-8')
  58 +
  59 + return hmac.compare_digest(calculated_hmac, hmac_header)
... ...
old/oauth_setup_guide.md 0 → 100644
  1 +++ a/old/oauth_setup_guide.md
... ... @@ -0,0 +1,120 @@
  1 +# Shoplazza OAuth2.0 认证设置指南
  2 +
  3 +## 第一步:获取应用凭证
  4 +
  5 +1. **访问Shoplazza开发者平台**
  6 + - 打开 https://partners.shoplazza.com/
  7 + - 登录您的账户
  8 +
  9 +2. **创建新应用**
  10 + - 点击"创建应用"
  11 + - 选择"公共应用"
  12 + - 填写应用信息
  13 +
  14 +3. **获取凭证**
  15 + - 复制 `CLIENT_ID`
  16 + - 复制 `CLIENT_SECRET`
  17 +
  18 +## 第二步:配置本地环境
  19 +
  20 +### 1. 安装依赖
  21 +```bash
  22 +pip install -r requirements.txt
  23 +```
  24 +
  25 +### 2. 创建环境配置文件
  26 +创建 `.env` 文件:
  27 +```env
  28 +# Shoplazza OAuth2.0 配置
  29 +CLIENT_ID=your_actual_client_id
  30 +CLIENT_SECRET=your_actual_client_secret
  31 +BASE_URL=https://your-ngrok-url.ngrok.io
  32 +REDIRECT_URI=/auth/shoplazza/callback
  33 +
  34 +# Flask 应用配置
  35 +SECRET_KEY=your-secret-key-here
  36 +FLASK_ENV=development
  37 +FLASK_DEBUG=True
  38 +PORT=3000
  39 +```
  40 +
  41 +### 3. 使用ngrok暴露本地服务
  42 +```bash
  43 +# 安装ngrok
  44 +npm install -g ngrok
  45 +
  46 +# 启动ngrok
  47 +ngrok http 3000
  48 +
  49 +# 复制ngrok提供的HTTPS URL到BASE_URL
  50 +```
  51 +
  52 +## 第三步:配置Shoplazza应用
  53 +
  54 +在Shoplazza开发者中心配置以下URL:
  55 +
  56 +1. **应用URL**: `https://your-ngrok-url.ngrok.io/auth/install`
  57 +2. **回调URL**: `https://your-ngrok-url.ngrok.io/auth/shoplazza/callback`
  58 +3. **Webhook URL**: `https://your-ngrok-url.ngrok.io/webhook/shoplazza`
  59 +
  60 +## 第四步:启动应用
  61 +
  62 +```bash
  63 +python run.py
  64 +```
  65 +
  66 +## 第五步:测试OAuth流程
  67 +
  68 +### 1. 开始认证
  69 +访问以下URL(替换为实际的商店域名):
  70 +```
  71 +https://your-ngrok-url.ngrok.io/auth/install?shop=your-shop.myshoplaza.com
  72 +```
  73 +
  74 +### 2. 完成授权
  75 +- 系统会重定向到Shoplazza授权页面
  76 +- 商家确认授权
  77 +- 系统自动处理回调并获取访问令牌
  78 +
  79 +### 3. 测试API调用
  80 +```bash
  81 +# 获取客户列表
  82 +curl https://your-ngrok-url.ngrok.io/api/customers/your-shop.myshoplaza.com
  83 +
  84 +# 获取产品列表
  85 +curl https://your-ngrok-url.ngrok.io/api/products/your-shop.myshoplaza.com
  86 +```
  87 +
  88 +## 故障排除
  89 +
  90 +### 常见问题
  91 +
  92 +1. **HMAC验证失败**
  93 + - 检查CLIENT_SECRET是否正确
  94 + - 确认请求来自Shoplazza
  95 +
  96 +2. **授权失败**
  97 + - 检查回调URL配置
  98 + - 确认BASE_URL使用HTTPS
  99 +
  100 +3. **API调用失败**
  101 + - 检查访问令牌是否有效
  102 + - 确认权限范围正确
  103 +
  104 +### 调试技巧
  105 +
  106 +1. **查看日志**
  107 + ```bash
  108 + # 应用会输出详细的日志信息
  109 + python run.py
  110 + ```
  111 +
  112 +2. **检查令牌状态**
  113 + ```bash
  114 + curl https://your-ngrok-url.ngrok.io/auth/tokens
  115 + ```
  116 +
  117 +3. **健康检查**
  118 + ```bash
  119 + curl https://your-ngrok-url.ngrok.io/health
  120 + ```
... ...
old/requirements.txt 0 → 100644
  1 +++ a/old/requirements.txt
... ... @@ -0,0 +1,4 @@
  1 +Flask==2.3.3
  2 +requests==2.31.0
  3 +python-dotenv==1.0.0
  4 +cryptography==41.0.4
... ...
old/routes/__pycache__/api.cpython-311.pyc 0 → 100644
No preview for this file type
old/routes/__pycache__/auth.cpython-311.pyc 0 → 100644
No preview for this file type
old/routes/__pycache__/webhook.cpython-311.pyc 0 → 100644
No preview for this file type
old/routes/api.py 0 → 100644
  1 +++ a/old/routes/api.py
... ... @@ -0,0 +1,142 @@
  1 +import requests
  2 +from flask import Blueprint, jsonify, request, current_app
  3 +from config import Config
  4 +
  5 +# 创建API蓝图
  6 +api_bp = Blueprint('api', __name__, url_prefix='/api')
  7 +
  8 +@api_bp.route('/customers/<shop>')
  9 +def get_customers(shop):
  10 + """
  11 + 获取客户列表
  12 + """
  13 + if shop not in Config.ACCESS_TOKENS:
  14 + return jsonify({'error': 'Shop not authorized'}), 401
  15 +
  16 + token_data = Config.ACCESS_TOKENS[shop]
  17 + access_token = token_data.get('access_token')
  18 +
  19 + if not access_token:
  20 + return jsonify({'error': 'No access token available'}), 401
  21 +
  22 + try:
  23 + # 调用Shoplazza API获取客户列表
  24 + api_url = f"https://{shop}/openapi/2022-01/customers"
  25 + headers = {
  26 + 'Access-Token': access_token,
  27 + 'Content-Type': 'application/json'
  28 + }
  29 +
  30 + response = requests.get(api_url, headers=headers)
  31 + response.raise_for_status()
  32 +
  33 + return jsonify({
  34 + 'shop': shop,
  35 + 'customers': response.json()
  36 + })
  37 +
  38 + except Exception as e:
  39 + current_app.logger.error(f"获取客户列表失败: {str(e)}")
  40 + return jsonify({'error': 'Failed to fetch customers'}), 500
  41 +
  42 +@api_bp.route('/products/<shop>')
  43 +def get_products(shop):
  44 + """
  45 + 获取产品列表
  46 + """
  47 + if shop not in Config.ACCESS_TOKENS:
  48 + return jsonify({'error': 'Shop not authorized'}), 401
  49 +
  50 + token_data = Config.ACCESS_TOKENS[shop]
  51 + access_token = token_data.get('access_token')
  52 +
  53 + if not access_token:
  54 + return jsonify({'error': 'No access token available'}), 401
  55 +
  56 + try:
  57 + # 调用Shoplazza API获取产品列表
  58 + api_url = f"https://{shop}/openapi/2020-01/products"
  59 + headers = {
  60 + 'Access-Token': access_token,
  61 + 'Content-Type': 'application/json'
  62 + }
  63 +
  64 + response = requests.get(api_url, headers=headers)
  65 + response.raise_for_status()
  66 +
  67 + return jsonify({
  68 + 'shop': shop,
  69 + 'products': response.json()
  70 + })
  71 +
  72 + except Exception as e:
  73 + current_app.logger.error(f"获取产品列表失败: {str(e)}")
  74 + return jsonify({'error': 'Failed to fetch products'}), 500
  75 +
  76 +@api_bp.route('/orders/<shop>')
  77 +def get_orders(shop):
  78 + """
  79 + 获取订单列表
  80 + """
  81 + if shop not in Config.ACCESS_TOKENS:
  82 + return jsonify({'error': 'Shop not authorized'}), 401
  83 +
  84 + token_data = Config.ACCESS_TOKENS[shop]
  85 + access_token = token_data.get('access_token')
  86 +
  87 + if not access_token:
  88 + return jsonify({'error': 'No access token available'}), 401
  89 +
  90 + try:
  91 + # 调用Shoplazza API获取订单列表
  92 + api_url = f"https://{shop}/openapi/2020-01/orders"
  93 + headers = {
  94 + 'Access-Token': access_token,
  95 + 'Content-Type': 'application/json'
  96 + }
  97 +
  98 + response = requests.get(api_url, headers=headers)
  99 + response.raise_for_status()
  100 +
  101 + return jsonify({
  102 + 'shop': shop,
  103 + 'orders': response.json()
  104 + })
  105 +
  106 + except Exception as e:
  107 + current_app.logger.error(f"获取订单列表失败: {str(e)}")
  108 + return jsonify({'error': 'Failed to fetch orders'}), 500
  109 +
  110 +@api_bp.route('/shop_info/<shop>')
  111 +def get_shop_info(shop):
  112 + """
  113 + 获取商店信息
  114 + """
  115 + if shop not in Config.ACCESS_TOKENS:
  116 + return jsonify({'error': 'Shop not authorized'}), 401
  117 +
  118 + token_data = Config.ACCESS_TOKENS[shop]
  119 + access_token = token_data.get('access_token')
  120 +
  121 + if not access_token:
  122 + return jsonify({'error': 'No access token available'}), 401
  123 +
  124 + try:
  125 + # 调用Shoplazza API获取商店信息
  126 + api_url = f"https://{shop}/openapi/2020-01/shop"
  127 + headers = {
  128 + 'Access-Token': access_token,
  129 + 'Content-Type': 'application/json'
  130 + }
  131 +
  132 + response = requests.get(api_url, headers=headers)
  133 + response.raise_for_status()
  134 +
  135 + return jsonify({
  136 + 'shop': shop,
  137 + 'shop_info': response.json()
  138 + })
  139 +
  140 + except Exception as e:
  141 + current_app.logger.error(f"获取商店信息失败: {str(e)}")
  142 + return jsonify({'error': 'Failed to fetch shop info'}), 500
... ...
old/routes/auth.py 0 → 100644
  1 +++ a/old/routes/auth.py
... ... @@ -0,0 +1,143 @@
  1 +import secrets
  2 +import requests
  3 +from flask import Blueprint, request, redirect, jsonify, current_app
  4 +from config import Config
  5 +from middleware.hmac_validator import hmac_validator_required
  6 +
  7 +# 创建认证蓝图
  8 +auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
  9 +
  10 +@auth_bp.route('/install')
  11 +def install():
  12 + """
  13 + Step 1: 处理应用安装请求
  14 + 当商家从应用商店安装应用时,Shoplazza会发送请求到这个端点
  15 + """
  16 + shop = request.args.get('shop')
  17 + if not shop:
  18 + return jsonify({'error': 'Missing shop parameter'}), 400
  19 +
  20 + # 验证shop参数格式
  21 + if not shop.endswith('.myshoplaza.com'):
  22 + return jsonify({'error': 'Invalid shop parameter'}), 400
  23 +
  24 + # 定义需要的权限范围
  25 + scopes = "read_customer,read_product,write_product,read_order,read_shop"
  26 +
  27 + # 生成随机state参数防止CSRF攻击
  28 + state = secrets.token_hex(16)
  29 +
  30 + # 构建授权URL
  31 + auth_url = (
  32 + f"https://{shop}/admin/oauth/authorize?"
  33 + f"client_id={Config.CLIENT_ID}&"
  34 + f"scope={scopes}&"
  35 + f"redirect_uri={Config.BASE_URL}{Config.REDIRECT_URI}&"
  36 + f"response_type=code&"
  37 + f"state={state}"
  38 + )
  39 +
  40 + return redirect(auth_url)
  41 +
  42 +@auth_bp.route('/shoplazza/callback')
  43 +@hmac_validator_required
  44 +def callback():
  45 + """
  46 + Step 2: 处理OAuth回调
  47 + 商家授权后,Shoplazza会重定向到这个端点
  48 + """
  49 + code = request.args.get('code')
  50 + shop = request.args.get('shop')
  51 + state = request.args.get('state')
  52 +
  53 + if not shop or not code:
  54 + return jsonify({'error': 'Missing required parameters'}), 400
  55 +
  56 + try:
  57 + # 交换授权码获取访问令牌
  58 + token_data = exchange_code_for_token(code, shop)
  59 +
  60 + # 存储访问令牌(生产环境应使用数据库)
  61 + Config.ACCESS_TOKENS[shop] = token_data
  62 +
  63 + current_app.logger.info(f"获取access_token成功 for shop: {shop}")
  64 +
  65 + return jsonify({
  66 + 'message': 'Authorization successful',
  67 + 'shop': shop,
  68 + 'store_id': token_data.get('store_id'),
  69 + 'store_name': token_data.get('store_name')
  70 + })
  71 +
  72 + except Exception as e:
  73 + current_app.logger.error(f"获取access_token失败: {str(e)}")
  74 + return jsonify({'error': 'Failed to obtain access token'}), 500
  75 +
  76 +def exchange_code_for_token(code, shop):
  77 + """
  78 + 使用授权码交换访问令牌
  79 + """
  80 + token_url = f"https://{shop}/admin/oauth/token"
  81 +
  82 + data = {
  83 + 'client_id': Config.CLIENT_ID,
  84 + 'client_secret': Config.CLIENT_SECRET,
  85 + 'code': code,
  86 + 'grant_type': 'authorization_code',
  87 + 'redirect_uri': f"{Config.BASE_URL}{Config.REDIRECT_URI}"
  88 + }
  89 +
  90 + response = requests.post(token_url, data=data)
  91 + response.raise_for_status()
  92 +
  93 + return response.json()
  94 +
  95 +@auth_bp.route('/refresh_token/<shop>')
  96 +def refresh_token(shop):
  97 + """
  98 + 刷新访问令牌
  99 + """
  100 + if shop not in Config.ACCESS_TOKENS:
  101 + return jsonify({'error': 'Shop not found'}), 404
  102 +
  103 + token_data = Config.ACCESS_TOKENS[shop]
  104 + refresh_token_value = token_data.get('refresh_token')
  105 +
  106 + if not refresh_token_value:
  107 + return jsonify({'error': 'No refresh token available'}), 400
  108 +
  109 + try:
  110 + refresh_url = f"https://{shop}/admin/oauth/token"
  111 +
  112 + data = {
  113 + 'client_id': Config.CLIENT_ID,
  114 + 'client_secret': Config.CLIENT_SECRET,
  115 + 'refresh_token': refresh_token_value,
  116 + 'grant_type': 'refresh_token',
  117 + 'redirect_uri': f"{Config.BASE_URL}{Config.REDIRECT_URI}"
  118 + }
  119 +
  120 + response = requests.post(refresh_url, data=data)
  121 + response.raise_for_status()
  122 +
  123 + # 更新存储的令牌
  124 + Config.ACCESS_TOKENS[shop] = response.json()
  125 +
  126 + return jsonify({
  127 + 'message': 'Token refreshed successfully',
  128 + 'new_token': response.json()
  129 + })
  130 +
  131 + except Exception as e:
  132 + current_app.logger.error(f"刷新token失败: {str(e)}")
  133 + return jsonify({'error': 'Failed to refresh token'}), 500
  134 +
  135 +@auth_bp.route('/tokens')
  136 +def list_tokens():
  137 + """
  138 + 列出所有已授权的商店令牌(仅用于调试)
  139 + """
  140 + return jsonify({
  141 + 'authorized_shops': list(Config.ACCESS_TOKENS.keys()),
  142 + 'tokens': Config.ACCESS_TOKENS
  143 + })
... ...
old/routes/webhook.py 0 → 100644
  1 +++ a/old/routes/webhook.py
... ... @@ -0,0 +1,77 @@
  1 +import hmac
  2 +import hashlib
  3 +import base64
  4 +from flask import Blueprint, request, jsonify, current_app
  5 +from config import Config
  6 +from middleware.hmac_validator import verify_webhook_hmac
  7 +
  8 +# 创建Webhook蓝图
  9 +webhook_bp = Blueprint('webhook', __name__, url_prefix='/webhook')
  10 +
  11 +@webhook_bp.route('/shoplazza', methods=['POST'])
  12 +def shoplazza_webhook():
  13 + """
  14 + 处理Shoplazza Webhook
  15 + """
  16 + # 获取HMAC签名头
  17 + hmac_header = request.headers.get('X-Shoplazza-Hmac-Sha256')
  18 + if not hmac_header:
  19 + return jsonify({'error': 'Missing HMAC header'}), 400
  20 +
  21 + # 获取原始数据
  22 + data = request.get_data()
  23 +
  24 + # 验证Webhook签名
  25 + if not verify_webhook_hmac(data, hmac_header):
  26 + current_app.logger.warning("Webhook HMAC verification failed")
  27 + return jsonify({'error': 'Invalid webhook signature'}), 403
  28 +
  29 + try:
  30 + # 解析Webhook数据
  31 + webhook_data = request.get_json()
  32 + current_app.logger.info(f"Received webhook: {webhook_data}")
  33 +
  34 + # 根据Webhook类型处理
  35 + webhook_type = webhook_data.get('type')
  36 +
  37 + if webhook_type == 'app/uninstalled':
  38 + handle_app_uninstalled(webhook_data)
  39 + elif webhook_type == 'orders/create':
  40 + handle_order_created(webhook_data)
  41 + elif webhook_type == 'orders/updated':
  42 + handle_order_updated(webhook_data)
  43 + elif webhook_type == 'products/create':
  44 + handle_product_created(webhook_data)
  45 + else:
  46 + current_app.logger.info(f"Unhandled webhook type: {webhook_type}")
  47 +
  48 + return jsonify({'status': 'success'})
  49 +
  50 + except Exception as e:
  51 + current_app.logger.error(f"Webhook处理失败: {str(e)}")
  52 + return jsonify({'error': 'Webhook processing failed'}), 500
  53 +
  54 +def handle_app_uninstalled(webhook_data):
  55 + """处理应用卸载事件"""
  56 + shop = webhook_data.get('shop')
  57 + if shop and shop in Config.ACCESS_TOKENS:
  58 + del Config.ACCESS_TOKENS[shop]
  59 + current_app.logger.info(f"App uninstalled for shop: {shop}")
  60 +
  61 +def handle_order_created(webhook_data):
  62 + """处理订单创建事件"""
  63 + order = webhook_data.get('order')
  64 + if order:
  65 + current_app.logger.info(f"New order created: {order.get('id')}")
  66 +
  67 +def handle_order_updated(webhook_data):
  68 + """处理订单更新事件"""
  69 + order = webhook_data.get('order')
  70 + if order:
  71 + current_app.logger.info(f"Order updated: {order.get('id')}")
  72 +
  73 +def handle_product_created(webhook_data):
  74 + """处理产品创建事件"""
  75 + product = webhook_data.get('product')
  76 + if product:
  77 + current_app.logger.info(f"New product created: {product.get('id')}")
... ...
old/run.py 0 → 100755
  1 +++ a/old/run.py
... ... @@ -0,0 +1,40 @@
  1 +#!/usr/bin/env python3
  2 +"""
  3 +Shoplazza OAuth2.0 后端应用启动脚本
  4 +"""
  5 +
  6 +import os
  7 +import sys
  8 +from app import create_app
  9 +
  10 +def main():
  11 + """主函数"""
  12 + # 检查环境变量
  13 + required_vars = ['CLIENT_ID', 'CLIENT_SECRET', 'BASE_URL']
  14 + missing_vars = [var for var in required_vars if not os.getenv(var)]
  15 +
  16 + if missing_vars:
  17 + print(f"错误: 缺少必需的环境变量: {', '.join(missing_vars)}")
  18 + print("请创建 .env 文件并配置以下变量:")
  19 + print("CLIENT_ID=your_client_id_here")
  20 + print("CLIENT_SECRET=your_client_secret_here")
  21 + print("BASE_URL=https://your-domain.com")
  22 + sys.exit(1)
  23 +
  24 + # 创建应用
  25 + app = create_app()
  26 +
  27 + # 启动应用
  28 + print("🚀 启动 Shoplazza OAuth2.0 后端服务...")
  29 + print(f"📡 服务地址: http://localhost:{app.config['PORT']}")
  30 + print(f"🔗 认证端点: {app.config['BASE_URL']}/auth/install?shop=your-shop.myshoplaza.com")
  31 + print(f"📋 API文档: {app.config['BASE_URL']}/")
  32 +
  33 + app.run(
  34 + host='0.0.0.0',
  35 + port=app.config['PORT'],
  36 + debug=app.config['DEBUG']
  37 + )
  38 +
  39 +if __name__ == '__main__':
  40 + main()
... ...
old/step_by_step_oauth.md 0 → 100644
  1 +++ a/old/step_by_step_oauth.md
... ... @@ -0,0 +1,184 @@
  1 +# OAuth2.0 认证步骤详解
  2 +
  3 +## 🎯 完整认证流程
  4 +
  5 +### 步骤1: 准备阶段
  6 +
  7 +#### 1.1 获取Shoplazza应用凭证
  8 +```
  9 +1. 访问 https://partners.shoplazza.com/
  10 +2. 登录并创建新应用
  11 +3. 记录 CLIENT_ID 和 CLIENT_SECRET
  12 +```
  13 +
  14 +#### 1.2 设置本地开发环境
  15 +```bash
  16 +# 安装依赖
  17 +pip install -r requirements.txt
  18 +
  19 +# 安装ngrok (用于本地开发)
  20 +npm install -g ngrok
  21 +```
  22 +
  23 +#### 1.3 启动ngrok隧道
  24 +```bash
  25 +# 在终端1中启动ngrok
  26 +ngrok http 3000
  27 +
  28 +# 记录ngrok提供的HTTPS URL,例如:
  29 +# https://abc123.ngrok.io
  30 +```
  31 +
  32 +### 步骤2: 配置应用
  33 +
  34 +#### 2.1 创建环境配置文件
  35 +创建 `.env` 文件:
  36 +```env
  37 +CLIENT_ID=your_actual_client_id_from_shoplazza
  38 +CLIENT_SECRET=your_actual_client_secret_from_shoplazza
  39 +BASE_URL=https://abc123.ngrok.io
  40 +REDIRECT_URI=/auth/shoplazza/callback
  41 +SECRET_KEY=your-random-secret-key
  42 +FLASK_ENV=development
  43 +FLASK_DEBUG=True
  44 +PORT=3000
  45 +```
  46 +
  47 +#### 2.2 在Shoplazza开发者中心配置URL
  48 +```
  49 +应用URL: https://abc123.ngrok.io/auth/install
  50 +回调URL: https://abc123.ngrok.io/auth/shoplazza/callback
  51 +Webhook URL: https://abc123.ngrok.io/webhook/shoplazza
  52 +```
  53 +
  54 +### 步骤3: 启动应用
  55 +
  56 +```bash
  57 +# 在终端2中启动应用
  58 +python run.py
  59 +```
  60 +
  61 +您应该看到类似输出:
  62 +```
  63 +🚀 启动 Shoplazza OAuth2.0 后端服务...
  64 +📡 服务地址: http://localhost:3000
  65 +🔗 认证端点: https://abc123.ngrok.io/auth/install?shop=your-shop.myshoplaza.com
  66 +📋 API文档: https://abc123.ngrok.io/
  67 +```
  68 +
  69 +### 步骤4: 执行OAuth认证
  70 +
  71 +#### 4.1 开始认证流程
  72 +在浏览器中访问:
  73 +```
  74 +https://abc123.ngrok.io/auth/install?shop=your-shop.myshoplaza.com
  75 +```
  76 +
  77 +**注意**: 将 `your-shop.myshoplaza.com` 替换为实际的商店域名
  78 +
  79 +#### 4.2 授权确认
  80 +1. 系统会重定向到Shoplazza授权页面
  81 +2. 商家点击"安装应用"或"授权"
  82 +3. 系统自动处理回调
  83 +
  84 +#### 4.3 验证认证结果
  85 +访问以下URL查看认证状态:
  86 +```
  87 +https://abc123.ngrok.io/auth/tokens
  88 +```
  89 +
  90 +### 步骤5: 测试API调用
  91 +
  92 +#### 5.1 获取客户列表
  93 +```bash
  94 +curl https://abc123.ngrok.io/api/customers/your-shop.myshoplaza.com
  95 +```
  96 +
  97 +#### 5.2 获取产品列表
  98 +```bash
  99 +curl https://abc123.ngrok.io/api/products/your-shop.myshoplaza.com
  100 +```
  101 +
  102 +#### 5.3 获取订单列表
  103 +```bash
  104 +curl https://abc123.ngrok.io/api/orders/your-shop.myshoplaza.com
  105 +```
  106 +
  107 +#### 5.4 获取商店信息
  108 +```bash
  109 +curl https://abc123.ngrok.io/api/shop_info/your-shop.myshoplaza.com
  110 +```
  111 +
  112 +## 🔍 认证流程详解
  113 +
  114 +### OAuth2.0 流程图
  115 +```
  116 +商家 → 应用安装 → 授权页面 → 确认授权 → 回调处理 → 获取令牌 → API调用
  117 +```
  118 +
  119 +### 详细步骤说明
  120 +
  121 +1. **应用安装请求**
  122 + - 商家从应用商店安装应用
  123 + - Shoplazza发送请求到 `/auth/install`
  124 + - 应用重定向到Shoplazza授权页面
  125 +
  126 +2. **用户授权**
  127 + - 商家在Shoplazza页面确认授权
  128 + - 选择需要的权限范围
  129 + - 点击"安装应用"
  130 +
  131 +3. **授权回调**
  132 + - Shoplazza重定向到 `/auth/shoplazza/callback`
  133 + - 携带授权码和HMAC签名
  134 + - 应用验证HMAC签名
  135 +
  136 +4. **令牌交换**
  137 + - 应用使用授权码请求访问令牌
  138 + - Shoplazza返回访问令牌和刷新令牌
  139 + - 应用存储令牌用于后续API调用
  140 +
  141 +5. **API调用**
  142 + - 使用访问令牌调用Shoplazza API
  143 + - 获取客户、产品、订单等数据
  144 +
  145 +## 🛠️ 故障排除
  146 +
  147 +### 常见错误及解决方案
  148 +
  149 +1. **"Missing HMAC parameter"**
  150 + - 检查请求是否来自Shoplazza
  151 + - 确认URL参数完整
  152 +
  153 +2. **"HMAC validation failed"**
  154 + - 检查CLIENT_SECRET配置
  155 + - 确认请求参数顺序正确
  156 +
  157 +3. **"Shop not authorized"**
  158 + - 确认已完成OAuth认证
  159 + - 检查访问令牌是否有效
  160 +
  161 +4. **"Failed to obtain access token"**
  162 + - 检查CLIENT_ID和CLIENT_SECRET
  163 + - 确认回调URL配置正确
  164 +
  165 +### 调试命令
  166 +
  167 +```bash
  168 +# 检查应用状态
  169 +curl https://abc123.ngrok.io/health
  170 +
  171 +# 查看已授权的商店
  172 +curl https://abc123.ngrok.io/auth/tokens
  173 +
  174 +# 刷新访问令牌
  175 +curl https://abc123.ngrok.io/auth/refresh_token/your-shop.myshoplaza.com
  176 +```
  177 +
  178 +## 📝 注意事项
  179 +
  180 +1. **HTTPS要求**: 生产环境必须使用HTTPS
  181 +2. **令牌安全**: 生产环境应使用数据库存储令牌
  182 +3. **错误处理**: 实现适当的错误处理和日志记录
  183 +4. **权限范围**: 只请求应用实际需要的权限
  184 +5. **令牌刷新**: 定期刷新访问令牌以保持有效性
... ...
old/test_oauth.py 0 → 100755
  1 +++ a/old/test_oauth.py
... ... @@ -0,0 +1,137 @@
  1 +#!/usr/bin/env python3
  2 +"""
  3 +OAuth2.0 认证测试脚本
  4 +用于验证Shoplazza OAuth流程是否正常工作
  5 +"""
  6 +
  7 +import requests
  8 +import json
  9 +import os
  10 +from dotenv import load_dotenv
  11 +
  12 +# 加载环境变量
  13 +load_dotenv()
  14 +
  15 +def test_health_check(base_url):
  16 + """测试健康检查端点"""
  17 + try:
  18 + response = requests.get(f"{base_url}/health")
  19 + if response.status_code == 200:
  20 + print("✅ 健康检查通过")
  21 + print(f" 状态: {response.json()}")
  22 + return True
  23 + else:
  24 + print(f"❌ 健康检查失败: {response.status_code}")
  25 + return False
  26 + except Exception as e:
  27 + print(f"❌ 健康检查异常: {str(e)}")
  28 + return False
  29 +
  30 +def test_auth_endpoints(base_url):
  31 + """测试认证端点"""
  32 + print("\n🔐 测试认证端点...")
  33 +
  34 + # 测试根端点
  35 + try:
  36 + response = requests.get(base_url)
  37 + if response.status_code == 200:
  38 + print("✅ 根端点正常")
  39 + endpoints = response.json().get('endpoints', {})
  40 + print(f" 可用端点: {list(endpoints.keys())}")
  41 + else:
  42 + print(f"❌ 根端点异常: {response.status_code}")
  43 + except Exception as e:
  44 + print(f"❌ 根端点异常: {str(e)}")
  45 +
  46 +def test_oauth_flow(base_url, shop_domain):
  47 + """测试OAuth流程"""
  48 + print(f"\n🔄 测试OAuth流程 (商店: {shop_domain})...")
  49 +
  50 + # 构建认证URL
  51 + auth_url = f"{base_url}/auth/install?shop={shop_domain}"
  52 + print(f"认证URL: {auth_url}")
  53 +
  54 + # 测试认证端点(不跟随重定向)
  55 + try:
  56 + response = requests.get(auth_url, allow_redirects=False)
  57 + if response.status_code in [302, 301]:
  58 + print("✅ 认证重定向正常")
  59 + print(f" 重定向到: {response.headers.get('Location', 'N/A')}")
  60 + else:
  61 + print(f"❌ 认证重定向异常: {response.status_code}")
  62 + except Exception as e:
  63 + print(f"❌ 认证端点异常: {str(e)}")
  64 +
  65 +def test_api_endpoints(base_url, shop_domain):
  66 + """测试API端点"""
  67 + print(f"\n📡 测试API端点 (商店: {shop_domain})...")
  68 +
  69 + endpoints = [
  70 + f"/api/customers/{shop_domain}",
  71 + f"/api/products/{shop_domain}",
  72 + f"/api/orders/{shop_domain}",
  73 + f"/api/shop_info/{shop_domain}"
  74 + ]
  75 +
  76 + for endpoint in endpoints:
  77 + try:
  78 + response = requests.get(f"{base_url}{endpoint}")
  79 + if response.status_code == 401:
  80 + print(f"⚠️ {endpoint} - 需要认证 (正常)")
  81 + elif response.status_code == 200:
  82 + print(f"✅ {endpoint} - 认证成功")
  83 + else:
  84 + print(f"❌ {endpoint} - 异常状态: {response.status_code}")
  85 + except Exception as e:
  86 + print(f"❌ {endpoint} - 异常: {str(e)}")
  87 +
  88 +def test_tokens_endpoint(base_url):
  89 + """测试令牌端点"""
  90 + print("\n🔑 测试令牌端点...")
  91 +
  92 + try:
  93 + response = requests.get(f"{base_url}/auth/tokens")
  94 + if response.status_code == 200:
  95 + tokens = response.json()
  96 + print("✅ 令牌端点正常")
  97 + print(f" 已授权商店: {tokens.get('authorized_shops', [])}")
  98 + if tokens.get('tokens'):
  99 + print(f" 令牌数量: {len(tokens.get('tokens', {}))}")
  100 + else:
  101 + print(f"❌ 令牌端点异常: {response.status_code}")
  102 + except Exception as e:
  103 + print(f"❌ 令牌端点异常: {str(e)}")
  104 +
  105 +def main():
  106 + """主测试函数"""
  107 + print("🚀 Shoplazza OAuth2.0 认证测试")
  108 + print("=" * 50)
  109 +
  110 + # 获取配置
  111 + base_url = os.getenv('BASE_URL', 'http://localhost:3000')
  112 + shop_domain = input("请输入商店域名 (例如: your-shop.myshoplaza.com): ").strip()
  113 +
  114 + if not shop_domain:
  115 + shop_domain = "your-shop.myshoplaza.com"
  116 + print(f"使用默认商店域名: {shop_domain}")
  117 +
  118 + print(f"\n测试配置:")
  119 + print(f" 基础URL: {base_url}")
  120 + print(f" 商店域名: {shop_domain}")
  121 +
  122 + # 执行测试
  123 + if test_health_check(base_url):
  124 + test_auth_endpoints(base_url)
  125 + test_oauth_flow(base_url, shop_domain)
  126 + test_api_endpoints(base_url, shop_domain)
  127 + test_tokens_endpoint(base_url)
  128 +
  129 + print("\n" + "=" * 50)
  130 + print("🎯 测试完成!")
  131 + print("\n下一步操作:")
  132 + print(f"1. 在浏览器中访问: {base_url}/auth/install?shop={shop_domain}")
  133 + print("2. 完成OAuth认证流程")
  134 + print("3. 重新运行此测试脚本验证API调用")
  135 +
  136 +if __name__ == "__main__":
  137 + main()
... ...
old/后端OAuth2.0认证流程.md 0 → 100644
  1 +++ a/old/后端OAuth2.0认证流程.md
... ... @@ -0,0 +1,128 @@
  1 +
  2 +后端OAuth2.0认证流程
  3 +1. 安装依赖
  4 +Codeblock
  5 + 1
  6 + npm install express crypto axios
  7 + 2. 配置⽂件/config/index.js
  8 +⽤于存放APP_NAME,CLIENT_ID,CLIENT_SECRET,REDIRECT_URI
  9 + Codeblock
  10 +
  11 + const CLIENT_ID = "<YOUR_CLIENT_ID>";
  12 + const CLIENT_SECRET = "<YOUR_CLIENT_SECRET>";
  13 + const BASE_URL = "<BASE_URL>";
  14 + const REDIRECT_URI = `${BASE_URL}/auth/shoplazza/callback`;
  15 + let access_token = {};
  16 + 3. HMAC校验中间件/middleware/hmacValidator.js
  17 + Codeblock
  18 +
  19 + 9
  20 + import crypto from "crypto";
  21 + export const hmacValidator = async (ctx, next) => {
  22 + const { hmac, ...queryParams } = ctx.query;
  23 + if (!hmac) {
  24 + ctx.status = 400;
  25 + ctx.body = { error: "Missing HMAC parameter" };
  26 + return;
  27 +
  28 + }
  29 + //
  30 +计算
  31 + HMAC
  32 +校验
  33 +
  34 +const message = Object.keys(queryParams)
  35 + .sort()
  36 + .map((key) => `${key}=${queryParams[key]}`)
  37 + .join("&");
  38 + const generatedHmac = crypto
  39 + .createHmac("sha256", process.env.CLIENT_SECRET)
  40 + .update(message)
  41 + .digest("hex");
  42 + if (crypto.timingSafeEqual(Buffer.from(generatedHmac), Buffer.from(hmac))) {
  43 + await next();
  44 + } else {
  45 + ctx.status = 403;
  46 + ctx.body = { error: "HMAC validation failed" };
  47 + }
  48 + };
  49 + 4. 认证路由/routes/auth.js
  50 + Codeblock
  51 +
  52 + import Router from "koa-router";
  53 + import crypto from "crypto";
  54 + import axios from "axios";
  55 + import { APP_NAME, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from
  56 +"../config/index.js";
  57 + import { hmacValidator } from "../middleware/hmacValidator.js";
  58 + const router = new Router({ prefix: "/api" });
  59 + router.get(`/auth/install`, async (ctx) => {
  60 + const shop = ctx.query.shop;
  61 + if (!shop) {
  62 + ctx.status = 400;
  63 + ctx.body = { error: "Missing shop parameter" };
  64 + return;
  65 + }
  66 + const scopes = "read_customer,read_product,write_product";
  67 + const state = crypto.randomBytes(16).toString("hex");
  68 + const redirectUri = `https://${ctx.host}${REDIRECT_URI}`;
  69 +
  70 + const authUrl = `https://${shop}/admin/oauth/authorize?
  71 + client_id=${CLIENT_ID}&scope=${scopes}&redirect_uri=${redirectUri}&response_typ
  72 + e=code&state=${state}`;
  73 + ctx.redirect(authUrl);
  74 + });
  75 + // ** Step 2:
  76 +处理
  77 + OAuth
  78 +回调
  79 +**
  80 +router.get(`/auth/callback`, hmacValidator, async (ctx) => {
  81 + const { code, shop } = ctx.query;
  82 + if (!shop || !code) {
  83 + ctx.status = 400;
  84 + ctx.body = { error: "Missing required parameters" };
  85 + return;
  86 + }
  87 + const redirectUri = `https://${ctx.host}${REDIRECT_URI}`;
  88 + try {
  89 + const response = await axios.post(`https://${shop}/admin/oauth/token`, {
  90 + client_id: CLIENT_ID,
  91 + client_secret: CLIENT_SECRET,
  92 + code,
  93 + grant_type: "authorization_code",
  94 + redirect_uri: redirectUri,
  95 + });
  96 + console.log("
  97 +获取
  98 + access_token
  99 +成功
  100 +:", response.data);
  101 + ctx.body = response.data; //
  102 +返回
  103 + token
  104 +及
  105 + store
  106 +信息
  107 +
  108 +} catch (error) {
  109 + console.error("
  110 +获取
  111 + access_token
  112 +失败
  113 +:", error.message);
  114 + ctx.status = 500;
  115 + ctx.body = { error: "Failed to obtain access token" };
  116 + }
  117 + });
  118 + export default router;
  119 + 5. 启动服务器挂载oauth,认证路由
  120 +Codeblock
  121 + 1
  122 + 2
  123 + const PORT = process.env.PORT || 3000;
  124 + app.listen(PORT, () => {
  125 +3
  126 + 4
  127 + console.log(`Server running on http://localhost:${PORT}`);
  128 + });
0 129 \ No newline at end of file
... ...
python-app-demo/README.md 0 → 100644
  1 +++ a/python-app-demo/README.md
... ... @@ -0,0 +1,90 @@
  1 +# Python OAuth2 认证服务器
  2 +
  3 +这是一个用 Flask 编写的 OAuth2 认证服务器,实现了与 Node.js 版本相同的功能。
  4 +
  5 +## 功能特性
  6 +
  7 +- ✅ OAuth2 认证流程
  8 +- ✅ HMAC-SHA256 签名验证
  9 +- ✅ Timing-safe 比较防止时序攻击
  10 +- ✅ 支持 Shopify OAuth 集成
  11 +- ✅ CORS 跨域支持
  12 +- ✅ 环境变量配置
  13 +
  14 +## 快速开始
  15 +
  16 +### 1. 安装依赖
  17 +
  18 +```bash
  19 +pip install -r requirements.txt
  20 +```
  21 +
  22 +### 2. 运行服务器
  23 +
  24 +```bash
  25 +python main.py
  26 +```
  27 +
  28 +服务器将在 `http://localhost:4000` 运行
  29 +
  30 +## API 端点
  31 +
  32 +### 1. 启动 OAuth 认证
  33 +
  34 +```
  35 +GET /api/auth?shop=example.myshopify.com
  36 +```
  37 +
  38 +重定向用户到 Shopify 授权页面。
  39 +
  40 +### 2. OAuth 回调处理
  41 +
  42 +```
  43 +GET /api/auth/callback?code=xxx&hmac=xxx&state=xxx&shop=example.myshopify.com
  44 +```
  45 +
  46 +- 验证 HMAC 签名
  47 +- 交换授权码获取访问令牌
  48 +- 返回令牌信息
  49 +
  50 +## 项目结构
  51 +
  52 +```
  53 +python-app-demo/
  54 +├── main.py # 主应用文件
  55 +├── requirements.txt # Python 依赖
  56 +├── .env # 环境变量(需要自己创建)
  57 +└── README.md # 说明文档
  58 +```
  59 +
  60 +## 与 Node.js 版本的对应关系
  61 +
  62 +| Node.js (devServer) | Python (python-app-demo) |
  63 +|-------------------|----------------------|
  64 +| Express 服务器 | Flask 服务器 |
  65 +| hmacValidatorMiddleWare | @hmac_validator 装饰器 |
  66 +| secureCompare | secure_compare 函数 |
  67 +| crypto.timingSafeEqual | hmac.compare_digest |
  68 +| /api/auth | /api/auth 路由 |
  69 +| /api/auth/callback | /api/auth/callback 路由 |
  70 +
  71 +## 安全特性
  72 +
  73 +1. **HMAC-SHA256 验证**:所有回调请求都通过 HMAC 签名验证
  74 +2. **Timing-safe 比较**:使用 `hmac.compare_digest` 防止时序攻击
  75 +3. **环境变量管理**:敏感配置存储在 `.env` 文件中
  76 +4. **CORS 支持**:安全的跨域资源共享
  77 +
  78 +## 使用 curl 测试
  79 +
  80 +```bash
  81 +# 测试认证端点
  82 +curl "http://localhost:4000/api/auth?shop=example.myshopify.com"
  83 +
  84 +# 测试回调端点(需要有效的HMAC)
  85 +curl "http://localhost:4000/api/auth/callback?code=test&hmac=xxx&state=xxx&shop=example.myshopify.com"
  86 +```
  87 +
  88 +## 完成!
  89 +
  90 +现在您有一个完整的 Python OAuth2 认证服务器,功能与 Node.js 版本完全相同。🚀
... ...
python-app-demo/main.py 0 → 100644
  1 +++ a/python-app-demo/main.py
... ... @@ -0,0 +1,129 @@
  1 +from flask import Flask, request, redirect, jsonify
  2 +from flask_cors import CORS
  3 +import hmac
  4 +import hashlib
  5 +import secrets
  6 +import requests
  7 +
  8 +app = Flask(__name__)
  9 +CORS(app)
  10 +
  11 +# 配置 - 直接写死
  12 +CLIENT_ID = "J2ntcUvAKRq_xVXcmysAc6iUJ-MYJF4JtoMKJGi_I9A"
  13 +CLIENT_SECRET = "WvmwpHVf1u1hfYEhsc7LPl0YxrbiFy3A2JVBhy8PWAU"
  14 +REDIRECT_URI = "https://rooster-wired-monster.ngrok-free.app/api/auth/callback"
  15 +
  16 +# 工具函数:安全比较HMAC
  17 +def secure_compare(a, b):
  18 + """
  19 + 使用timing-safe比较来防止时序攻击
  20 + """
  21 + return hmac.compare_digest(a, b)
  22 +
  23 +# 中间件:HMAC验证
  24 +def hmac_validator(func):
  25 + """
  26 + HMAC验证装饰器 - 验证请求的HMAC签名
  27 + """
  28 + def wrapper(*args, **kwargs):
  29 + # 获取查询参数
  30 + code = request.args.get('code')
  31 + hmac_value = request.args.get('hmac')
  32 + state = request.args.get('state')
  33 + shop = request.args.get('shop')
  34 +
  35 + if not hmac_value:
  36 + return jsonify({"error": "HMAC validation failed"}), 400
  37 +
  38 + # 构建消息
  39 + params = dict(request.args)
  40 + del params['hmac'] # 移除hmac参数
  41 +
  42 + # 按字典序排序参数并构建消息字符串
  43 + sorted_keys = sorted(params.keys())
  44 + message = "&".join([f"{key}={params[key]}" for key in sorted_keys])
  45 +
  46 + # 生成HMAC哈希
  47 + generated_hash = hmac.new(
  48 + CLIENT_SECRET.encode(),
  49 + message.encode(),
  50 + hashlib.sha256
  51 + ).hexdigest()
  52 +
  53 + # 比较HMAC
  54 + if not secure_compare(generated_hash, hmac_value):
  55 + return jsonify({"error": "HMAC validation failed"}), 400
  56 +
  57 + return func(*args, **kwargs)
  58 +
  59 + wrapper.__name__ = func.__name__
  60 + return wrapper
  61 +
  62 +# OAuth认证路由
  63 +@app.route('/api/auth', methods=['GET'])
  64 +def auth():
  65 + """
  66 + OAuth认证端点 - 重定向到Shopify授权页面
  67 + """
  68 + shop = request.args.get('shop')
  69 + if not shop:
  70 + return jsonify({"error": "shop parameter is required"}), 400
  71 +
  72 + scopes = "read_customer"
  73 + state = secrets.token_hex(16) # 生成随机state
  74 +
  75 + auth_url = (
  76 + f"https://{shop}/admin/oauth/authorize?"
  77 + f"client_id={CLIENT_ID}"
  78 + f"&scope={scopes}"
  79 + f"&redirect_uri={REDIRECT_URI}"
  80 + f"&response_type=code"
  81 + f"&state={state}"
  82 + )
  83 +
  84 + return redirect(auth_url)
  85 +
  86 +# OAuth回调路由
  87 +@app.route('/api/auth/callback', methods=['GET'])
  88 +@hmac_validator
  89 +def auth_callback():
  90 + """
  91 + OAuth回调端点 - 交换授权码获取访问令牌
  92 + """
  93 + code = request.args.get('code')
  94 + shop = request.args.get('shop')
  95 +
  96 + if not shop or not code:
  97 + return jsonify({"error": "Required parameters missing"}), 400
  98 +
  99 + try:
  100 + # 请求访问令牌
  101 + token_url = f"https://{shop}/admin/oauth/token"
  102 + data = {
  103 + "client_id": CLIENT_ID,
  104 + "client_secret": CLIENT_SECRET,
  105 + "code": code,
  106 + "grant_type": "authorization_code",
  107 + "redirect_uri": REDIRECT_URI,
  108 + }
  109 +
  110 + response = requests.post(token_url, json=data)
  111 + response.raise_for_status()
  112 +
  113 + token_data = response.json()
  114 + print("token data:", token_data)
  115 +
  116 + return jsonify(token_data)
  117 +
  118 + # 这里可以调用Shoplazza OpenAPI获取更多数据
  119 + # headers = {"Access-Token": token_data.get("access_token")}
  120 + # customers_url = f"https://{shop}/openapi/2022-01/customers"
  121 + # customers_response = requests.get(customers_url, headers=headers)
  122 + # return redirect(LAST_URL)
  123 +
  124 + except requests.exceptions.RequestException as e:
  125 + print(f"Error: {e}")
  126 + return jsonify({"error": "Failed to get access token"}), 500
  127 +
  128 +if __name__ == '__main__':
  129 + app.run(host='0.0.0.0', port=4000, debug=True)
... ...
python-app-demo/requirements.txt 0 → 100644
  1 +++ a/python-app-demo/requirements.txt
... ... @@ -0,0 +1,3 @@
  1 +Flask==3.0.0
  2 +requests==2.31.0
  3 +flask-cors==4.0.0
... ...