Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions deploy/miniapp-market/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,41 @@ docker compose -f deploy/miniapp-market/docker-compose.yml \
up -d --no-build --force-recreate miniapp-market"
```

### GitHub OAuth 配置

GitHub OAuth App callback 必须精确为:

`https://market.openbitfun.com/miniapp/api/v1/auth/github/callback`

创建或复用 OAuth App 时的实操要点:

- OAuth App 在 GitHub → Settings → Developer settings → OAuth Apps 下创建,
没有对应的管理 API,只能人工在网页操作。创建表单支持 URL 预填:
`https://github.com/settings/applications/new?oauth_application[name]=...&oauth_application[url]=...&oauth_application[callback_url]=...`
- Client Secret 只在生成那一刻显示一次,之后无法再查看。已有 App 拿不回旧
secret 时,直接在原 App 上 "Generate a new client secret",不需要新建 App。
- 凭据按上文流程用受控编辑器写入 `market.env`,然后 recreate 容器。不要把
secret 以命令行参数形式传给脚本——它会进入 shell history 和进程列表。

配置生效的只读验证:

```bash
curl -fsS https://market.openbitfun.com/miniapp/api/v1/health
# 预期包含 "githubAuthConfigured":true

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" \
https://market.openbitfun.com/miniapp/api/v1/auth/github/start
# 预期 307,跳转 github.com/login/oauth/authorize,
# 且 client_id、redirect_uri 与注册的 App 一致
```

排错提示:浏览器登录入口是 `/auth/github/start`,不存在 `/auth/github/login`
这类路径。旧版本中未匹配的 `/miniapp/api/v1/*` 路径会落到 SPA 返回
200 + HTML,容易误判成"接口存在但行为异常";新版本已改为返回标准
404 JSON 错误信封。

### 初次开放市场

初次开放市场时保持 `MARKET_PUBLIC_BROWSE=false`,先由管理员 GitHub ID
`24753352` 登录、上传并批准样例,再用全新桌面客户端验证安装和手动更新。
全部通过后才可改为 `true` 并 recreate。审批完成不代表用户自动授予 MiniApp
Expand Down
18 changes: 18 additions & 0 deletions src/crates/services/miniapp-market-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ mod tests {
.unwrap();
assert_eq!(response.status(), StatusCode::OK, "{uri}");
}

// Unknown API paths must not fall through to the SPA's index.html.
let response = app
.clone()
.oneshot(
Request::builder()
.uri("/miniapp/api/v1/auth/github/login")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let body = axum::body::to_bytes(response.into_body(), 1024)
.await
.unwrap();
let body: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(body["error"]["code"], "not_found");
}

#[tokio::test]
Expand Down
10 changes: 9 additions & 1 deletion src/crates/services/miniapp-market-service/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use axum::body::{Body, Bytes};
use axum::extract::{DefaultBodyLimit, Path, Query, State};
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Redirect, Response};
use axum::routing::{get, post, put};
use axum::routing::{any, get, post, put};
use axum::{Json, Router};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
Expand Down Expand Up @@ -195,10 +195,18 @@ pub(crate) fn api_router(state: Arc<MarketState>) -> Router {
"/admin/listings/{listing_id}/unpublish",
post(unpublish_listing),
)
// Unmatched API paths must return the versioned JSON error envelope.
// A nested fallback would lose to the outer SPA catch-all, so this has
// to be an explicit wildcard route that outranks `/miniapp/{*rest}`.
.route("/{*rest}", any(api_not_found))
.layer(DefaultBodyLimit::max(21 * 1024 * 1024))
.with_state(state)
}

async fn api_not_found() -> MarketError {
MarketError::not_found("Unknown API route.")
}

async fn health(State(state): State<Arc<MarketState>>) -> impl IntoResponse {
let database_ready = sqlx::query_scalar::<_, i64>("SELECT 1")
.fetch_one(state.db.pool())
Expand Down