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:
|
env:
|
||||||
OAUTH_DIR: oauth_new
|
OAUTH_DIR: oauth_new
|
||||||
|
KEEP_DEPLOYMENTS: 5
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
@ -108,3 +109,49 @@ jobs:
|
|||||||
directory: my-blog/public
|
directory: my-blog/public
|
||||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
wranglerVersion: '3'
|
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
|
const did = session.sub || session.did
|
||||||
let handle = session.handle || 'unknown'
|
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 }
|
this.sessionInfo = { did, handle }
|
||||||
|
|
||||||
// Resolve handle if missing
|
// Resolve handle if missing
|
||||||
if (handle === 'unknown' && this.agent) {
|
if (handle === 'unknown' && this.agent) {
|
||||||
try {
|
try {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
const profile = await this.agent.getProfile({ actor: did })
|
const profile = await this.agent.getProfile({ actor: did })
|
||||||
handle = profile.data.handle
|
handle = profile.data.handle
|
||||||
this.sessionInfo.handle = handle
|
this.sessionInfo.handle = handle
|
||||||
@ -86,7 +98,9 @@ export class OAuthService {
|
|||||||
await this.initialize()
|
await this.initialize()
|
||||||
|
|
||||||
const client = isSyuIsHandle(handle) ? this.clients.syu : this.clients.bsky
|
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()
|
window.location.href = authUrl.toString()
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,15 @@ async function getDid(handle) {
|
|||||||
// DIDからプロフィール情報を取得
|
// DIDからプロフィール情報を取得
|
||||||
async function getProfile(did, handle) {
|
async function getProfile(did, handle) {
|
||||||
try {
|
try {
|
||||||
|
// Determine which public API to use based on handle
|
||||||
const pds = await getPdsFromHandle(handle)
|
const pds = await getPdsFromHandle(handle)
|
||||||
const apiConfig = getApiConfig(pds)
|
const apiConfig = getApiConfig(pds)
|
||||||
|
|
||||||
logger.log('Getting profile for DID:', did, 'using API:', apiConfig.bsky)
|
// Use the appropriate public API endpoint
|
||||||
const response = await fetch(`${apiConfig.bsky}/xrpc/app.bsky.actor.getProfile?actor=${did}`)
|
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) {
|
if (!response.ok) {
|
||||||
throw new Error(`Profile API error: ${response.status} ${response.statusText}`)
|
throw new Error(`Profile API error: ${response.status} ${response.statusText}`)
|
||||||
|
Reference in New Issue
Block a user