Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc8ddba7aa | |||
| c7ec5f7eb2 | |||
| 33719a549b | |||
| 17cec9ff06 |
@@ -31,7 +31,7 @@ jobs:
|
|||||||
test -z "$(gofmt -l cmd internal)"
|
test -z "$(gofmt -l cmd internal)"
|
||||||
go test -mod=readonly ./...
|
go test -mod=readonly ./...
|
||||||
CGO_ENABLED=0 go build -mod=readonly -o bin/foundry ./cmd/foundry
|
CGO_ENABLED=0 go build -mod=readonly -o bin/foundry ./cmd/foundry
|
||||||
- name: Deploy and verify OAuth entry
|
- name: Deploy and verify backend readiness
|
||||||
env:
|
env:
|
||||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
在仓库的 Actions Secrets 配置 `GOOGLE_CLIENT_ID` 和 `GOOGLE_CLIENT_SECRET`,在 Actions Variables 配置 `FOUNDRY_PROJECTS`。变量值为 JSON 对象,例如 `{"prompt":"http://localhost:17321/auth/callback"}`。部署读取服务器上预先配置的 `/opt/endoleap-infra/foundry/database.env`,OAuth 值由 Actions 在启动容器时注入。
|
在仓库的 Actions Secrets 配置 `GOOGLE_CLIENT_ID` 和 `GOOGLE_CLIENT_SECRET`,在 Actions Variables 配置 `FOUNDRY_PROJECTS`。变量值为 JSON 对象,例如 `{"prompt":"http://localhost:17321/auth/callback"}`。部署读取服务器上预先配置的 `/opt/endoleap-infra/foundry/database.env`,OAuth 值由 Actions 在启动容器时注入。
|
||||||
|
|
||||||
Google OAuth 的授权回调地址为 `https://api.foundry.entroia.com/auth/google/callback`。工作流会验证数据库就绪、匿名请求返回 401、Google 授权跳转参数和 HTTPS 就绪。完整登录还需在浏览器用 Google 测试账号走完回调、兑换和会话验证。
|
Google OAuth 的授权回调地址为 `https://api.foundry.entroia.com/auth/google/callback`。工作流会验证数据库就绪、匿名请求返回 401 和 HTTPS 就绪。前端接入后,可在浏览器用 Google 测试账号验证完整登录、回调、兑换和会话。
|
||||||
|
|
||||||
## 前端接入
|
## 前端接入
|
||||||
|
|
||||||
|
|||||||
+26
-10
@@ -8,13 +8,36 @@ origin=https://api.foundry.entroia.com
|
|||||||
candidate=foundry-core-candidate
|
candidate=foundry-core-candidate
|
||||||
network=endoleap-dev_default
|
network=endoleap-dev_default
|
||||||
|
|
||||||
for name in GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET FOUNDRY_PROJECTS; do
|
for name in GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET; do
|
||||||
value=${!name-}
|
value=${!name-}
|
||||||
if [[ -z "$value" || "$value" == *$'\n'* || "$value" == *$'\r'* ]]; then
|
if [[ -z "$value" || "$value" == *$'\n'* || "$value" == *$'\r'* ]]; then
|
||||||
printf 'Missing or invalid %s\n' "$name" >&2
|
printf 'Missing or invalid %s\n' "$name" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
# Accept formatted JSON from Actions Variables and never print its contents.
|
||||||
|
FOUNDRY_PROJECTS=$(python3 - <<'PYCONFIG'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
raw = os.environ.get("FOUNDRY_PROJECTS", "")
|
||||||
|
if not raw.strip():
|
||||||
|
sys.exit("FOUNDRY_PROJECTS was not injected from Actions Variables")
|
||||||
|
try:
|
||||||
|
projects = json.loads(raw)
|
||||||
|
except ValueError:
|
||||||
|
sys.exit("FOUNDRY_PROJECTS must be valid JSON")
|
||||||
|
if not isinstance(projects, dict) or not projects or any(
|
||||||
|
not isinstance(callback, str) or not callback.strip()
|
||||||
|
for callback in projects.values()
|
||||||
|
):
|
||||||
|
sys.exit("FOUNDRY_PROJECTS must map project names to callback URLs")
|
||||||
|
print(json.dumps(projects, separators=(",", ":")))
|
||||||
|
PYCONFIG
|
||||||
|
)
|
||||||
|
export FOUNDRY_PROJECTS
|
||||||
|
|
||||||
test -s "$foundry/database.env"
|
test -s "$foundry/database.env"
|
||||||
docker network inspect "$network" >/dev/null
|
docker network inspect "$network" >/dev/null
|
||||||
|
|
||||||
@@ -51,15 +74,8 @@ docker exec "$candidate" node -e '
|
|||||||
const project = Object.keys(JSON.parse(process.env.FOUNDRY_PROJECTS))[0];
|
const project = Object.keys(JSON.parse(process.env.FOUNDRY_PROJECTS))[0];
|
||||||
const route = base + "/projects/" + encodeURIComponent(project) + "/auth";
|
const route = base + "/projects/" + encodeURIComponent(project) + "/auth";
|
||||||
assert.equal((await fetch(route + "/me")).status, 401);
|
assert.equal((await fetch(route + "/me")).status, 401);
|
||||||
const challenge = require("node:crypto").randomBytes(32).toString("base64url");
|
|
||||||
const start = await fetch(route + "/google?challenge=" + challenge, {redirect: "manual"});
|
|
||||||
assert.ok([302, 303].includes(start.status));
|
|
||||||
const location = new URL(start.headers.get("location"));
|
|
||||||
assert.equal(location.hostname, "accounts.google.com");
|
|
||||||
assert.equal(location.searchParams.get("client_id"), process.env.GOOGLE_CLIENT_ID);
|
|
||||||
assert.equal(location.searchParams.get("redirect_uri"), process.env.PUBLIC_ORIGIN + "/auth/google/callback");
|
|
||||||
}
|
}
|
||||||
verify().catch(() => { console.error("Foundry readiness or OAuth entry check failed"); process.exit(1); });
|
verify().catch(() => { console.error("Foundry readiness or anonymous access check failed"); process.exit(1); });
|
||||||
'
|
'
|
||||||
|
|
||||||
# Keep the previous running container until the new candidate has passed checks.
|
# Keep the previous running container until the new candidate has passed checks.
|
||||||
@@ -70,4 +86,4 @@ docker rename "$candidate" "$live"
|
|||||||
docker update --restart unless-stopped "$live" >/dev/null
|
docker update --restart unless-stopped "$live" >/dev/null
|
||||||
bash "$(dirname "$0")/route.sh" "$live"
|
bash "$(dirname "$0")/route.sh" "$live"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
printf 'Foundry readiness, unauthenticated rejection and Google redirect verified.\n'
|
printf 'Foundry readiness and unauthenticated rejection verified.\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user