Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -7137,6 +7137,16 @@ func (q *querier) UpdateUserLink(ctx context.Context, arg database.UpdateUserLin
return fetchAndQuery(q.log, q.auth, policy.ActionUpdatePersonal, fetch, q.db.UpdateUserLink)(ctx, arg)
}

func (q *querier) UpdateUserLinkRefreshToken(ctx context.Context, arg database.UpdateUserLinkRefreshTokenParams) (database.UserLink, error) {
fetch := func(ctx context.Context, arg database.UpdateUserLinkRefreshTokenParams) (database.UserLink, error) {
return q.db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
UserID: arg.UserID,
LoginType: arg.LoginType,
})
}
return fetchAndQuery(q.log, q.auth, policy.ActionUpdatePersonal, fetch, q.db.UpdateUserLinkRefreshToken)(ctx, arg)
}

func (q *querier) UpdateUserLoginType(ctx context.Context, arg database.UpdateUserLoginTypeParams) (database.User, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
return database.User{}, err
Expand Down
15 changes: 15 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2976,6 +2976,21 @@ func (s *MethodTestSuite) TestUser() {
dbm.EXPECT().UpdateUserLink(gomock.Any(), arg).Return(link, nil).AnyTimes()
check.Args(arg).Asserts(link, policy.ActionUpdatePersonal).Returns(link)
}))
s.Run("UpdateUserLinkRefreshToken", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
link := testutil.Fake(s.T(), faker, database.UserLink{})
arg := database.UpdateUserLinkRefreshTokenParams{
OAuthAccessToken: link.OAuthAccessToken,
OAuthRefreshToken: link.OAuthRefreshToken,
OAuthExpiry: link.OAuthExpiry,
UserID: link.UserID,
LoginType: link.LoginType,
Claims: database.UserLinkClaims{},
OldOauthRefreshToken: link.OAuthRefreshToken,
}
dbm.EXPECT().GetUserLinkByUserIDLoginType(gomock.Any(), database.GetUserLinkByUserIDLoginTypeParams{UserID: link.UserID, LoginType: link.LoginType}).Return(link, nil).AnyTimes()
dbm.EXPECT().UpdateUserLinkRefreshToken(gomock.Any(), arg).Return(link, nil).AnyTimes()
check.Args(arg).Asserts(link, policy.ActionUpdatePersonal).Returns(link)
}))
s.Run("UpdateUserRoles", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
u := testutil.Fake(s.T(), faker, database.User{RBACRoles: []string{codersdk.RoleTemplateAdmin}})
o := u
Expand Down
8 changes: 8 additions & 0 deletions coderd/database/dbmetrics/querymetrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions coderd/database/dbmock/dbmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions coderd/database/queries/user_links.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ SET
WHERE
user_id = $7 AND login_type = $8 RETURNING *;

-- name: UpdateUserLinkRefreshToken :one
-- Optimistic lock: only update the row if the refresh token in the database
-- still matches the one we read before attempting the refresh. This prevents
-- a concurrent caller that lost a token-refresh race (across replicas, where
-- in-process deduplication via singleflight cannot reach) from overwriting a
-- valid token stored by the winner. Callers should treat sql.ErrNoRows as
-- "another caller refreshed first" and re-read the row rather than erroring.
UPDATE
user_links
SET
oauth_access_token = @oauth_access_token,
oauth_access_token_key_id = @oauth_access_token_key_id,
oauth_refresh_token = @oauth_refresh_token,
oauth_refresh_token_key_id = @oauth_refresh_token_key_id,
oauth_expiry = @oauth_expiry,
claims = @claims
WHERE
user_id = @user_id
AND
login_type = @login_type
AND
oauth_refresh_token = @old_oauth_refresh_token
RETURNING *;
Comment on lines +53 to +75

@Emyrk Emyrk May 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Also implement the encryption side of this query in dbcrypt

Example: https://github.com/coder/coder/blob/main/enterprise/dbcrypt/dbcrypt.go#L175-L175


-- name: OIDCClaimFields :many
-- OIDCClaimFields returns a list of distinct keys in the the merged_claims fields.
-- This query is used to generate the list of available sync fields for idp sync settings.
Expand Down
Loading
Loading