# 示例：Apache Superset 两步登录（Cookies）

## 1) 创建平台

```bash
curl -X POST http://127.0.0.1:8000/v1/platforms \
  -H 'content-type: application/json' \
  -d '{
    "id":"plat_superset",
    "orgId":"org_demo",
    "nameId":"superset",
    "name":"Apache Superset",
    "code":"superset",
    "baseUrl":"http://<BASE_URL>:8088/"
  }'
```

## 2) 创建授权方式（steps + requestPlacements）

```bash
curl -X POST http://127.0.0.1:8000/v1/auth-methods \
  -H 'content-type: application/json' \
  -d '{
    "id": "am_superset_cookies",
    "platformId": "plat_superset",
    "name": "superset_cookie_login",
    "type": "Cookies",
    "description": "Superset 两步登录：先取 csrf_token + session，再二次提交获取会话。",
    "loginFieldsSchema": {
      "type": "object",
      "required": ["username", "password"],
      "properties": {
        "username": {"type": "string"},
        "password": {"type": "string"}
      }
    },
    "loginFlow": {
      "steps": [
        {
          "url": "http://<BASE_URL>:8088/login/",
          "method": "POST",
          "headers": {"Content-Type": "application/x-www-form-urlencoded"},
          "body_template": {"username": "{{username}}", "password": "{{password}}"},
          "responseMapping": {
            "cookies": {"collectAll": true, "targetField": "cookies", "fields": {"session": "session"}},
            "html": {"fields": {"csrf_token": {"selector": {"tag": "input", "attrs": {"id": "csrf_token"}}, "attribute": "value"}}}
          }
        },
        {
          "url": "http://<BASE_URL>:8088/login/",
          "method": "POST",
          "headers": {"Content-Type": "application/x-www-form-urlencoded"},
          "allowRedirects": false,
          "reqeustPlacements": {
            "cookies": {"session": "cookies.session"},
            "body": {"username": "username", "password": "password", "csrf_token": "csrf_token"}
          }
        }
      ]
    },
    "responseMapping": {
      "cookies": {"collectAll": true, "targetField": "cookies", "fields": {"session": "session"}}
    },
    "authFieldPlacements": {
      "cookies": {"session": {"sourceField": "cookies.session"}}
    },
    "defaultValiditySeconds": 86400
  }'
```

## 3) 创建 Secret 并触发自动登录

```bash
curl -X POST http://127.0.0.1:8000/v1/auth-methods/am_superset_cookies/secret \
  -H 'content-type: application/json' \
  -d '{
    "id": "sec_superset_demo",
    "name": "superset_demo",
    "tags": ["superset"],
    "data": {},
    "loginPayload": {"username": "<user>", "password": "<pass>"},
    "autoLoginEnabled": true
  }'
```

# 客户端应用 `usageMapping`

前置：服务端 `loginFlow.steps` 完成两步登录并把会话 Cookie 聚合到 `Secret.data.cookies`。`AuthMethod.authFieldPlacements` 应声明如何把 Cookie 投放到业务请求。

典型 `authFieldPlacements`（服务端在 Secret 响应中以 `usageMapping` 返回）：

```json
{
  "cookies": {
    "session": { "sourceField": "cookies.session" }
  }
}
```

## Python 示例

```python
import requests
from typing import Any, Dict

base = "http://127.0.0.1:8000"
secret_id = "<your-secret-id>"
secret = requests.get(f"{base}/v1/secrets/{secret_id}").json()

usage = secret.get("usageMapping") or {}
data = secret.get("data") or {}
stype = secret.get("type")  # 应为 'Cookies'
if stype != 'Cookies':
  raise RuntimeError(f"unexpected secret.type={stype}, expected 'Cookies'")

# 应用映射：这里只需把 cookies.session 复制到请求 Cookie
cookies = {}
if usage.get("cookies", {}).get("session"):
    path = usage["cookies"]["session"].get("sourceField", "cookies.session")
    # 取值（简易路径解析）
    val = data
    for p in path.split('.'):
        if isinstance(val, dict) and p in val:
            val = val[p]
        else:
            val = None
            break
    if val is not None:
        cookies["session"] = str(val)

# 发起业务请求（示例）
resp = requests.get("http://<BASE_URL>:8088/api/v1/database/", cookies=cookies)
print(resp.status_code)
```

## JavaScript 示例

```js
async function getSecret(secretId) {
  const res = await fetch(`http://127.0.0.1:8000/v1/secrets/${secretId}`);
  if (!res.ok) throw new Error('failed');
  return res.json();
}

function getByPath(obj, path) {
  return path.split('.').reduce((cur, p) => (cur && cur[p] !== undefined ? cur[p] : undefined), obj);
}

(async () => {
  const secret = await getSecret('<your-secret-id>');
  const data = secret.data || {};
  const usage = secret.usageMapping || {};
  if (secret.type !== 'Cookies') throw new Error(`unexpected secret.type=${secret.type}`);
  const sessionSpec = usage.cookies?.session;
  const cookieVal = sessionSpec ? (getByPath(data, sessionSpec.sourceField || 'cookies.session')) : undefined;

  // 在浏览器中发起同域请求时，通常依赖浏览器管理 Cookie；
  // 若是跨域或服务端请求，需要手动设置 Cookie 头：
  const headers = {};
  if (cookieVal) headers['Cookie'] = `session=${cookieVal}`;

  const res = await fetch('http://<BASE_URL>:8088/api/v1/database/', { headers });
  console.log(res.status, await res.text());
})();
```
