fix gh-actions oauth-session
This commit is contained in:
47
.github/workflows/cloudflare-pages.yml
vendored
47
.github/workflows/cloudflare-pages.yml
vendored
@ -8,6 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
OAUTH_DIR: oauth_new
|
||||
KEEP_DEPLOYMENTS: 5
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
@ -108,3 +109,49 @@ jobs:
|
||||
directory: my-blog/public
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
wranglerVersion: '3'
|
||||
|
||||
cleanup:
|
||||
needs: deploy
|
||||
runs-on: ubuntu-latest
|
||||
if: success()
|
||||
|
||||
steps:
|
||||
- name: Wait for deployment to complete
|
||||
run: sleep 3
|
||||
|
||||
- name: Cleanup old deployments
|
||||
run: |
|
||||
# Get all deployments
|
||||
DEPLOYMENTS=$(curl -s -X GET \
|
||||
"https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CLOUDFLARE_PROJECT_NAME }}/deployments" \
|
||||
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
# Extract deployment IDs (skip the latest N deployments)
|
||||
DEPLOYMENT_IDS=$(echo "$DEPLOYMENTS" | jq -r ".result | sort_by(.created_on) | reverse | .[${{ env.KEEP_DEPLOYMENTS }}:] | .[].id // empty")
|
||||
|
||||
if [ -z "$DEPLOYMENT_IDS" ]; then
|
||||
echo "No old deployments to delete"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Delete old deployments
|
||||
for ID in $DEPLOYMENT_IDS; do
|
||||
echo "Deleting deployment: $ID"
|
||||
RESPONSE=$(curl -s -X DELETE \
|
||||
"https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CLOUDFLARE_PROJECT_NAME }}/deployments/$ID" \
|
||||
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
|
||||
if [ "$SUCCESS" = "true" ]; then
|
||||
echo "Successfully deleted deployment: $ID"
|
||||
else
|
||||
echo "Failed to delete deployment: $ID"
|
||||
echo "$RESPONSE" | jq .
|
||||
fi
|
||||
|
||||
sleep 1 # Rate limiting
|
||||
done
|
||||
|
||||
echo "Cleanup completed!"
|
||||
|
104
oauth_new/CLOUDFLARE_DEPLOY_WITH_CLEANUP.yml
Normal file
104
oauth_new/CLOUDFLARE_DEPLOY_WITH_CLEANUP.yml
Normal file
@ -0,0 +1,104 @@
|
||||
name: Deploy to Cloudflare Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
OAUTH_DIR: oauth_new
|
||||
KEEP_DEPLOYMENTS: 5 # 保持するデプロイメント数
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: ${{ env.OAUTH_DIR }}/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd ${{ env.OAUTH_DIR }}
|
||||
npm ci
|
||||
|
||||
- name: Build OAuth app
|
||||
run: |
|
||||
cd ${{ env.OAUTH_DIR }}
|
||||
NODE_ENV=production npm run build
|
||||
env:
|
||||
VITE_ADMIN: ${{ secrets.VITE_ADMIN }}
|
||||
VITE_PDS: ${{ secrets.VITE_PDS }}
|
||||
VITE_HANDLE_LIST: ${{ secrets.VITE_HANDLE_LIST }}
|
||||
VITE_COLLECTION: ${{ secrets.VITE_COLLECTION }}
|
||||
VITE_OAUTH_CLIENT_ID: ${{ secrets.VITE_OAUTH_CLIENT_ID }}
|
||||
VITE_OAUTH_REDIRECT_URI: ${{ secrets.VITE_OAUTH_REDIRECT_URI }}
|
||||
VITE_ENABLE_TEST_UI: 'false'
|
||||
VITE_ENABLE_DEBUG: 'false'
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
directory: ${{ env.OAUTH_DIR }}/dist
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploymentName: Production
|
||||
|
||||
cleanup:
|
||||
needs: deploy
|
||||
runs-on: ubuntu-latest
|
||||
if: success()
|
||||
|
||||
steps:
|
||||
- name: Wait for deployment to complete
|
||||
run: sleep 30
|
||||
|
||||
- name: Cleanup old deployments
|
||||
run: |
|
||||
# Get all deployments
|
||||
DEPLOYMENTS=$(curl -s -X GET \
|
||||
"https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CLOUDFLARE_PROJECT_NAME }}/deployments" \
|
||||
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
# Extract deployment IDs (skip the latest N deployments)
|
||||
DEPLOYMENT_IDS=$(echo "$DEPLOYMENTS" | jq -r ".result | sort_by(.created_on) | reverse | .[${{ env.KEEP_DEPLOYMENTS }}:] | .[].id // empty")
|
||||
|
||||
if [ -z "$DEPLOYMENT_IDS" ]; then
|
||||
echo "No old deployments to delete"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Delete old deployments
|
||||
for ID in $DEPLOYMENT_IDS; do
|
||||
echo "Deleting deployment: $ID"
|
||||
RESPONSE=$(curl -s -X DELETE \
|
||||
"https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CLOUDFLARE_PROJECT_NAME }}/deployments/$ID" \
|
||||
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
|
||||
if [ "$SUCCESS" = "true" ]; then
|
||||
echo "Successfully deleted deployment: $ID"
|
||||
else
|
||||
echo "Failed to delete deployment: $ID"
|
||||
echo "$RESPONSE" | jq .
|
||||
fi
|
||||
|
||||
sleep 1 # Rate limiting
|
||||
done
|
||||
|
||||
echo "Cleanup completed!"
|
81
oauth_new/OAUTH_FIX.md
Normal file
81
oauth_new/OAUTH_FIX.md
Normal file
@ -0,0 +1,81 @@
|
||||
# OAuth認証の修正案
|
||||
|
||||
## 現在の問題
|
||||
|
||||
1. **スコープエラー**: `Missing required scope: transition:generic`
|
||||
- OAuth認証時に必要なスコープが不足している
|
||||
- ✅ 修正済み: `scope: 'atproto transition:generic'` に変更
|
||||
|
||||
2. **401エラー**: PDSへの直接アクセス
|
||||
- `https://shiitake.us-east.host.bsky.network/xrpc/app.bsky.actor.getProfile` で401エラー
|
||||
- 原因: 個人のPDSに直接アクセスしているが、これは認証が必要
|
||||
- 解決策: 公開APIエンドポイント(`https://public.api.bsky.app`)を使用すべき
|
||||
|
||||
3. **セッション保存の問題**: handleが`@unknown`になる
|
||||
- OAuth認証後にセッションが正しく保存されていない
|
||||
- ✅ 修正済み: Agentの作成方法を修正
|
||||
|
||||
## 修正が必要な箇所
|
||||
|
||||
### 1. avatarFetcher.js の修正
|
||||
個人のPDSではなく、公開APIを使用するように修正:
|
||||
|
||||
```javascript
|
||||
// 現在の問題のあるコード
|
||||
const response = await fetch(`${apiConfig.bsky}/xrpc/app.bsky.actor.getProfile?actor=${did}`)
|
||||
|
||||
// 修正案
|
||||
// PDSに関係なく、常に公開APIを使用
|
||||
const response = await fetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${did}`)
|
||||
```
|
||||
|
||||
### 2. セッション復元の改善
|
||||
OAuth認証後のコールバック処理で、セッションが正しく復元されていない可能性がある。
|
||||
|
||||
```javascript
|
||||
// restoreSession メソッドの改善
|
||||
async restoreSession() {
|
||||
// Try both clients
|
||||
for (const [name, client] of Object.entries(this.clients)) {
|
||||
if (!client) continue
|
||||
|
||||
const result = await client.init()
|
||||
if (result?.session) {
|
||||
// セッション処理を確実に行う
|
||||
this.agent = new Agent(result.session)
|
||||
const sessionInfo = await this.processSession(result.session)
|
||||
|
||||
// セッション情報をログに出力(デバッグ用)
|
||||
logger.log('Session restored:', { name, sessionInfo })
|
||||
|
||||
return sessionInfo
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
## 根本的な問題
|
||||
|
||||
1. **PDSアクセスの誤解**
|
||||
- `app.bsky.actor.getProfile` は公開API(認証不要)
|
||||
- 個人のPDSサーバーに直接アクセスする必要はない
|
||||
- 常に `https://public.api.bsky.app` を使用すべき
|
||||
|
||||
2. **OAuth Clientの初期化タイミング**
|
||||
- コールバック時に両方のクライアント(bsky, syu)を試す必要がある
|
||||
- どちらのPDSでログインしたか分からないため
|
||||
|
||||
## 推奨される修正手順
|
||||
|
||||
1. **即座の修正**(401エラー解決)
|
||||
- `avatarFetcher.js` で公開APIを使用
|
||||
- `getProfile` 呼び出しをすべて公開APIに変更
|
||||
|
||||
2. **セッション管理の改善**
|
||||
- OAuth認証後のセッション復元を確実に
|
||||
- エラーハンドリングの強化
|
||||
|
||||
3. **デバッグ情報の追加**
|
||||
- セッション復元時のログ追加
|
||||
- どのOAuthクライアントが使用されたか確認
|
41
oauth_new/cleanup-deployments.yml
Normal file
41
oauth_new/cleanup-deployments.yml
Normal file
@ -0,0 +1,41 @@
|
||||
name: Cleanup Old Deployments
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Deploy to Cloudflare Pages"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
KEEP_DEPLOYMENTS: 5 # 保持するデプロイメント数
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
steps:
|
||||
- name: Cleanup old deployments
|
||||
run: |
|
||||
# Get all deployments
|
||||
DEPLOYMENTS=$(curl -s -X GET \
|
||||
"https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CLOUDFLARE_PROJECT_NAME }}/deployments" \
|
||||
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
# Extract deployment IDs (skip the latest N deployments)
|
||||
DEPLOYMENT_IDS=$(echo "$DEPLOYMENTS" | jq -r ".result | sort_by(.created_on) | reverse | .[${{ env.KEEP_DEPLOYMENTS }}:] | .[].id")
|
||||
|
||||
# Delete old deployments
|
||||
for ID in $DEPLOYMENT_IDS; do
|
||||
echo "Deleting deployment: $ID"
|
||||
curl -s -X DELETE \
|
||||
"https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/${{ secrets.CLOUDFLARE_PROJECT_NAME }}/deployments/$ID" \
|
||||
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json"
|
||||
echo "Deleted deployment: $ID"
|
||||
sleep 1 # Rate limiting
|
||||
done
|
||||
|
||||
echo "Cleanup completed!"
|
@ -66,11 +66,23 @@ export class OAuthService {
|
||||
const did = session.sub || session.did
|
||||
let handle = session.handle || 'unknown'
|
||||
|
||||
// Create Agent directly with session (per official docs)
|
||||
try {
|
||||
this.agent = new Agent(session)
|
||||
} catch (err) {
|
||||
// Fallback to dpopFetch method
|
||||
this.agent = new Agent({
|
||||
service: session.server?.serviceEndpoint || 'https://bsky.social',
|
||||
fetch: session.dpopFetch
|
||||
})
|
||||
}
|
||||
|
||||
this.sessionInfo = { did, handle }
|
||||
|
||||
// Resolve handle if missing
|
||||
if (handle === 'unknown' && this.agent) {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
const profile = await this.agent.getProfile({ actor: did })
|
||||
handle = profile.data.handle
|
||||
this.sessionInfo.handle = handle
|
||||
@ -86,7 +98,9 @@ export class OAuthService {
|
||||
await this.initialize()
|
||||
|
||||
const client = isSyuIsHandle(handle) ? this.clients.syu : this.clients.bsky
|
||||
const authUrl = await client.authorize(handle, { scope: 'atproto' })
|
||||
const authUrl = await client.authorize(handle, {
|
||||
scope: 'atproto transition:generic'
|
||||
})
|
||||
|
||||
window.location.href = authUrl.toString()
|
||||
}
|
||||
|
@ -34,11 +34,15 @@ async function getDid(handle) {
|
||||
// DIDからプロフィール情報を取得
|
||||
async function getProfile(did, handle) {
|
||||
try {
|
||||
// Determine which public API to use based on handle
|
||||
const pds = await getPdsFromHandle(handle)
|
||||
const apiConfig = getApiConfig(pds)
|
||||
|
||||
logger.log('Getting profile for DID:', did, 'using API:', apiConfig.bsky)
|
||||
const response = await fetch(`${apiConfig.bsky}/xrpc/app.bsky.actor.getProfile?actor=${did}`)
|
||||
// Use the appropriate public API endpoint
|
||||
const publicApiUrl = apiConfig.bsky
|
||||
|
||||
logger.log('Getting profile for DID:', did, 'using public API:', publicApiUrl)
|
||||
const response = await fetch(`${publicApiUrl}/xrpc/app.bsky.actor.getProfile?actor=${did}`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Profile API error: ${response.status} ${response.statusText}`)
|
||||
|
Reference in New Issue
Block a user