fix: refresh access token per request to prevent 1-hour expiry 401 - #37
Open
no-problem-kyoichi wants to merge 1 commit into
Open
no-problem-kyoichi wants to merge 1 commit into
no-problem-kyoichi wants to merge 1 commit into
Conversation
Cloud Run の長時間稼働インスタンスで発生していた間欠的な Firestore / Storage の 401 Unauthenticated エラーを解消する。 ## 症状 Cloud Run instance が 1 時間以上生きると、その instance が捌く全 Firestore / Storage リクエストが Unauthenticated で失敗。新しい instance が spawn されるまで復旧しない。reading-memory プロジェクトで /v1/auth/initialize が間欠的に 500 を返す問題として観測されていた。 ## 根本原因 `FirestoreClient.token` と `StorageClient.token` が init 時のスナップ ショットで `public let` として固定されていた。`GCPEnvironment` 側に 5 分バッファの cache refresh 機構が存在したが、clients はそれを参照 していなかったため、1 時間後 (GCP access token の TTL) に失効した トークンを使い続ける結果となっていた。 ## 修正 新設の内部型 `TokenSource` で token 取得戦略を表現し、各 API リクエスト発行時に `tokenSource.currentToken()` を通じて最新トークン を取得する実装に変更: - `.dynamic` → `GCPEnvironment.shared.getAccessToken()` 経由 (cache の 5 分バッファ refresh に追従、metadata server への実 アクセスは 55 分に 1 回程度) - `.emulator` → ダミー "owner" トークン - `.staticToken` → `.explicit(projectId:token:)` で指定された 静的トークン 影響する operations: - `FirestoreClient`: getDocument, createDocument, updateDocument, deleteDocument, listDocuments, runQueryRaw - `StorageClient`: upload, download, delete, getMetadata - `AuthAdminClient.deleteUser` は元から per-request fetch 実装 のため、本修正の対象外 ## 後方互換 `public let token: String` は残置。init 時の値を返す。外部コードが 直接参照していた場合は振る舞い変わらず (失効するトークンが返る)。 将来削除する可能性があるため、doc コメントで非推奨を明示。 ## テスト `TokenSource` の 8 ケース (auto / autoWithDatabase / emulator / explicit 各モードの生成、currentToken() の返却値、dynamic case の契約) を新規追加。全 15 tests in GCPAuth suite pass。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FirestoreClient/StorageClientが init 時に捕獲した OAuth access token を使い続け、1 時間後に失効して全リクエストが401 Unauthenticatedで失敗する間欠バグを修正TokenSourceで取得戦略を明示(dynamic/emulator/staticToken)、各 API リクエスト発行時にtokenSource.currentToken()から最新を取得.auto/.autoWithDatabaseはGCPEnvironment.shared.getAccessToken()経由で、既存の 5 分バッファ cache refresh に追従public let token: Stringは後方互換のため残置(init 時の値を返す、非推奨コメント付き)背景
reading-memory の Cloud Run サーバーで
/v1/auth/initializeが間欠的に 500 を返す問題を観測。サーバーログに以下:調査の結果、
FirestoreClient.tokenがpublic letで init 時スナップショットに固定されており、長時間稼働する Cloud Run instance で GCP access token の TTL (1 時間) を超えると、その instance が捌く全 Firestore リクエストが失敗する構造であった。GCPEnvironmentactor 側の cache refresh は正しく動作していたが、clients が参照していなかった。AuthAdminClient.deleteUserは元からlet token = try await getAccessToken()を毎回呼ぶ実装で正しかったため、同じパターンをFirestoreClientとStorageClientにも適用した。修正範囲
新規ファイル
Sources/Internal/GCPAuth/TokenSource.swiftdynamic/emulator/staticToken)init(config:resolvedToken:)でGCPConfigurationからマッピングcurrentToken()で最新トークンを返す変更ファイル
Sources/FirestoreServer/Core/FirestoreClient.swiftinternal let tokenSource: TokenSourceを追加tokenSourceを初期化Sources/FirestoreServer/Operations/DocumentOperations.swiftgetDocument/createDocument/updateDocument/deleteDocument/listDocumentsの 5 箇所でtry await tokenSource.currentToken()を呼ぶよう変更Sources/FirestoreServer/Operations/QueryOperations.swiftrunQueryRawで同様の変更Sources/FirebaseStorageServer/Core/StorageClient.swiftinternal let tokenSource: TokenSourceを追加upload/download/delete/getMetadataの 4 箇所で同様の変更テスト
Tests/InternalTests/GCPAuthTests.swiftに 8 ケース追加currentToken()の emulator / static 返却値の確認全 15 tests in GCPAuth suite pass。
Test plan
swift build成功 (140s)swift test --filter GCPAuth全 pass (15 tests)swift test全 suite pass (connection refused の 1 network test は外部依存で本修正と無関係)後方互換
public let token: Stringは残置 (init 時の値を返す)。外部コードが直接参照していた場合、挙動は変わらず (失効するトークンを返す)。doc コメントで非推奨を明記。🤖 Generated with Claude Code