Serialize Google connect and disconnect; consume connect state once
A disconnect read the stored refresh token, revoked it at Google, then deleted the row only if it still held that token. Google revokes the whole grant, so a reconnect whose token was issued before that revoke was kept as connected with a dead token. Connect's exchange-and-store and disconnect's read-revoke-delete now hold one mutex across the Google request and the database write, and the delete is unconditional (the conditional delete and ErrChanged are gone: the lock is the one guard). The connect cookie was single-use only in that the browser was asked to drop it; a saved copy verified again until it expired. The service now keeps each started connect's state in memory until the cookie expires and the callback consumes it, so a replayed cookie is refused as bad state. The fake Google now issues a distinct refresh token per exchange, revokes the whole grant like Google, and can hold a token or revoke request so tests can interleave a reconnect and a disconnect. Maestro task 1e9d7850-3a09-4572-b8a1-cb18cd4774cb (MQ125-01, MQ125-02). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,12 +106,9 @@ ON CONFLICT (id) DO UPDATE SET
|
||||
updated_at = excluded.updated_at
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteIntegrationsGoogle :execrows
|
||||
-- Deletes the row only while it still holds refresh_token_ct, so a
|
||||
-- connection made after the caller read the row is kept. Returns the number
|
||||
-- of rows deleted.
|
||||
-- name: DeleteIntegrationsGoogle :exec
|
||||
DELETE FROM integrations_google
|
||||
WHERE id = 1 AND refresh_token_ct = ?;
|
||||
WHERE id = 1;
|
||||
|
||||
-- name: GetChatSettings :one
|
||||
-- Returns sql.ErrNoRows until the row exists.
|
||||
|
||||
Reference in New Issue
Block a user