-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
One Token, Multiple Credentials #2970
base: main
Are you sure you want to change the base?
Conversation
a99d4ef
to
c4cfe1b
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2970 +/- ##
==========================================
+ Coverage 91.61% 91.63% +0.02%
==========================================
Files 346 347 +1
Lines 12744 12865 +121
==========================================
+ Hits 11675 11789 +114
- Misses 1069 1076 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome work @elias-ba . There are few details left only for future maintainability and it's a beautiful goal!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @elias-ba , this looks really great. I've left some comments regarding the migration timestamp and the script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to update the timestamp of the migration file to latest. This way we can always just mix ecto.rollback
if there is a problem. I think there are migrations already in main
which are later than 20250224
priv/repo/migrate_oauth_tokens.exs
Outdated
# then updates credentials to reference these tokens instead of storing the body directly. | ||
|
||
# Make sure the application is started | ||
Mix.Task.run("app.start") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 not sure this can run in release mode given that mix
won't be available
- OauthToken table - Migrate old oauth credentials to the new schema
- OauthToken data model - Update Credential, and OauthClient to align with new data model
… context to align with new data model
Description
Overview
This PR refactors how OAuth tokens are stored and referenced, so that multiple “credentials” with the same scope set (for the same OAuth provider) can share a single refresh token. This prevents errors caused when OAuth providers (e.g. Google) invalidate or omit a refresh token if it has already been issued for the same user+client+scopes combination.
Why?
Previously, each credential row stored its own OAuth tokens in
credentials.body
. This was problematic when:What’s New?
We introduce a new
oauth_tokens
table (previously referred to ascredential_bodies
) where the actual OAuth token data (includingrefresh_token
,access_token
,expires_at
, etc.) now lives. Each row inoauth_tokens
is keyed by[user_id, oauth_client_id, scope set]
. Thecredentials
table references this token row viaoauth_token_id
.Problems Addressed
Multiple Credentials, Same Scopes
refresh_token
for the second.refresh_token
—which blocked the user.Reauthorization Overwrites Old Tokens
Goals & Approach
[User, OauthClient, Exact Scope Set]
oauth_tokens
(fields:user_id
,oauth_client_id
,scopes
,body
).credentials
referencesoauth_tokens
viaoauth_token_id
.oauth_tokens
.oauth_tokens
.credentials.body
is then cleared for OAuth creds, and we setoauth_token_id
.Result: Multiple credentials in the UI can share a single refresh token behind the scenes. If the refresh token is reissued, we update it once and all referencing credentials keep working.
Example Flow
User Creates Credential “Google Sheets 1”
refresh_token=ABC123
.oauth_tokens
record with that refresh token, and onecredentials
record pointing to it.User Creates Credential “Google Sheets 2”
oauth_tokens
.credentials
record pointing to the existing token row. Both credentials share the token.Reauthorization
oauth_tokens.body
with the new refresh token.oauth_tokens
record automatically reference the new token.Technical Implementation
oauth_tokens
Tableid
,user_id
,oauth_client_id
,scopes
,body (encrypted)
, timestamps.[user_id, oauth_client_id, scopes]
.credentials
Tableoauth_token_id
→ referencesoauth_tokens
.body
column is set to hold other credential fields not directly related to OAuth for example theapiVersion
andsandbox
post-migration.oauth_client_id
column incredentials
(it now lives inoauth_tokens
).Ecto Changes
OauthToken
schema handles creation, updating, and retrieving of tokens.Credential
referencesOauthToken
for any OAuth credentials.Credentials.validate_oauth_token_data/5
ensures we either have a refresh token or detect an existing token for a given scope set.Migration Script
credentials.body
, upserts intooauth_tokens
.credentials
to point to the newly inserted token record.oauth_client_id
column fromcredentials
once migration is verified.Notes / Future Considerations
Conclusion
By splitting out token storage into
oauth_tokens
keyed by[user_id, oauth_client_id, exact scopes]
, we fix edge cases around missing refresh tokens, and keep multiple credentials in sync upon reauthorization. This streamlines the OAuth process and prevents accidental breakage when providers reuse or invalidate tokens.Closes #2908
Might close #2945 too
Validation steps
Additional notes for the reviewer
AI Usage
Please disclose how you've used AI in this work (it's cool, we just want to know!):
You can read more details in our Responsible AI Policy
Pre-submission checklist
:owner
,:admin
,:editor
,:viewer
)