Skip to content

feat(安全): 实现零知识金库架构,密钥以 SSE 密文落库,会话层以密文传输,仅在 OTP 生成或导出的瞬间于内存中解密,用完即焚。 #79

feat(安全): 实现零知识金库架构,密钥以 SSE 密文落库,会话层以密文传输,仅在 OTP 生成或导出的瞬间于内存中解密,用完即焚。

feat(安全): 实现零知识金库架构,密钥以 SSE 密文落库,会话层以密文传输,仅在 OTP 生成或导出的瞬间于内存中解密,用完即焚。 #79

Workflow file for this run

name: Deploy to Cloudflare Workers
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
name: Deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check Required Secrets
env:
NODEAUTH_LICENSE: ${{ secrets.NODEAUTH_LICENSE }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
OAUTH_ALLOWED_USERS: ${{ secrets.OAUTH_ALLOWED_USERS }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_D1_DATABASE_ID: ${{ secrets.CLOUDFLARE_D1_DATABASE_ID }}
run: |
REQUIRED_VARS=(
"NODEAUTH_LICENSE"
"JWT_SECRET"
"ENCRYPTION_KEY"
"OAUTH_ALLOWED_USERS"
"CLOUDFLARE_API_TOKEN"
"CLOUDFLARE_ACCOUNT_ID"
"CLOUDFLARE_D1_DATABASE_ID"
)
MISSING_VARS=()
for VAR in "${REQUIRED_VARS[@]}"; do
if [ -z "${!VAR}" ]; then
MISSING_VARS+=("$VAR")
fi
done
if [ ${#MISSING_VARS[@]} -ne 0 ]; then
echo "❌ Error: The following required secrets are missing or empty:"
for VAR in "${MISSING_VARS[@]}"; do
echo " - $VAR"
done
echo "Please configure them in your repository's Settings > Secrets and variables > Actions."
exit 1
fi
echo "✅ All required secrets are present."
- name: Prepare Wrangler Config
env:
CONFIG_FILE: ${{ env.WRANGLER_CONFIG || 'wrangler.toml' }}
run: |
cp wrangler.toml wrangler.toml.bak
if [ "$CONFIG_FILE" != "wrangler.toml" ]; then
cp "$CONFIG_FILE" wrangler.toml
fi
# 注入 Cloudflare Workers 名称
- name: Inject Workers Name
env:
CONFIG_FILE: ${{ env.WRANGLER_CONFIG || 'wrangler.toml' }}
CLOUDFLARE_WORKERS_NAME: ${{ secrets.CLOUDFLARE_WORKERS_NAME || 'nodeauth-worker' }}
run: |
sed -i "0,/name =/s/name = \".*\"/name = \"$CLOUDFLARE_WORKERS_NAME\"/" "wrangler.toml"
- name: Inject Global Configurations
env:
WORKERS_DEV: ${{ secrets.CLOUDFLARE_WORKERS_DEV }}
PREVIEW_URLS: ${{ secrets.CLOUDFLARE_WORKERS_PREVIEW_URLS }}
ROUTES: ${{ secrets.CLOUDFLARE_WORKERS_ROUTES }}
run: |
if [ -n "$WORKERS_DEV" ]; then
sed -i '/^workers_dev[[:space:]]*=/d' wrangler.toml
sed -i "1i workers_dev = $WORKERS_DEV" wrangler.toml
fi
if [ -n "$PREVIEW_URLS" ]; then
sed -i '/^preview_urls[[:space:]]*=/d' wrangler.toml
sed -i "1i preview_urls = $PREVIEW_URLS" wrangler.toml
fi
if [ -n "$ROUTES" ]; then
sed -i '/^\[\[routes\]\]/d; /^pattern[[:space:]]*=[[:space:]]*".*"/d; /^zone_name[[:space:]]*=[[:space:]]*".*"/d' wrangler.toml
IFS=';' read -ra ADDR <<< "$ROUTES"
for row in "${ADDR[@]}"; do
IFS=',' read -ra PARTS <<< "$row"
echo -e "\n[[routes]]\npattern = \"${PARTS[0]}\"" >> wrangler.toml
[ ${#PARTS[@]} -ge 2 ] && echo "zone_name = \"${PARTS[1]}\"" >> wrangler.toml
done
fi
- name: Inject D1 Database ID
env:
CONFIG_FILE: ${{ env.WRANGLER_CONFIG || 'wrangler.toml' }}
CLOUDFLARE_D1_DATABASE_ID: ${{ secrets.CLOUDFLARE_D1_DATABASE_ID }}
CLOUDFLARE_D1_DATABASE_NAME: ${{ secrets.CLOUDFLARE_D1_DATABASE_NAME || 'nodeauth-db' }}
run: |
sed -i '/^\[\[d1_databases\]\]/d; /^[[:space:]]*database_name[[:space:]]*=/d; /^[[:space:]]*database_id[[:space:]]*=/d; /^[[:space:]]*binding[[:space:]]*=[[:space:]]*"DB"/d' wrangler.toml
printf "\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"$CLOUDFLARE_D1_DATABASE_NAME\"\ndatabase_id = \"$CLOUDFLARE_D1_DATABASE_ID\"\n" >> wrangler.toml
- name: Sync Database Schema
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: '4.75.0'
command: d1 execute ${{ secrets.CLOUDFLARE_D1_DATABASE_NAME || 'nodeauth-db' }} --remote --file=backend/schema.sql
- name: Inject Commit Hash & Platform
run: |
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
echo "Injecting Public SHA: $SHORT_SHA (Platform: Cloudflare Workers)"
# 1. Backend Injection
sed -i "s/__DIST_COMMIT_HASH__/$SHORT_SHA/g" backend/dist/server.js || true
sed -i "s/__DIST_COMMIT_HASH__/$SHORT_SHA/g" backend/dist/worker.js || true
sed -i "s/__DIST_PLATFORM__/Cloudflare Workers/g" backend/dist/worker.js || true
# 2. Frontend Injection: Icons & Manifest
sed -i "s/__DIST_ICON_SUFFIX__/cloudflare/g" frontend/dist/index.html || true
sed -i "s/__DIST_ICON_SUFFIX__/cloudflare/g" frontend/dist/manifest.webmanifest || true
find frontend/dist -name "*.js" -exec sed -i "s/__DIST_ICON_SUFFIX__/cloudflare/g" {} + || true
# 3. Frontend Injection: Version & Platform Info
find frontend/dist -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i "s/__DIST_COMMIT_HASH__/$SHORT_SHA/g" {} + || true
find frontend/dist -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i "s/__DIST_PLATFORM__/Cloudflare Workers/g" {} + || true
- name: Generate Secret List
id: secret_list
env:
OAUTH_GITHUB_CLIENT_ID: ${{ secrets.OAUTH_GITHUB_CLIENT_ID }}
OAUTH_GITHUB_CLIENT_SECRET: ${{ secrets.OAUTH_GITHUB_CLIENT_SECRET }}
OAUTH_GITHUB_REDIRECT_URI: ${{ secrets.OAUTH_GITHUB_REDIRECT_URI }}
OAUTH_CLOUDFLARE_CLIENT_ID: ${{ secrets.OAUTH_CLOUDFLARE_CLIENT_ID }}
OAUTH_CLOUDFLARE_CLIENT_SECRET: ${{ secrets.OAUTH_CLOUDFLARE_CLIENT_SECRET }}
OAUTH_CLOUDFLARE_ORG_DOMAIN: ${{ secrets.OAUTH_CLOUDFLARE_ORG_DOMAIN }}
OAUTH_CLOUDFLARE_REDIRECT_URI: ${{ secrets.OAUTH_CLOUDFLARE_REDIRECT_URI }}
OAUTH_NODELOC_CLIENT_ID: ${{ secrets.OAUTH_NODELOC_CLIENT_ID }}
OAUTH_NODELOC_CLIENT_SECRET: ${{ secrets.OAUTH_NODELOC_CLIENT_SECRET }}
OAUTH_NODELOC_REDIRECT_URI: ${{ secrets.OAUTH_NODELOC_REDIRECT_URI }}
OAUTH_GITEE_CLIENT_ID: ${{ secrets.OAUTH_GITEE_CLIENT_ID }}
OAUTH_GITEE_CLIENT_SECRET: ${{ secrets.OAUTH_GITEE_CLIENT_SECRET }}
OAUTH_GITEE_REDIRECT_URI: ${{ secrets.OAUTH_GITEE_REDIRECT_URI }}
OAUTH_TELEGRAM_BOT_NAME: ${{ secrets.OAUTH_TELEGRAM_BOT_NAME }}
OAUTH_TELEGRAM_BOT_TOKEN: ${{ secrets.OAUTH_TELEGRAM_BOT_TOKEN }}
OAUTH_TELEGRAM_WEBHOOK_SECRET: ${{ secrets.OAUTH_TELEGRAM_WEBHOOK_SECRET }}
OAUTH_GOOGLE_CLIENT_ID: ${{ secrets.OAUTH_GOOGLE_CLIENT_ID }}
OAUTH_GOOGLE_CLIENT_SECRET: ${{ secrets.OAUTH_GOOGLE_CLIENT_SECRET }}
OAUTH_GOOGLE_REDIRECT_URI: ${{ secrets.OAUTH_GOOGLE_REDIRECT_URI }}
OAUTH_MICROSOFT_CLIENT_ID: ${{ secrets.OAUTH_MICROSOFT_CLIENT_ID }}
OAUTH_MICROSOFT_CLIENT_SECRET: ${{ secrets.OAUTH_MICROSOFT_CLIENT_SECRET }}
OAUTH_BAIDU_CLIENT_ID: ${{ secrets.OAUTH_BAIDU_CLIENT_ID }}
OAUTH_BAIDU_CLIENT_SECRET: ${{ secrets.OAUTH_BAIDU_CLIENT_SECRET }}
OAUTH_DROPBOX_CLIENT_ID: ${{ secrets.OAUTH_DROPBOX_CLIENT_ID }}
OAUTH_DROPBOX_CLIENT_SECRET: ${{ secrets.OAUTH_DROPBOX_CLIENT_SECRET }}
OAUTH_WALLETCONNECT_PROJECT_ID: ${{ secrets.OAUTH_WALLETCONNECT_PROJECT_ID }}
OAUTH_WALLETCONNECT_SELF_PROXY: ${{ secrets.OAUTH_WALLETCONNECT_SELF_PROXY }}
run: |
SECRETS="NODEAUTH_LICENSE
OAUTH_ALLOW_ALL
OAUTH_ALLOWED_USERS
ENCRYPTION_KEY
JWT_SECRET"
OPTIONAL_VARS=(
"OAUTH_GITHUB_CLIENT_ID" "OAUTH_GITHUB_CLIENT_SECRET" "OAUTH_GITHUB_REDIRECT_URI"
"OAUTH_CLOUDFLARE_CLIENT_ID" "OAUTH_CLOUDFLARE_CLIENT_SECRET" "OAUTH_CLOUDFLARE_ORG_DOMAIN" "OAUTH_CLOUDFLARE_REDIRECT_URI"
"OAUTH_NODELOC_CLIENT_ID" "OAUTH_NODELOC_CLIENT_SECRET" "OAUTH_NODELOC_REDIRECT_URI"
"OAUTH_GITEE_CLIENT_ID" "OAUTH_GITEE_CLIENT_SECRET" "OAUTH_GITEE_REDIRECT_URI"
"OAUTH_TELEGRAM_BOT_NAME" "OAUTH_TELEGRAM_BOT_TOKEN" "OAUTH_TELEGRAM_WEBHOOK_SECRET"
"OAUTH_GOOGLE_CLIENT_ID" "OAUTH_GOOGLE_CLIENT_SECRET" "OAUTH_GOOGLE_REDIRECT_URI"
"OAUTH_MICROSOFT_CLIENT_ID" "OAUTH_MICROSOFT_CLIENT_SECRET"
"OAUTH_BAIDU_CLIENT_ID" "OAUTH_BAIDU_CLIENT_SECRET"
"OAUTH_DROPBOX_CLIENT_ID" "OAUTH_DROPBOX_CLIENT_SECRET"
"OAUTH_WALLETCONNECT_PROJECT_ID" "OAUTH_WALLETCONNECT_SELF_PROXY"
)
for VAR in "${OPTIONAL_VARS[@]}"; do
if [ -n "${!VAR}" ]; then SECRETS="$SECRETS
$VAR"; fi
done
echo "secrets<<EOF" >> $GITHUB_OUTPUT
echo "$SECRETS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Mask Sensitive Domain
run: |
if [ -n "${{ secrets.CLOUDFLARE_WORKER_SUBDOMAIN }}" ]; then
echo "::add-mask::${{ secrets.CLOUDFLARE_WORKER_SUBDOMAIN }}"
fi
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
wranglerVersion: '4.75.0'
command: deploy --minify
secrets: ${{ steps.secret_list.outputs.secrets }}
env:
OAUTH_ALLOW_ALL: ${{ secrets.OAUTH_ALLOW_ALL || 'false' }}
OAUTH_ALLOWED_USERS: ${{ secrets.OAUTH_ALLOWED_USERS }}
OAUTH_GITHUB_CLIENT_ID: ${{ secrets.OAUTH_GITHUB_CLIENT_ID }}
OAUTH_GITHUB_CLIENT_SECRET: ${{ secrets.OAUTH_GITHUB_CLIENT_SECRET }}
OAUTH_GITHUB_REDIRECT_URI: ${{ secrets.OAUTH_GITHUB_REDIRECT_URI }}
OAUTH_CLOUDFLARE_CLIENT_ID: ${{ secrets.OAUTH_CLOUDFLARE_CLIENT_ID }}
OAUTH_CLOUDFLARE_CLIENT_SECRET: ${{ secrets.OAUTH_CLOUDFLARE_CLIENT_SECRET }}
OAUTH_CLOUDFLARE_ORG_DOMAIN: ${{ secrets.OAUTH_CLOUDFLARE_ORG_DOMAIN }}
OAUTH_CLOUDFLARE_REDIRECT_URI: ${{ secrets.OAUTH_CLOUDFLARE_REDIRECT_URI }}
OAUTH_NODELOC_CLIENT_ID: ${{ secrets.OAUTH_NODELOC_CLIENT_ID }}
OAUTH_NODELOC_CLIENT_SECRET: ${{ secrets.OAUTH_NODELOC_CLIENT_SECRET }}
OAUTH_NODELOC_REDIRECT_URI: ${{ secrets.OAUTH_NODELOC_REDIRECT_URI }}
OAUTH_GITEE_CLIENT_ID: ${{ secrets.OAUTH_GITEE_CLIENT_ID }}
OAUTH_GITEE_CLIENT_SECRET: ${{ secrets.OAUTH_GITEE_CLIENT_SECRET }}
OAUTH_GITEE_REDIRECT_URI: ${{ secrets.OAUTH_GITEE_REDIRECT_URI }}
OAUTH_TELEGRAM_BOT_NAME: ${{ secrets.OAUTH_TELEGRAM_BOT_NAME }}
OAUTH_TELEGRAM_BOT_TOKEN: ${{ secrets.OAUTH_TELEGRAM_BOT_TOKEN }}
OAUTH_TELEGRAM_WEBHOOK_SECRET: ${{ secrets.OAUTH_TELEGRAM_WEBHOOK_SECRET }}
OAUTH_GOOGLE_CLIENT_ID: ${{ secrets.OAUTH_GOOGLE_CLIENT_ID }}
OAUTH_GOOGLE_CLIENT_SECRET: ${{ secrets.OAUTH_GOOGLE_CLIENT_SECRET }}
OAUTH_GOOGLE_REDIRECT_URI: ${{ secrets.OAUTH_GOOGLE_REDIRECT_URI }}
OAUTH_MICROSOFT_CLIENT_ID: ${{ secrets.OAUTH_MICROSOFT_CLIENT_ID }}
OAUTH_MICROSOFT_CLIENT_SECRET: ${{ secrets.OAUTH_MICROSOFT_CLIENT_SECRET }}
OAUTH_BAIDU_CLIENT_ID: ${{ secrets.OAUTH_BAIDU_CLIENT_ID }}
OAUTH_BAIDU_CLIENT_SECRET: ${{ secrets.OAUTH_BAIDU_CLIENT_SECRET }}
OAUTH_DROPBOX_CLIENT_ID: ${{ secrets.OAUTH_DROPBOX_CLIENT_ID }}
OAUTH_DROPBOX_CLIENT_SECRET: ${{ secrets.OAUTH_DROPBOX_CLIENT_SECRET }}
OAUTH_WALLETCONNECT_PROJECT_ID: ${{ secrets.OAUTH_WALLETCONNECT_PROJECT_ID }}
OAUTH_WALLETCONNECT_SELF_PROXY: ${{ secrets.OAUTH_WALLETCONNECT_SELF_PROXY }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
NODEAUTH_LICENSE: ${{ secrets.NODEAUTH_LICENSE }}
- name: Restore Wrangler Config
if: always()
run: |
if [ -f wrangler.toml.bak ]; then mv wrangler.toml.bak wrangler.toml; fi