From d54e56af5e23c9abd2354d31201903c8fe7a44cb Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 8 Jan 2024 15:19:48 -0900 Subject: [PATCH 1/8] Add OAuth2 provider codes and tokens to database --- coderd/database/dbauthz/dbauthz.go | 67 +++++ coderd/database/dbauthz/dbauthz_test.go | 130 +++++++++ coderd/database/dbgen/dbgen.go | 26 ++ coderd/database/dbmem/dbmem.go | 263 +++++++++++++++++- coderd/database/dbmetrics/dbmetrics.go | 63 +++++ coderd/database/dbmock/dbmock.go | 132 +++++++++ coderd/database/dump.sql | 62 ++++- coderd/database/foreign_key_constraint.go | 4 + .../000186_oauth2_provider_codes.down.sql | 8 + .../000186_oauth2_provider_codes.up.sql | 44 +++ .../000186_oauth2_provider_codes.up.sql | 21 ++ coderd/database/modelmethods.go | 16 ++ coderd/database/models.go | 35 ++- coderd/database/querier.go | 9 + coderd/database/queries.sql.go | 253 +++++++++++++++++ coderd/database/queries/oauth2.sql | 71 +++++ coderd/database/sqlc.yaml | 4 + coderd/database/unique_constraint.go | 146 +++++----- coderd/rbac/object.go | 12 +- coderd/rbac/object_gen.go | 1 + coderd/rbac/roles.go | 2 + 21 files changed, 1287 insertions(+), 82 deletions(-) create mode 100644 coderd/database/migrations/000186_oauth2_provider_codes.down.sql create mode 100644 coderd/database/migrations/000186_oauth2_provider_codes.up.sql create mode 100644 coderd/database/migrations/testdata/fixtures/000186_oauth2_provider_codes.up.sql diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a5b295e2e35eb..a9abce9712ae5 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -821,6 +821,25 @@ func (q *querier) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) return q.db.DeleteOAuth2ProviderAppByID(ctx, id) } +func (q *querier) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error { + code, err := q.db.GetOAuth2ProviderAppCodeByID(ctx, id) + if err != nil { + return err + } + if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(code.UserID.String())); err != nil { + return err + } + return q.db.DeleteOAuth2ProviderAppCodeByID(ctx, id) +} + +func (q *querier) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error { + if err := q.authorizeContext(ctx, rbac.ActionDelete, + rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(arg.UserID.String())); err != nil { + return err + } + return q.db.DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx, arg) +} + func (q *querier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error { if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceOAuth2ProviderAppSecret); err != nil { return err @@ -828,6 +847,14 @@ func (q *querier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid return q.db.DeleteOAuth2ProviderAppSecretByID(ctx, id) } +func (q *querier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error { + if err := q.authorizeContext(ctx, rbac.ActionDelete, + rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(arg.UserID.String())); err != nil { + return err + } + return q.db.DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx, arg) +} + func (q *querier) DeleteOldProvisionerDaemons(ctx context.Context) error { if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceSystem); err != nil { return err @@ -1161,6 +1188,18 @@ func (q *querier) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (d return q.db.GetOAuth2ProviderAppByID(ctx, id) } +func (q *querier) GetOAuth2ProviderAppCodeByAppIDAndSecret(ctx context.Context, arg database.GetOAuth2ProviderAppCodeByAppIDAndSecretParams) (database.OAuth2ProviderAppCode, error) { + return fetch(q.log, q.auth, q.db.GetOAuth2ProviderAppCodeByAppIDAndSecret)(ctx, arg) +} + +func (q *querier) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) { + return fetch(q.log, q.auth, q.db.GetOAuth2ProviderAppCodeByID)(ctx, id) +} + +func (q *querier) GetOAuth2ProviderAppSecretByAppIDAndSecret(ctx context.Context, arg database.GetOAuth2ProviderAppSecretByAppIDAndSecretParams) (database.OAuth2ProviderAppSecret, error) { + return fetch(q.log, q.auth, q.db.GetOAuth2ProviderAppSecretByAppIDAndSecret)(ctx, arg) +} + func (q *querier) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) { if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderAppSecret); err != nil { return database.OAuth2ProviderAppSecret{}, err @@ -1182,6 +1221,15 @@ func (q *querier) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAuth2P return q.db.GetOAuth2ProviderApps(ctx) } +func (q *querier) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) { + // These two authz checks make sure the caller can read all their own tokens. + if err := q.authorizeContext(ctx, rbac.ActionRead, + rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(userID.String())); err != nil { + return []database.GetOAuth2ProviderAppsByUserIDRow{}, err + } + return q.db.GetOAuth2ProviderAppsByUserID(ctx, userID) +} + func (q *querier) GetOAuthSigningKey(ctx context.Context) (string, error) { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return "", err @@ -2203,6 +2251,14 @@ func (q *querier) InsertOAuth2ProviderApp(ctx context.Context, arg database.Inse return q.db.InsertOAuth2ProviderApp(ctx, arg) } +func (q *querier) InsertOAuth2ProviderAppCode(ctx context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) { + if err := q.authorizeContext(ctx, rbac.ActionCreate, + rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(arg.UserID.String())); err != nil { + return database.OAuth2ProviderAppCode{}, err + } + return q.db.InsertOAuth2ProviderAppCode(ctx, arg) +} + func (q *querier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceOAuth2ProviderAppSecret); err != nil { return database.OAuth2ProviderAppSecret{}, err @@ -2210,6 +2266,17 @@ func (q *querier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg databas return q.db.InsertOAuth2ProviderAppSecret(ctx, arg) } +func (q *querier) InsertOAuth2ProviderAppToken(ctx context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) { + key, err := q.db.GetAPIKeyByID(ctx, arg.APIKeyID) + if err != nil { + return database.OAuth2ProviderAppToken{}, err + } + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(key.UserID.String())); err != nil { + return database.OAuth2ProviderAppToken{}, err + } + return q.db.InsertOAuth2ProviderAppToken(ctx, arg) +} + func (q *querier) InsertOrganization(ctx context.Context, arg database.InsertOrganizationParams) (database.Organization, error) { return insert(q.log, q.auth, rbac.ResourceOrganization, q.db.InsertOrganization)(ctx, arg) } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index d9444278722e7..79a22081b0bae 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2220,6 +2220,34 @@ func (s *MethodTestSuite) TestOAuth2ProviderApps() { app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) check.Args(app.ID).Asserts(rbac.ResourceOAuth2ProviderApp, rbac.ActionRead).Returns(app) })) + s.Run("GetOAuth2ProviderAppsByUserID", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + key, _ := dbgen.APIKey(s.T(), db, database.APIKey{ + UserID: user.ID, + }) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + _ = dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{ + AppID: app.ID, + }) + for i := 0; i < 5; i++ { + _ = dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{ + AppSecretID: secret.ID, + APIKeyID: key.ID, + }) + } + check.Args(user.ID).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead).Returns([]database.GetOAuth2ProviderAppsByUserIDRow{ + { + OAuth2ProviderApp: database.OAuth2ProviderApp{ + ID: app.ID, + CallbackURL: app.CallbackURL, + Icon: app.Icon, + Name: app.Name, + }, + TokenCount: 5, + }, + }) + })) s.Run("InsertOAuth2ProviderApp", s.Subtest(func(db database.Store, check *expects) { check.Args(database.InsertOAuth2ProviderAppParams{}).Asserts(rbac.ResourceOAuth2ProviderApp, rbac.ActionCreate) })) @@ -2265,6 +2293,16 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppSecrets() { }) check.Args(secret.ID).Asserts(rbac.ResourceOAuth2ProviderAppSecret, rbac.ActionRead).Returns(secret) })) + s.Run("GetOAuth2ProviderAppSecretByAppIDAndSecret", s.Subtest(func(db database.Store, check *expects) { + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{ + AppID: app.ID, + }) + check.Args(database.GetOAuth2ProviderAppSecretByAppIDAndSecretParams{ + AppID: app.ID, + HashedSecret: secret.HashedSecret, + }).Asserts(rbac.ResourceOAuth2ProviderAppSecret, rbac.ActionRead).Returns(secret) + })) s.Run("InsertOAuth2ProviderAppSecret", s.Subtest(func(db database.Store, check *expects) { app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) check.Args(database.InsertOAuth2ProviderAppSecretParams{ @@ -2290,3 +2328,95 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppSecrets() { check.Args(secret.ID).Asserts(rbac.ResourceOAuth2ProviderAppSecret, rbac.ActionDelete) })) } + +func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() { + s.Run("GetOAuth2ProviderAppCodeByID", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{ + AppID: app.ID, + UserID: user.ID, + }) + check.Args(code.ID).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead).Returns(code) + })) + s.Run("GetOAuth2ProviderAppCodeByAppIDAndSecret", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{ + AppID: app.ID, + UserID: user.ID, + }) + check.Args(database.GetOAuth2ProviderAppCodeByAppIDAndSecretParams{ + AppID: app.ID, + HashedSecret: code.HashedSecret, + }).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead).Returns(code) + })) + s.Run("InsertOAuth2ProviderAppCode", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + check.Args(database.InsertOAuth2ProviderAppCodeParams{ + AppID: app.ID, + UserID: user.ID, + }).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionCreate) + })) + s.Run("DeleteOAuth2ProviderAppCodeByID", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + code := dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{ + AppID: app.ID, + UserID: user.ID, + }) + check.Args(code.ID).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionDelete) + })) + s.Run("DeleteOAuth2ProviderAppCodesByAppAndUserID", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + for i := 0; i < 5; i++ { + _ = dbgen.OAuth2ProviderAppCode(s.T(), db, database.OAuth2ProviderAppCode{ + AppID: app.ID, + UserID: user.ID, + }) + } + check.Args(database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams{ + AppID: app.ID, + UserID: user.ID, + }).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionDelete) + })) +} + +func (s *MethodTestSuite) TestOAuth2ProviderAppTokens() { + s.Run("InsertOAuth2ProviderAppToken", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + key, _ := dbgen.APIKey(s.T(), db, database.APIKey{ + UserID: user.ID, + }) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{ + AppID: app.ID, + }) + check.Args(database.InsertOAuth2ProviderAppTokenParams{ + AppSecretID: secret.ID, + APIKeyID: key.ID, + }).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionCreate) + })) + s.Run("DeleteOAuth2ProviderAppTokensByAppAndUserID", s.Subtest(func(db database.Store, check *expects) { + user := dbgen.User(s.T(), db, database.User{}) + key, _ := dbgen.APIKey(s.T(), db, database.APIKey{ + UserID: user.ID, + }) + app := dbgen.OAuth2ProviderApp(s.T(), db, database.OAuth2ProviderApp{}) + secret := dbgen.OAuth2ProviderAppSecret(s.T(), db, database.OAuth2ProviderAppSecret{ + AppID: app.ID, + }) + for i := 0; i < 5; i++ { + _ = dbgen.OAuth2ProviderAppToken(s.T(), db, database.OAuth2ProviderAppToken{ + AppSecretID: secret.ID, + APIKeyID: key.ID, + }) + } + check.Args(database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams{ + AppID: app.ID, + UserID: user.ID, + }).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionDelete) + })) +} diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index a4101151d2858..accc593712658 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -704,6 +704,32 @@ func OAuth2ProviderAppSecret(t testing.TB, db database.Store, seed database.OAut return app } +func OAuth2ProviderAppCode(t testing.TB, db database.Store, seed database.OAuth2ProviderAppCode) database.OAuth2ProviderAppCode { + code, err := db.InsertOAuth2ProviderAppCode(genCtx, database.InsertOAuth2ProviderAppCodeParams{ + ID: takeFirst(seed.ID, uuid.New()), + CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()), + ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()), + HashedSecret: takeFirstSlice(seed.HashedSecret, []byte("hashed-secret")), + AppID: takeFirst(seed.AppID, uuid.New()), + UserID: takeFirst(seed.UserID, uuid.New()), + }) + require.NoError(t, err, "insert oauth2 app code") + return code +} + +func OAuth2ProviderAppToken(t testing.TB, db database.Store, seed database.OAuth2ProviderAppToken) database.OAuth2ProviderAppToken { + token, err := db.InsertOAuth2ProviderAppToken(genCtx, database.InsertOAuth2ProviderAppTokenParams{ + ID: takeFirst(seed.ID, uuid.New()), + CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()), + ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()), + HashedSecret: takeFirstSlice(seed.HashedSecret, []byte("hashed-secret")), + AppSecretID: takeFirst(seed.AppSecretID, uuid.New()), + APIKeyID: takeFirst(seed.APIKeyID, uuid.New().String()), + }) + require.NoError(t, err, "insert oauth2 app token") + return token +} + func must[V any](v V, err error) V { if err != nil { panic(err) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index e3572a2e10ff1..95287b50bd48a 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -1,6 +1,7 @@ package dbmem import ( + "bytes" "context" "database/sql" "encoding/json" @@ -132,6 +133,8 @@ type data struct { licenses []database.License oauth2ProviderApps []database.OAuth2ProviderApp oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret + oauth2ProviderAppCodes []database.OAuth2ProviderAppCode + oauth2ProviderAppTokens []database.OAuth2ProviderAppToken parameterSchemas []database.ParameterSchema provisionerDaemons []database.ProvisionerDaemon provisionerJobLogs []database.ProvisionerJobLog @@ -1180,14 +1183,72 @@ func (q *FakeQuerier) DeleteOAuth2ProviderAppByID(_ context.Context, id uuid.UUI q.oauth2ProviderApps[index] = q.oauth2ProviderApps[len(q.oauth2ProviderApps)-1] q.oauth2ProviderApps = q.oauth2ProviderApps[:len(q.oauth2ProviderApps)-1] - secrets := []database.OAuth2ProviderAppSecret{} + // Cascade delete into secrets. + var deletedSecretIDs []uuid.UUID + var secrets []database.OAuth2ProviderAppSecret for _, secret := range q.oauth2ProviderAppSecrets { - if secret.AppID != id { + if secret.AppID == id { + deletedSecretIDs = append(deletedSecretIDs, secret.ID) + } else { secrets = append(secrets, secret) } } q.oauth2ProviderAppSecrets = secrets + // Cascade delete into tokens. + var keyIDsToDelete []string + var tokens []database.OAuth2ProviderAppToken + for _, token := range q.oauth2ProviderAppTokens { + if slice.Contains(deletedSecretIDs, token.AppSecretID) { + keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) + } else { + tokens = append(tokens, token) + } + } + q.oauth2ProviderAppTokens = tokens + + // Delete from API keys. + var keys []database.APIKey + for _, key := range q.apiKeys { + if !slices.Contains(keyIDsToDelete, key.ID) { + keys = append(keys, key) + } + } + q.apiKeys = keys + + return nil + } + } + return sql.ErrNoRows +} + +func (q *FakeQuerier) DeleteOAuth2ProviderAppCodeByID(_ context.Context, id uuid.UUID) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + for index, code := range q.oauth2ProviderAppCodes { + if code.ID == id { + q.oauth2ProviderAppCodes[index] = q.oauth2ProviderAppCodes[len(q.oauth2ProviderAppCodes)-1] + q.oauth2ProviderAppCodes = q.oauth2ProviderAppCodes[:len(q.oauth2ProviderAppCodes)-1] + return nil + } + } + return sql.ErrNoRows +} + +func (q *FakeQuerier) DeleteOAuth2ProviderAppCodesByAppAndUserID(_ context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for index, code := range q.oauth2ProviderAppCodes { + if code.AppID == arg.AppID && code.UserID == arg.UserID { + q.oauth2ProviderAppCodes[index] = q.oauth2ProviderAppCodes[len(q.oauth2ProviderAppCodes)-1] + q.oauth2ProviderAppCodes = q.oauth2ProviderAppCodes[:len(q.oauth2ProviderAppCodes)-1] return nil } } @@ -1202,12 +1263,73 @@ func (q *FakeQuerier) DeleteOAuth2ProviderAppSecretByID(_ context.Context, id uu if secret.ID == id { q.oauth2ProviderAppSecrets[index] = q.oauth2ProviderAppSecrets[len(q.oauth2ProviderAppSecrets)-1] q.oauth2ProviderAppSecrets = q.oauth2ProviderAppSecrets[:len(q.oauth2ProviderAppSecrets)-1] + + // Cascade delete into tokens. + var keyIDsToDelete []string + var tokens []database.OAuth2ProviderAppToken + for _, token := range q.oauth2ProviderAppTokens { + if token.AppSecretID == id { + keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) + } else { + tokens = append(tokens, token) + } + } + q.oauth2ProviderAppTokens = tokens + + // Delete from API keys. + var keys []database.APIKey + for _, key := range q.apiKeys { + if !slices.Contains(keyIDsToDelete, key.ID) { + keys = append(keys, key) + } + } + q.apiKeys = keys + return nil } } return sql.ErrNoRows } +func (q *FakeQuerier) DeleteOAuth2ProviderAppTokensByAppAndUserID(_ context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + var keyIDsToDelete []string + var tokens []database.OAuth2ProviderAppToken + for _, token := range q.oauth2ProviderAppTokens { + // Join secrets and keys to see if the token matches. + secretIdx := slices.IndexFunc(q.oauth2ProviderAppSecrets, func(secret database.OAuth2ProviderAppSecret) bool { + return secret.ID == token.AppSecretID + }) + keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool { + return key.ID == token.APIKeyID + }) + if q.oauth2ProviderAppSecrets[secretIdx].AppID == arg.AppID && q.apiKeys[keyIdx].UserID == arg.UserID { + keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) + } else { + tokens = append(tokens, token) + } + } + q.oauth2ProviderAppTokens = tokens + + // Cascade delete into API keys. + var keys []database.APIKey + for _, key := range q.apiKeys { + if !slices.Contains(keyIDsToDelete, key.ID) { + keys = append(keys, key) + } + } + q.apiKeys = keys + + return nil +} + func (q *FakeQuerier) DeleteOldProvisionerDaemons(_ context.Context) error { q.mutex.Lock() defer q.mutex.Unlock() @@ -2080,6 +2202,52 @@ func (q *FakeQuerier) GetOAuth2ProviderAppByID(_ context.Context, id uuid.UUID) return database.OAuth2ProviderApp{}, sql.ErrNoRows } +func (q *FakeQuerier) GetOAuth2ProviderAppCodeByAppIDAndSecret(_ context.Context, arg database.GetOAuth2ProviderAppCodeByAppIDAndSecretParams) (database.OAuth2ProviderAppCode, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.OAuth2ProviderAppCode{}, err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for _, code := range q.oauth2ProviderAppCodes { + if bytes.Equal(code.HashedSecret, arg.HashedSecret) && code.AppID == arg.AppID { + return code, nil + } + } + return database.OAuth2ProviderAppCode{}, sql.ErrNoRows +} + +func (q *FakeQuerier) GetOAuth2ProviderAppCodeByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + + for _, code := range q.oauth2ProviderAppCodes { + if code.ID == id { + return code, nil + } + } + return database.OAuth2ProviderAppCode{}, sql.ErrNoRows +} + +func (q *FakeQuerier) GetOAuth2ProviderAppSecretByAppIDAndSecret(_ context.Context, arg database.GetOAuth2ProviderAppSecretByAppIDAndSecretParams) (database.OAuth2ProviderAppSecret, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.OAuth2ProviderAppSecret{}, err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for _, secret := range q.oauth2ProviderAppSecrets { + if secret.AppID == arg.AppID && bytes.Equal(secret.HashedSecret, arg.HashedSecret) { + return secret, nil + } + } + return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows +} + func (q *FakeQuerier) GetOAuth2ProviderAppSecretByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) { q.mutex.Lock() defer q.mutex.Unlock() @@ -2130,6 +2298,42 @@ func (q *FakeQuerier) GetOAuth2ProviderApps(_ context.Context) ([]database.OAuth return q.oauth2ProviderApps, nil } +func (q *FakeQuerier) GetOAuth2ProviderAppsByUserID(_ context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + + rows := []database.GetOAuth2ProviderAppsByUserIDRow{} + for _, app := range q.oauth2ProviderApps { + tokens := []database.OAuth2ProviderAppToken{} + for _, secret := range q.oauth2ProviderAppSecrets { + if secret.AppID == app.ID { + for _, token := range q.oauth2ProviderAppTokens { + if token.AppSecretID == secret.ID { + keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool { + return key.ID == token.APIKeyID + }) + if q.apiKeys[keyIdx].UserID == userID { + tokens = append(tokens, token) + } + } + } + } + } + if len(tokens) > 0 { + rows = append(rows, database.GetOAuth2ProviderAppsByUserIDRow{ + OAuth2ProviderApp: database.OAuth2ProviderApp{ + CallbackURL: app.CallbackURL, + ID: app.ID, + Icon: app.Icon, + Name: app.Name, + }, + TokenCount: int64(len(tokens)), + }) + } + } + return rows, nil +} + func (q *FakeQuerier) GetOAuthSigningKey(_ context.Context) (string, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -5101,6 +5305,33 @@ func (q *FakeQuerier) InsertOAuth2ProviderApp(_ context.Context, arg database.In return app, nil } +func (q *FakeQuerier) InsertOAuth2ProviderAppCode(_ context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.OAuth2ProviderAppCode{}, err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for _, app := range q.oauth2ProviderApps { + if app.ID == arg.AppID { + code := database.OAuth2ProviderAppCode{ + ID: arg.ID, + CreatedAt: arg.CreatedAt, + ExpiresAt: arg.ExpiresAt, + HashedSecret: arg.HashedSecret, + UserID: arg.UserID, + AppID: arg.AppID, + } + q.oauth2ProviderAppCodes = append(q.oauth2ProviderAppCodes, code) + return code, nil + } + } + + return database.OAuth2ProviderAppCode{}, sql.ErrNoRows +} + func (q *FakeQuerier) InsertOAuth2ProviderAppSecret(_ context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) { err := validateDatabaseType(arg) if err != nil { @@ -5127,6 +5358,34 @@ func (q *FakeQuerier) InsertOAuth2ProviderAppSecret(_ context.Context, arg datab return database.OAuth2ProviderAppSecret{}, sql.ErrNoRows } +func (q *FakeQuerier) InsertOAuth2ProviderAppToken(_ context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.OAuth2ProviderAppToken{}, err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for _, secret := range q.oauth2ProviderAppSecrets { + if secret.ID == arg.AppSecretID { + //nolint:gosimple // Go wants database.OAuth2ProviderAppToken(arg), but we cannot be sure the structs will remain identical. + token := database.OAuth2ProviderAppToken{ + ID: arg.ID, + CreatedAt: arg.CreatedAt, + ExpiresAt: arg.ExpiresAt, + HashedSecret: arg.HashedSecret, + APIKeyID: arg.APIKeyID, + AppSecretID: arg.AppSecretID, + } + q.oauth2ProviderAppTokens = append(q.oauth2ProviderAppTokens, token) + return token, nil + } + } + + return database.OAuth2ProviderAppToken{}, sql.ErrNoRows +} + func (q *FakeQuerier) InsertOrganization(_ context.Context, arg database.InsertOrganizationParams) (database.Organization, error) { if err := validateDatabaseType(arg); err != nil { return database.Organization{}, err diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 625871500dbeb..84a45f16630e3 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -232,6 +232,20 @@ func (m metricsStore) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.U return r0 } +func (m metricsStore) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error { + start := time.Now() + r0 := m.s.DeleteOAuth2ProviderAppCodeByID(ctx, id) + m.queryLatencies.WithLabelValues("DeleteOAuth2ProviderAppCodeByID").Observe(time.Since(start).Seconds()) + return r0 +} + +func (m metricsStore) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error { + start := time.Now() + r0 := m.s.DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx, arg) + m.queryLatencies.WithLabelValues("DeleteOAuth2ProviderAppCodesByAppAndUserID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error { start := time.Now() r0 := m.s.DeleteOAuth2ProviderAppSecretByID(ctx, id) @@ -239,6 +253,13 @@ func (m metricsStore) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id return r0 } +func (m metricsStore) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error { + start := time.Now() + r0 := m.s.DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx, arg) + m.queryLatencies.WithLabelValues("DeleteOAuth2ProviderAppTokensByAppAndUserID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) DeleteOldProvisionerDaemons(ctx context.Context) error { start := time.Now() r0 := m.s.DeleteOldProvisionerDaemons(ctx) @@ -594,6 +615,27 @@ func (m metricsStore) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID return r0, r1 } +func (m metricsStore) GetOAuth2ProviderAppCodeByAppIDAndSecret(ctx context.Context, arg database.GetOAuth2ProviderAppCodeByAppIDAndSecretParams) (database.OAuth2ProviderAppCode, error) { + start := time.Now() + r0, r1 := m.s.GetOAuth2ProviderAppCodeByAppIDAndSecret(ctx, arg) + m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppCodeByAppIDAndSecret").Observe(time.Since(start).Seconds()) + return r0, r1 +} + +func (m metricsStore) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppCode, error) { + start := time.Now() + r0, r1 := m.s.GetOAuth2ProviderAppCodeByID(ctx, id) + m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppCodeByID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + +func (m metricsStore) GetOAuth2ProviderAppSecretByAppIDAndSecret(ctx context.Context, arg database.GetOAuth2ProviderAppSecretByAppIDAndSecretParams) (database.OAuth2ProviderAppSecret, error) { + start := time.Now() + r0, r1 := m.s.GetOAuth2ProviderAppSecretByAppIDAndSecret(ctx, arg) + m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppSecretByAppIDAndSecret").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderAppSecret, error) { start := time.Now() r0, r1 := m.s.GetOAuth2ProviderAppSecretByID(ctx, id) @@ -615,6 +657,13 @@ func (m metricsStore) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAu return r0, r1 } +func (m metricsStore) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) { + start := time.Now() + r0, r1 := m.s.GetOAuth2ProviderAppsByUserID(ctx, userID) + m.queryLatencies.WithLabelValues("GetOAuth2ProviderAppsByUserID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetOAuthSigningKey(ctx context.Context) (string, error) { start := time.Now() r0, r1 := m.s.GetOAuthSigningKey(ctx) @@ -1390,6 +1439,13 @@ func (m metricsStore) InsertOAuth2ProviderApp(ctx context.Context, arg database. return r0, r1 } +func (m metricsStore) InsertOAuth2ProviderAppCode(ctx context.Context, arg database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) { + start := time.Now() + r0, r1 := m.s.InsertOAuth2ProviderAppCode(ctx, arg) + m.queryLatencies.WithLabelValues("InsertOAuth2ProviderAppCode").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) InsertOAuth2ProviderAppSecret(ctx context.Context, arg database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) { start := time.Now() r0, r1 := m.s.InsertOAuth2ProviderAppSecret(ctx, arg) @@ -1397,6 +1453,13 @@ func (m metricsStore) InsertOAuth2ProviderAppSecret(ctx context.Context, arg dat return r0, r1 } +func (m metricsStore) InsertOAuth2ProviderAppToken(ctx context.Context, arg database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) { + start := time.Now() + r0, r1 := m.s.InsertOAuth2ProviderAppToken(ctx, arg) + m.queryLatencies.WithLabelValues("InsertOAuth2ProviderAppToken").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) InsertOrganization(ctx context.Context, arg database.InsertOrganizationParams) (database.Organization, error) { start := time.Now() organization, err := m.s.InsertOrganization(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index bfb93405f5524..7877c9c93faaf 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -356,6 +356,34 @@ func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppByID(arg0, arg1 any) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppByID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppByID), arg0, arg1) } +// DeleteOAuth2ProviderAppCodeByID mocks base method. +func (m *MockStore) DeleteOAuth2ProviderAppCodeByID(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteOAuth2ProviderAppCodeByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteOAuth2ProviderAppCodeByID indicates an expected call of DeleteOAuth2ProviderAppCodeByID. +func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppCodeByID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppCodeByID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppCodeByID), arg0, arg1) +} + +// DeleteOAuth2ProviderAppCodesByAppAndUserID mocks base method. +func (m *MockStore) DeleteOAuth2ProviderAppCodesByAppAndUserID(arg0 context.Context, arg1 database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteOAuth2ProviderAppCodesByAppAndUserID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteOAuth2ProviderAppCodesByAppAndUserID indicates an expected call of DeleteOAuth2ProviderAppCodesByAppAndUserID. +func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppCodesByAppAndUserID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppCodesByAppAndUserID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppCodesByAppAndUserID), arg0, arg1) +} + // DeleteOAuth2ProviderAppSecretByID mocks base method. func (m *MockStore) DeleteOAuth2ProviderAppSecretByID(arg0 context.Context, arg1 uuid.UUID) error { m.ctrl.T.Helper() @@ -370,6 +398,20 @@ func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppSecretByID(arg0, arg1 an return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppSecretByID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppSecretByID), arg0, arg1) } +// DeleteOAuth2ProviderAppTokensByAppAndUserID mocks base method. +func (m *MockStore) DeleteOAuth2ProviderAppTokensByAppAndUserID(arg0 context.Context, arg1 database.DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteOAuth2ProviderAppTokensByAppAndUserID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteOAuth2ProviderAppTokensByAppAndUserID indicates an expected call of DeleteOAuth2ProviderAppTokensByAppAndUserID. +func (mr *MockStoreMockRecorder) DeleteOAuth2ProviderAppTokensByAppAndUserID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOAuth2ProviderAppTokensByAppAndUserID", reflect.TypeOf((*MockStore)(nil).DeleteOAuth2ProviderAppTokensByAppAndUserID), arg0, arg1) +} + // DeleteOldProvisionerDaemons mocks base method. func (m *MockStore) DeleteOldProvisionerDaemons(arg0 context.Context) error { m.ctrl.T.Helper() @@ -1175,6 +1217,51 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppByID(arg0, arg1 any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppByID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppByID), arg0, arg1) } +// GetOAuth2ProviderAppCodeByAppIDAndSecret mocks base method. +func (m *MockStore) GetOAuth2ProviderAppCodeByAppIDAndSecret(arg0 context.Context, arg1 database.GetOAuth2ProviderAppCodeByAppIDAndSecretParams) (database.OAuth2ProviderAppCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOAuth2ProviderAppCodeByAppIDAndSecret", arg0, arg1) + ret0, _ := ret[0].(database.OAuth2ProviderAppCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOAuth2ProviderAppCodeByAppIDAndSecret indicates an expected call of GetOAuth2ProviderAppCodeByAppIDAndSecret. +func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppCodeByAppIDAndSecret(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppCodeByAppIDAndSecret", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppCodeByAppIDAndSecret), arg0, arg1) +} + +// GetOAuth2ProviderAppCodeByID mocks base method. +func (m *MockStore) GetOAuth2ProviderAppCodeByID(arg0 context.Context, arg1 uuid.UUID) (database.OAuth2ProviderAppCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOAuth2ProviderAppCodeByID", arg0, arg1) + ret0, _ := ret[0].(database.OAuth2ProviderAppCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOAuth2ProviderAppCodeByID indicates an expected call of GetOAuth2ProviderAppCodeByID. +func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppCodeByID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppCodeByID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppCodeByID), arg0, arg1) +} + +// GetOAuth2ProviderAppSecretByAppIDAndSecret mocks base method. +func (m *MockStore) GetOAuth2ProviderAppSecretByAppIDAndSecret(arg0 context.Context, arg1 database.GetOAuth2ProviderAppSecretByAppIDAndSecretParams) (database.OAuth2ProviderAppSecret, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOAuth2ProviderAppSecretByAppIDAndSecret", arg0, arg1) + ret0, _ := ret[0].(database.OAuth2ProviderAppSecret) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOAuth2ProviderAppSecretByAppIDAndSecret indicates an expected call of GetOAuth2ProviderAppSecretByAppIDAndSecret. +func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppSecretByAppIDAndSecret(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppSecretByAppIDAndSecret", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppSecretByAppIDAndSecret), arg0, arg1) +} + // GetOAuth2ProviderAppSecretByID mocks base method. func (m *MockStore) GetOAuth2ProviderAppSecretByID(arg0 context.Context, arg1 uuid.UUID) (database.OAuth2ProviderAppSecret, error) { m.ctrl.T.Helper() @@ -1220,6 +1307,21 @@ func (mr *MockStoreMockRecorder) GetOAuth2ProviderApps(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderApps", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderApps), arg0) } +// GetOAuth2ProviderAppsByUserID mocks base method. +func (m *MockStore) GetOAuth2ProviderAppsByUserID(arg0 context.Context, arg1 uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOAuth2ProviderAppsByUserID", arg0, arg1) + ret0, _ := ret[0].([]database.GetOAuth2ProviderAppsByUserIDRow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOAuth2ProviderAppsByUserID indicates an expected call of GetOAuth2ProviderAppsByUserID. +func (mr *MockStoreMockRecorder) GetOAuth2ProviderAppsByUserID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOAuth2ProviderAppsByUserID", reflect.TypeOf((*MockStore)(nil).GetOAuth2ProviderAppsByUserID), arg0, arg1) +} + // GetOAuthSigningKey mocks base method. func (m *MockStore) GetOAuthSigningKey(arg0 context.Context) (string, error) { m.ctrl.T.Helper() @@ -2925,6 +3027,21 @@ func (mr *MockStoreMockRecorder) InsertOAuth2ProviderApp(arg0, arg1 any) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderApp", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderApp), arg0, arg1) } +// InsertOAuth2ProviderAppCode mocks base method. +func (m *MockStore) InsertOAuth2ProviderAppCode(arg0 context.Context, arg1 database.InsertOAuth2ProviderAppCodeParams) (database.OAuth2ProviderAppCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertOAuth2ProviderAppCode", arg0, arg1) + ret0, _ := ret[0].(database.OAuth2ProviderAppCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertOAuth2ProviderAppCode indicates an expected call of InsertOAuth2ProviderAppCode. +func (mr *MockStoreMockRecorder) InsertOAuth2ProviderAppCode(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderAppCode", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderAppCode), arg0, arg1) +} + // InsertOAuth2ProviderAppSecret mocks base method. func (m *MockStore) InsertOAuth2ProviderAppSecret(arg0 context.Context, arg1 database.InsertOAuth2ProviderAppSecretParams) (database.OAuth2ProviderAppSecret, error) { m.ctrl.T.Helper() @@ -2940,6 +3057,21 @@ func (mr *MockStoreMockRecorder) InsertOAuth2ProviderAppSecret(arg0, arg1 any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderAppSecret", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderAppSecret), arg0, arg1) } +// InsertOAuth2ProviderAppToken mocks base method. +func (m *MockStore) InsertOAuth2ProviderAppToken(arg0 context.Context, arg1 database.InsertOAuth2ProviderAppTokenParams) (database.OAuth2ProviderAppToken, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertOAuth2ProviderAppToken", arg0, arg1) + ret0, _ := ret[0].(database.OAuth2ProviderAppToken) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertOAuth2ProviderAppToken indicates an expected call of InsertOAuth2ProviderAppToken. +func (mr *MockStoreMockRecorder) InsertOAuth2ProviderAppToken(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOAuth2ProviderAppToken", reflect.TypeOf((*MockStore)(nil).InsertOAuth2ProviderAppToken), arg0, arg1) +} + // InsertOrganization mocks base method. func (m *MockStore) InsertOrganization(arg0 context.Context, arg1 database.InsertOrganizationParams) (database.Organization, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f9d1e4311b2b2..f29ae4b47a83f 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -67,7 +67,8 @@ CREATE TYPE login_type AS ENUM ( 'github', 'oidc', 'token', - 'none' + 'none', + 'oauth2_provider_app' ); COMMENT ON TYPE login_type IS 'Specifies the method of authentication. "none" is a special case in which no authentication method is allowed.'; @@ -187,6 +188,17 @@ CREATE TYPE workspace_transition AS ENUM ( 'delete' ); +CREATE FUNCTION delete_deleted_oauth2_provider_app_token_api_key() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE +BEGIN + DELETE FROM api_keys + WHERE id = OLD.api_key_id; + RETURN OLD; +END; +$$; + CREATE FUNCTION delete_deleted_user_api_keys() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -458,6 +470,17 @@ CREATE SEQUENCE licenses_id_seq ALTER SEQUENCE licenses_id_seq OWNED BY licenses.id; +CREATE TABLE oauth2_provider_app_codes ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + expires_at timestamp with time zone NOT NULL, + hashed_secret bytea NOT NULL, + user_id uuid NOT NULL, + app_id uuid NOT NULL +); + +COMMENT ON TABLE oauth2_provider_app_codes IS 'Codes are meant to be exchanged for access tokens.'; + CREATE TABLE oauth2_provider_app_secrets ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -469,6 +492,17 @@ CREATE TABLE oauth2_provider_app_secrets ( COMMENT ON COLUMN oauth2_provider_app_secrets.display_secret IS 'The tail end of the original secret so secrets can be differentiated.'; +CREATE TABLE oauth2_provider_app_tokens ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + expires_at timestamp with time zone NOT NULL, + hashed_secret bytea NOT NULL, + app_secret_id uuid NOT NULL, + api_key_id text NOT NULL +); + +COMMENT ON TABLE oauth2_provider_app_tokens IS 'Refresh tokens both provide a way to refresh an access tokens (API keys) and a way to link API keys with the OAuth2 app and secret that generated them.'; + CREATE TABLE oauth2_provider_apps ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -1295,12 +1329,24 @@ ALTER TABLE ONLY licenses ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); +ALTER TABLE ONLY oauth2_provider_app_codes + ADD CONSTRAINT oauth2_provider_app_codes_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); + +ALTER TABLE ONLY oauth2_provider_app_codes + ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); + ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); +ALTER TABLE ONLY oauth2_provider_app_tokens + ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_hashed_secret_key UNIQUE (app_secret_id, hashed_secret); + +ALTER TABLE ONLY oauth2_provider_app_tokens + ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id); + ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name); @@ -1508,6 +1554,8 @@ CREATE TRIGGER tailnet_notify_peer_change AFTER INSERT OR DELETE OR UPDATE ON ta CREATE TRIGGER tailnet_notify_tunnel_change AFTER INSERT OR DELETE OR UPDATE ON tailnet_tunnels FOR EACH ROW EXECUTE FUNCTION tailnet_notify_tunnel_change(); +CREATE TRIGGER trigger_delete_oauth2_provider_app_token AFTER DELETE ON oauth2_provider_app_tokens FOR EACH ROW EXECUTE FUNCTION delete_deleted_oauth2_provider_app_token_api_key(); + CREATE TRIGGER trigger_insert_apikeys BEFORE INSERT ON api_keys FOR EACH ROW EXECUTE FUNCTION insert_apikey_fail_if_user_deleted(); CREATE TRIGGER trigger_update_users AFTER INSERT OR UPDATE ON users FOR EACH ROW WHEN ((new.deleted = true)) EXECUTE FUNCTION delete_deleted_user_api_keys(); @@ -1533,9 +1581,21 @@ ALTER TABLE ONLY group_members ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; +ALTER TABLE ONLY oauth2_provider_app_codes + ADD CONSTRAINT oauth2_provider_app_codes_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE; + +ALTER TABLE ONLY oauth2_provider_app_codes + ADD CONSTRAINT oauth2_provider_app_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE; +ALTER TABLE ONLY oauth2_provider_app_tokens + ADD CONSTRAINT oauth2_provider_app_tokens_api_key_id_fkey FOREIGN KEY (api_key_id) REFERENCES api_keys(id) ON DELETE CASCADE; + +ALTER TABLE ONLY oauth2_provider_app_tokens + ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_fkey FOREIGN KEY (app_secret_id) REFERENCES oauth2_provider_app_secrets(id) ON DELETE CASCADE; + ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index 5dc75af93f1be..1fecc5b82c491 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -13,7 +13,11 @@ const ( ForeignKeyGroupMembersGroupID ForeignKeyConstraint = "group_members_group_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE; ForeignKeyGroupMembersUserID ForeignKeyConstraint = "group_members_user_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyGroupsOrganizationID ForeignKeyConstraint = "groups_organization_id_fkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ForeignKeyOauth2ProviderAppCodesAppID ForeignKeyConstraint = "oauth2_provider_app_codes_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE; + ForeignKeyOauth2ProviderAppCodesUserID ForeignKeyConstraint = "oauth2_provider_app_codes_user_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyOauth2ProviderAppSecretsAppID ForeignKeyConstraint = "oauth2_provider_app_secrets_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE; + ForeignKeyOauth2ProviderAppTokensAPIKeyID ForeignKeyConstraint = "oauth2_provider_app_tokens_api_key_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_api_key_id_fkey FOREIGN KEY (api_key_id) REFERENCES api_keys(id) ON DELETE CASCADE; + ForeignKeyOauth2ProviderAppTokensAppSecretID ForeignKeyConstraint = "oauth2_provider_app_tokens_app_secret_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_fkey FOREIGN KEY (app_secret_id) REFERENCES oauth2_provider_app_secrets(id) ON DELETE CASCADE; ForeignKeyOrganizationMembersOrganizationIDUUID ForeignKeyConstraint = "organization_members_organization_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyOrganizationMembersUserIDUUID ForeignKeyConstraint = "organization_members_user_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyParameterSchemasJobID ForeignKeyConstraint = "parameter_schemas_job_id_fkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000186_oauth2_provider_codes.down.sql b/coderd/database/migrations/000186_oauth2_provider_codes.down.sql new file mode 100644 index 0000000000000..09b1ee274a341 --- /dev/null +++ b/coderd/database/migrations/000186_oauth2_provider_codes.down.sql @@ -0,0 +1,8 @@ +DROP TRIGGER IF EXISTS trigger_delete_oauth2_provider_app_token ON oauth2_provider_app_tokens; +DROP FUNCTION IF EXISTS delete_deleted_oauth2_provider_app_token_api_key; + +DROP TABLE oauth2_provider_app_tokens; +DROP TABLE oauth2_provider_app_codes; + +-- It is not possible to drop enum values from enum types, so the UP on +-- login_type has "IF NOT EXISTS". diff --git a/coderd/database/migrations/000186_oauth2_provider_codes.up.sql b/coderd/database/migrations/000186_oauth2_provider_codes.up.sql new file mode 100644 index 0000000000000..48295361a0238 --- /dev/null +++ b/coderd/database/migrations/000186_oauth2_provider_codes.up.sql @@ -0,0 +1,44 @@ +CREATE TABLE oauth2_provider_app_codes ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + expires_at timestamp with time zone NOT NULL, + hashed_secret bytea NOT NULL, + user_id uuid NOT NULL REFERENCES users (id) ON DELETE CASCADE, + app_id uuid NOT NULL REFERENCES oauth2_provider_apps (id) ON DELETE CASCADE, + PRIMARY KEY (id), + UNIQUE(app_id, hashed_secret) +); + +COMMENT ON TABLE oauth2_provider_app_codes IS 'Codes are meant to be exchanged for access tokens.'; + +CREATE TABLE oauth2_provider_app_tokens ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + expires_at timestamp with time zone NOT NULL, + hashed_secret bytea NOT NULL, + app_secret_id uuid NOT NULL REFERENCES oauth2_provider_app_secrets (id) ON DELETE CASCADE, + api_key_id text NOT NULL REFERENCES api_keys (id) ON DELETE CASCADE, + PRIMARY KEY (id), + UNIQUE(app_secret_id, hashed_secret) +); + +COMMENT ON TABLE oauth2_provider_app_tokens IS 'Refresh tokens both provide a way to refresh an access tokens (API keys) and a way to link API keys with the OAuth2 app and secret that generated them.'; + +-- When we delete a token, delete the API key associated with it. +CREATE FUNCTION delete_deleted_oauth2_provider_app_token_api_key() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE +BEGIN + DELETE FROM api_keys + WHERE id = OLD.api_key_id; + RETURN OLD; +END; +$$; + +CREATE TRIGGER trigger_delete_oauth2_provider_app_token +AFTER DELETE ON oauth2_provider_app_tokens +FOR EACH ROW +EXECUTE PROCEDURE delete_deleted_oauth2_provider_app_token_api_key(); + +ALTER TYPE login_type ADD VALUE IF NOT EXISTS 'oauth2_provider_app'; diff --git a/coderd/database/migrations/testdata/fixtures/000186_oauth2_provider_codes.up.sql b/coderd/database/migrations/testdata/fixtures/000186_oauth2_provider_codes.up.sql new file mode 100644 index 0000000000000..f4532a4523c6d --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000186_oauth2_provider_codes.up.sql @@ -0,0 +1,21 @@ +INSERT INTO oauth2_provider_app_codes + (id, created_at, expires_at, hashed_secret, user_id, app_id) +VALUES ( + 'c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', + '2023-06-15 10:23:54+00', + '2023-06-15 10:23:54+00', + CAST('abcdefg' AS bytea), + '0ed9befc-4911-4ccf-a8e2-559bf72daa94', + 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' +); + +INSERT INTO oauth2_provider_app_tokens + (id, created_at, expires_at, hashed_secret, app_secret_id, api_key_id) +VALUES ( + 'd0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', + '2023-06-15 10:25:33+00', + '2023-12-15 11:40:20+00', + CAST('abcdefg' AS bytea), + 'b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', + 'peuLZhMXt4' +); diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 685c138c95288..3721eac83dd75 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -277,6 +277,22 @@ func (l License) RBACObject() rbac.Object { return rbac.ResourceLicense.WithIDString(strconv.FormatInt(int64(l.ID), 10)) } +func (c OAuth2ProviderAppCode) RBACObject() rbac.Object { + return rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(c.UserID.String()) +} + +func (OAuth2ProviderAppSecret) RBACObject() rbac.Object { + return rbac.ResourceOAuth2ProviderAppSecret +} + +func (OAuth2ProviderApp) RBACObject() rbac.Object { + return rbac.ResourceOAuth2ProviderApp +} + +func (a GetOAuth2ProviderAppsByUserIDRow) RBACObject() rbac.Object { + return a.OAuth2ProviderApp.RBACObject() +} + type WorkspaceAgentConnectionStatus struct { Status WorkspaceAgentStatus `json:"status"` FirstConnectedAt *time.Time `json:"first_connected_at"` diff --git a/coderd/database/models.go b/coderd/database/models.go index 5308f88b35a79..29ec66275e875 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -593,11 +593,12 @@ func AllLogSourceValues() []LogSource { type LoginType string const ( - LoginTypePassword LoginType = "password" - LoginTypeGithub LoginType = "github" - LoginTypeOIDC LoginType = "oidc" - LoginTypeToken LoginType = "token" - LoginTypeNone LoginType = "none" + LoginTypePassword LoginType = "password" + LoginTypeGithub LoginType = "github" + LoginTypeOIDC LoginType = "oidc" + LoginTypeToken LoginType = "token" + LoginTypeNone LoginType = "none" + LoginTypeOAuth2ProviderApp LoginType = "oauth2_provider_app" ) func (e *LoginType) Scan(src interface{}) error { @@ -641,7 +642,8 @@ func (e LoginType) Valid() bool { LoginTypeGithub, LoginTypeOIDC, LoginTypeToken, - LoginTypeNone: + LoginTypeNone, + LoginTypeOAuth2ProviderApp: return true } return false @@ -654,6 +656,7 @@ func AllLoginTypeValues() []LoginType { LoginTypeOIDC, LoginTypeToken, LoginTypeNone, + LoginTypeOAuth2ProviderApp, } } @@ -1798,6 +1801,16 @@ type OAuth2ProviderApp struct { CallbackURL string `db:"callback_url" json:"callback_url"` } +// Codes are meant to be exchanged for access tokens. +type OAuth2ProviderAppCode struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ExpiresAt time.Time `db:"expires_at" json:"expires_at"` + HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` + UserID uuid.UUID `db:"user_id" json:"user_id"` + AppID uuid.UUID `db:"app_id" json:"app_id"` +} + type OAuth2ProviderAppSecret struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` @@ -1808,6 +1821,16 @@ type OAuth2ProviderAppSecret struct { AppID uuid.UUID `db:"app_id" json:"app_id"` } +// Refresh tokens both provide a way to refresh an access tokens (API keys) and a way to link API keys with the OAuth2 app and secret that generated them. +type OAuth2ProviderAppToken struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ExpiresAt time.Time `db:"expires_at" json:"expires_at"` + HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` + AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"` + APIKeyID string `db:"api_key_id" json:"api_key_id"` +} + type Organization struct { ID uuid.UUID `db:"id" json:"id"` Name string `db:"name" json:"name"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 8947ba185d14d..7f839a1e61074 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -59,7 +59,10 @@ type sqlcQuerier interface { DeleteGroupMembersByOrgAndUser(ctx context.Context, arg DeleteGroupMembersByOrgAndUserParams) error DeleteLicense(ctx context.Context, id int32) (int32, error) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) error + DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error + DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) error + DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error // Delete provisioner daemons that have been created at least a week ago // and have not connected to coderd since a week. // A provisioner daemon with "zeroed" last_seen_at column indicates possible @@ -126,9 +129,13 @@ type sqlcQuerier interface { GetLicenses(ctx context.Context) ([]License, error) GetLogoURL(ctx context.Context) (string, error) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error) + GetOAuth2ProviderAppCodeByAppIDAndSecret(ctx context.Context, arg GetOAuth2ProviderAppCodeByAppIDAndSecretParams) (OAuth2ProviderAppCode, error) + GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) + GetOAuth2ProviderAppSecretByAppIDAndSecret(ctx context.Context, arg GetOAuth2ProviderAppSecretByAppIDAndSecretParams) (OAuth2ProviderAppSecret, error) GetOAuth2ProviderAppSecretByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppSecret, error) GetOAuth2ProviderAppSecretsByAppID(ctx context.Context, appID uuid.UUID) ([]OAuth2ProviderAppSecret, error) GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2ProviderApp, error) + GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]GetOAuth2ProviderAppsByUserIDRow, error) GetOAuthSigningKey(ctx context.Context) (string, error) GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error) GetOrganizationByName(ctx context.Context, name string) (Organization, error) @@ -283,7 +290,9 @@ type sqlcQuerier interface { // If the name conflicts, do nothing. InsertMissingGroups(ctx context.Context, arg InsertMissingGroupsParams) ([]Group, error) InsertOAuth2ProviderApp(ctx context.Context, arg InsertOAuth2ProviderAppParams) (OAuth2ProviderApp, error) + InsertOAuth2ProviderAppCode(ctx context.Context, arg InsertOAuth2ProviderAppCodeParams) (OAuth2ProviderAppCode, error) InsertOAuth2ProviderAppSecret(ctx context.Context, arg InsertOAuth2ProviderAppSecretParams) (OAuth2ProviderAppSecret, error) + InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error) InsertOrganization(ctx context.Context, arg InsertOrganizationParams) (Organization, error) InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b18662df3537e..4741c3a1c7803 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2620,6 +2620,29 @@ func (q *sqlQuerier) DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UU return err } +const deleteOAuth2ProviderAppCodeByID = `-- name: DeleteOAuth2ProviderAppCodeByID :exec +DELETE FROM oauth2_provider_app_codes WHERE id = $1 +` + +func (q *sqlQuerier) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) error { + _, err := q.db.ExecContext(ctx, deleteOAuth2ProviderAppCodeByID, id) + return err +} + +const deleteOAuth2ProviderAppCodesByAppAndUserID = `-- name: DeleteOAuth2ProviderAppCodesByAppAndUserID :exec +DELETE FROM oauth2_provider_app_codes WHERE app_id = $1 AND user_id = $2 +` + +type DeleteOAuth2ProviderAppCodesByAppAndUserIDParams struct { + AppID uuid.UUID `db:"app_id" json:"app_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` +} + +func (q *sqlQuerier) DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppCodesByAppAndUserIDParams) error { + _, err := q.db.ExecContext(ctx, deleteOAuth2ProviderAppCodesByAppAndUserID, arg.AppID, arg.UserID) + return err +} + const deleteOAuth2ProviderAppSecretByID = `-- name: DeleteOAuth2ProviderAppSecretByID :exec DELETE FROM oauth2_provider_app_secrets WHERE id = $1 ` @@ -2629,6 +2652,26 @@ func (q *sqlQuerier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id u return err } +const deleteOAuth2ProviderAppTokensByAppAndUserID = `-- name: DeleteOAuth2ProviderAppTokensByAppAndUserID :exec +DELETE FROM oauth2_provider_app_tokens +USING oauth2_provider_app_secrets, api_keys +WHERE + oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id + AND api_keys.id = oauth2_provider_app_tokens.api_key_id + AND oauth2_provider_app_secrets.app_id = $1 + AND api_keys.user_id = $2 +` + +type DeleteOAuth2ProviderAppTokensByAppAndUserIDParams struct { + AppID uuid.UUID `db:"app_id" json:"app_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` +} + +func (q *sqlQuerier) DeleteOAuth2ProviderAppTokensByAppAndUserID(ctx context.Context, arg DeleteOAuth2ProviderAppTokensByAppAndUserIDParams) error { + _, err := q.db.ExecContext(ctx, deleteOAuth2ProviderAppTokensByAppAndUserID, arg.AppID, arg.UserID) + return err +} + const getOAuth2ProviderAppByID = `-- name: GetOAuth2ProviderAppByID :one SELECT id, created_at, updated_at, name, icon, callback_url FROM oauth2_provider_apps WHERE id = $1 ` @@ -2647,6 +2690,70 @@ func (q *sqlQuerier) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) return i, err } +const getOAuth2ProviderAppCodeByAppIDAndSecret = `-- name: GetOAuth2ProviderAppCodeByAppIDAndSecret :one +SELECT id, created_at, expires_at, hashed_secret, user_id, app_id FROM oauth2_provider_app_codes WHERE app_id = $1 AND hashed_secret = $2 +` + +type GetOAuth2ProviderAppCodeByAppIDAndSecretParams struct { + AppID uuid.UUID `db:"app_id" json:"app_id"` + HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` +} + +func (q *sqlQuerier) GetOAuth2ProviderAppCodeByAppIDAndSecret(ctx context.Context, arg GetOAuth2ProviderAppCodeByAppIDAndSecretParams) (OAuth2ProviderAppCode, error) { + row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppCodeByAppIDAndSecret, arg.AppID, arg.HashedSecret) + var i OAuth2ProviderAppCode + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.ExpiresAt, + &i.HashedSecret, + &i.UserID, + &i.AppID, + ) + return i, err +} + +const getOAuth2ProviderAppCodeByID = `-- name: GetOAuth2ProviderAppCodeByID :one +SELECT id, created_at, expires_at, hashed_secret, user_id, app_id FROM oauth2_provider_app_codes WHERE id = $1 +` + +func (q *sqlQuerier) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) { + row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppCodeByID, id) + var i OAuth2ProviderAppCode + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.ExpiresAt, + &i.HashedSecret, + &i.UserID, + &i.AppID, + ) + return i, err +} + +const getOAuth2ProviderAppSecretByAppIDAndSecret = `-- name: GetOAuth2ProviderAppSecretByAppIDAndSecret :one +SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id FROM oauth2_provider_app_secrets WHERE app_id = $1 AND hashed_secret = $2 +` + +type GetOAuth2ProviderAppSecretByAppIDAndSecretParams struct { + AppID uuid.UUID `db:"app_id" json:"app_id"` + HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` +} + +func (q *sqlQuerier) GetOAuth2ProviderAppSecretByAppIDAndSecret(ctx context.Context, arg GetOAuth2ProviderAppSecretByAppIDAndSecretParams) (OAuth2ProviderAppSecret, error) { + row := q.db.QueryRowContext(ctx, getOAuth2ProviderAppSecretByAppIDAndSecret, arg.AppID, arg.HashedSecret) + var i OAuth2ProviderAppSecret + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.LastUsedAt, + &i.HashedSecret, + &i.DisplaySecret, + &i.AppID, + ) + return i, err +} + const getOAuth2ProviderAppSecretByID = `-- name: GetOAuth2ProviderAppSecretByID :one SELECT id, created_at, last_used_at, hashed_secret, display_secret, app_id FROM oauth2_provider_app_secrets WHERE id = $1 ` @@ -2733,6 +2840,56 @@ func (q *sqlQuerier) GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2Provide return items, nil } +const getOAuth2ProviderAppsByUserID = `-- name: GetOAuth2ProviderAppsByUserID :many +SELECT COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count, + oauth2_provider_apps.id, oauth2_provider_apps.created_at, oauth2_provider_apps.updated_at, oauth2_provider_apps.name, oauth2_provider_apps.icon, oauth2_provider_apps.callback_url +FROM oauth2_provider_app_tokens + INNER JOIN oauth2_provider_app_secrets + ON oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id + INNER JOIN oauth2_provider_apps + ON oauth2_provider_apps.id = oauth2_provider_app_secrets.app_id + INNER JOIN api_keys + ON api_keys.id = oauth2_provider_app_tokens.api_key_id +WHERE api_keys.user_id = $1 +GROUP BY oauth2_provider_apps.id +` + +type GetOAuth2ProviderAppsByUserIDRow struct { + TokenCount int64 `db:"token_count" json:"token_count"` + OAuth2ProviderApp OAuth2ProviderApp `db:"oauth2_provider_app" json:"oauth2_provider_app"` +} + +func (q *sqlQuerier) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]GetOAuth2ProviderAppsByUserIDRow, error) { + rows, err := q.db.QueryContext(ctx, getOAuth2ProviderAppsByUserID, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetOAuth2ProviderAppsByUserIDRow + for rows.Next() { + var i GetOAuth2ProviderAppsByUserIDRow + if err := rows.Scan( + &i.TokenCount, + &i.OAuth2ProviderApp.ID, + &i.OAuth2ProviderApp.CreatedAt, + &i.OAuth2ProviderApp.UpdatedAt, + &i.OAuth2ProviderApp.Name, + &i.OAuth2ProviderApp.Icon, + &i.OAuth2ProviderApp.CallbackURL, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const insertOAuth2ProviderApp = `-- name: InsertOAuth2ProviderApp :one INSERT INTO oauth2_provider_apps ( id, @@ -2781,6 +2938,54 @@ func (q *sqlQuerier) InsertOAuth2ProviderApp(ctx context.Context, arg InsertOAut return i, err } +const insertOAuth2ProviderAppCode = `-- name: InsertOAuth2ProviderAppCode :one +INSERT INTO oauth2_provider_app_codes ( + id, + created_at, + expires_at, + hashed_secret, + app_id, + user_id +) VALUES( + $1, + $2, + $3, + $4, + $5, + $6 +) RETURNING id, created_at, expires_at, hashed_secret, user_id, app_id +` + +type InsertOAuth2ProviderAppCodeParams struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ExpiresAt time.Time `db:"expires_at" json:"expires_at"` + HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` + AppID uuid.UUID `db:"app_id" json:"app_id"` + UserID uuid.UUID `db:"user_id" json:"user_id"` +} + +func (q *sqlQuerier) InsertOAuth2ProviderAppCode(ctx context.Context, arg InsertOAuth2ProviderAppCodeParams) (OAuth2ProviderAppCode, error) { + row := q.db.QueryRowContext(ctx, insertOAuth2ProviderAppCode, + arg.ID, + arg.CreatedAt, + arg.ExpiresAt, + arg.HashedSecret, + arg.AppID, + arg.UserID, + ) + var i OAuth2ProviderAppCode + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.ExpiresAt, + &i.HashedSecret, + &i.UserID, + &i.AppID, + ) + return i, err +} + const insertOAuth2ProviderAppSecret = `-- name: InsertOAuth2ProviderAppSecret :one INSERT INTO oauth2_provider_app_secrets ( id, @@ -2825,6 +3030,54 @@ func (q *sqlQuerier) InsertOAuth2ProviderAppSecret(ctx context.Context, arg Inse return i, err } +const insertOAuth2ProviderAppToken = `-- name: InsertOAuth2ProviderAppToken :one +INSERT INTO oauth2_provider_app_tokens ( + id, + created_at, + expires_at, + hashed_secret, + app_secret_id, + api_key_id +) VALUES( + $1, + $2, + $3, + $4, + $5, + $6 +) RETURNING id, created_at, expires_at, hashed_secret, app_secret_id, api_key_id +` + +type InsertOAuth2ProviderAppTokenParams struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ExpiresAt time.Time `db:"expires_at" json:"expires_at"` + HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` + AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"` + APIKeyID string `db:"api_key_id" json:"api_key_id"` +} + +func (q *sqlQuerier) InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error) { + row := q.db.QueryRowContext(ctx, insertOAuth2ProviderAppToken, + arg.ID, + arg.CreatedAt, + arg.ExpiresAt, + arg.HashedSecret, + arg.AppSecretID, + arg.APIKeyID, + ) + var i OAuth2ProviderAppToken + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.ExpiresAt, + &i.HashedSecret, + &i.AppSecretID, + &i.APIKeyID, + ) + return i, err +} + const updateOAuth2ProviderAppByID = `-- name: UpdateOAuth2ProviderAppByID :one UPDATE oauth2_provider_apps SET updated_at = $2, diff --git a/coderd/database/queries/oauth2.sql b/coderd/database/queries/oauth2.sql index cd9a150d0bf38..c0c0c7dab6928 100644 --- a/coderd/database/queries/oauth2.sql +++ b/coderd/database/queries/oauth2.sql @@ -38,6 +38,9 @@ SELECT * FROM oauth2_provider_app_secrets WHERE id = $1; -- name: GetOAuth2ProviderAppSecretsByAppID :many SELECT * FROM oauth2_provider_app_secrets WHERE app_id = $1 ORDER BY (created_at, id) ASC; +-- name: GetOAuth2ProviderAppSecretByAppIDAndSecret :one +SELECT * FROM oauth2_provider_app_secrets WHERE app_id = $1 AND hashed_secret = $2; + -- name: InsertOAuth2ProviderAppSecret :one INSERT INTO oauth2_provider_app_secrets ( id, @@ -60,3 +63,71 @@ WHERE id = $1 RETURNING *; -- name: DeleteOAuth2ProviderAppSecretByID :exec DELETE FROM oauth2_provider_app_secrets WHERE id = $1; + +-- name: GetOAuth2ProviderAppCodeByID :one +SELECT * FROM oauth2_provider_app_codes WHERE id = $1; + +-- name: GetOAuth2ProviderAppCodeByAppIDAndSecret :one +SELECT * FROM oauth2_provider_app_codes WHERE app_id = $1 AND hashed_secret = $2; + +-- name: InsertOAuth2ProviderAppCode :one +INSERT INTO oauth2_provider_app_codes ( + id, + created_at, + expires_at, + hashed_secret, + app_id, + user_id +) VALUES( + $1, + $2, + $3, + $4, + $5, + $6 +) RETURNING *; + +-- name: DeleteOAuth2ProviderAppCodeByID :exec +DELETE FROM oauth2_provider_app_codes WHERE id = $1; + +-- name: DeleteOAuth2ProviderAppCodesByAppAndUserID :exec +DELETE FROM oauth2_provider_app_codes WHERE app_id = $1 AND user_id = $2; + +-- name: InsertOAuth2ProviderAppToken :one +INSERT INTO oauth2_provider_app_tokens ( + id, + created_at, + expires_at, + hashed_secret, + app_secret_id, + api_key_id +) VALUES( + $1, + $2, + $3, + $4, + $5, + $6 +) RETURNING *; + +-- name: GetOAuth2ProviderAppsByUserID :many +SELECT COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count, + sqlc.embed(oauth2_provider_apps) +FROM oauth2_provider_app_tokens + INNER JOIN oauth2_provider_app_secrets + ON oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id + INNER JOIN oauth2_provider_apps + ON oauth2_provider_apps.id = oauth2_provider_app_secrets.app_id + INNER JOIN api_keys + ON api_keys.id = oauth2_provider_app_tokens.api_key_id +WHERE api_keys.user_id = $1 +GROUP BY oauth2_provider_apps.id; + +-- name: DeleteOAuth2ProviderAppTokensByAppAndUserID :exec +DELETE FROM oauth2_provider_app_tokens +USING oauth2_provider_app_secrets, api_keys +WHERE + oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id + AND api_keys.id = oauth2_provider_app_tokens.api_key_id + AND oauth2_provider_app_secrets.app_id = $1 + AND api_keys.user_id = $2; diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index 49140d597ae9e..621946e7b4913 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -99,4 +99,8 @@ sql: display_app_ssh_helper: DisplayAppSSHHelper oauth2_provider_app: OAuth2ProviderApp oauth2_provider_app_secret: OAuth2ProviderAppSecret + oauth2_provider_app_code: OAuth2ProviderAppCode + oauth2_provider_app_token: OAuth2ProviderAppToken + api_key_id: APIKeyID callback_url: CallbackURL + login_type_oauth2_provider_app: LoginTypeOAuth2ProviderApp diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index f397692f1d6d1..039172c012a35 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -6,75 +6,79 @@ type UniqueConstraint string // UniqueConstraint enums. const ( - UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id); - UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id); - UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id); - UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest); - UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number); - UniqueDbcryptKeysRevokedKeyDigestKey UniqueConstraint = "dbcrypt_keys_revoked_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_revoked_key_digest_key UNIQUE (revoked_key_digest); - UniqueFilesHashCreatedByKey UniqueConstraint = "files_hash_created_by_key" // ALTER TABLE ONLY files ADD CONSTRAINT files_hash_created_by_key UNIQUE (hash, created_by); - UniqueFilesPkey UniqueConstraint = "files_pkey" // ALTER TABLE ONLY files ADD CONSTRAINT files_pkey PRIMARY KEY (id); - UniqueGitAuthLinksProviderIDUserIDKey UniqueConstraint = "git_auth_links_provider_id_user_id_key" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_provider_id_user_id_key UNIQUE (provider_id, user_id); - UniqueGitSSHKeysPkey UniqueConstraint = "gitsshkeys_pkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_pkey PRIMARY KEY (user_id); - UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); - UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); - UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); - UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); - UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); - UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); - UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); - UniqueOauth2ProviderAppsNameKey UniqueConstraint = "oauth2_provider_apps_name_key" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name); - UniqueOauth2ProviderAppsPkey UniqueConstraint = "oauth2_provider_apps_pkey" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_pkey PRIMARY KEY (id); - UniqueOrganizationMembersPkey UniqueConstraint = "organization_members_pkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id); - UniqueOrganizationsPkey UniqueConstraint = "organizations_pkey" // ALTER TABLE ONLY organizations ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); - UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name); - UniqueParameterSchemasPkey UniqueConstraint = "parameter_schemas_pkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id); - UniqueParameterValuesPkey UniqueConstraint = "parameter_values_pkey" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id); - UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name); - UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id); - UniqueProvisionerJobLogsPkey UniqueConstraint = "provisioner_job_logs_pkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id); - UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id); - UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key); - UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id); - UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id); - UniqueTailnetClientsPkey UniqueConstraint = "tailnet_clients_pkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_pkey PRIMARY KEY (id, coordinator_id); - UniqueTailnetCoordinatorsPkey UniqueConstraint = "tailnet_coordinators_pkey" // ALTER TABLE ONLY tailnet_coordinators ADD CONSTRAINT tailnet_coordinators_pkey PRIMARY KEY (id); - UniqueTailnetPeersPkey UniqueConstraint = "tailnet_peers_pkey" // ALTER TABLE ONLY tailnet_peers ADD CONSTRAINT tailnet_peers_pkey PRIMARY KEY (id, coordinator_id); - UniqueTailnetTunnelsPkey UniqueConstraint = "tailnet_tunnels_pkey" // ALTER TABLE ONLY tailnet_tunnels ADD CONSTRAINT tailnet_tunnels_pkey PRIMARY KEY (coordinator_id, src_id, dst_id); - UniqueTemplateVersionParametersTemplateVersionIDNameKey UniqueConstraint = "template_version_parameters_template_version_id_name_key" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_name_key UNIQUE (template_version_id, name); - UniqueTemplateVersionVariablesTemplateVersionIDNameKey UniqueConstraint = "template_version_variables_template_version_id_name_key" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_name_key UNIQUE (template_version_id, name); - UniqueTemplateVersionsPkey UniqueConstraint = "template_versions_pkey" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_pkey PRIMARY KEY (id); - UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name); - UniqueTemplatesPkey UniqueConstraint = "templates_pkey" // ALTER TABLE ONLY templates ADD CONSTRAINT templates_pkey PRIMARY KEY (id); - UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); - UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); - UniqueWorkspaceAgentLogSourcesPkey UniqueConstraint = "workspace_agent_log_sources_pkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id); - UniqueWorkspaceAgentMetadataPkey UniqueConstraint = "workspace_agent_metadata_pkey" // ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); - UniqueWorkspaceAgentStartupLogsPkey UniqueConstraint = "workspace_agent_startup_logs_pkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id); - UniqueWorkspaceAgentsPkey UniqueConstraint = "workspace_agents_pkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); - UniqueWorkspaceAppStatsPkey UniqueConstraint = "workspace_app_stats_pkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_pkey PRIMARY KEY (id); - UniqueWorkspaceAppStatsUserIDAgentIDSessionIDKey UniqueConstraint = "workspace_app_stats_user_id_agent_id_session_id_key" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_user_id_agent_id_session_id_key UNIQUE (user_id, agent_id, session_id); - UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug); - UniqueWorkspaceAppsPkey UniqueConstraint = "workspace_apps_pkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id); - UniqueWorkspaceBuildParametersWorkspaceBuildIDNameKey UniqueConstraint = "workspace_build_parameters_workspace_build_id_name_key" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_name_key UNIQUE (workspace_build_id, name); - UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); - UniqueWorkspaceBuildsPkey UniqueConstraint = "workspace_builds_pkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_pkey PRIMARY KEY (id); - UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number); - UniqueWorkspaceProxiesPkey UniqueConstraint = "workspace_proxies_pkey" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_pkey PRIMARY KEY (id); - UniqueWorkspaceProxiesRegionIDUnique UniqueConstraint = "workspace_proxies_region_id_unique" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_region_id_unique UNIQUE (region_id); - UniqueWorkspaceResourceMetadataName UniqueConstraint = "workspace_resource_metadata_name" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key); - UniqueWorkspaceResourceMetadataPkey UniqueConstraint = "workspace_resource_metadata_pkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id); - UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id); - UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id); - UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type); - UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name); - UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name)); - UniqueIndexProvisionerDaemonsNameOwnerKey UniqueConstraint = "idx_provisioner_daemons_name_owner_key" // CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower(COALESCE((tags ->> 'owner'::text), ''::text))); - UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false); - UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false); - UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); - UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); - UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); - UniqueWorkspaceProxiesLowerNameIndex UniqueConstraint = "workspace_proxies_lower_name_idx" // CREATE UNIQUE INDEX workspace_proxies_lower_name_idx ON workspace_proxies USING btree (lower(name)) WHERE (deleted = false); - UniqueWorkspacesOwnerIDLowerIndex UniqueConstraint = "workspaces_owner_id_lower_idx" // CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false); + UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id); + UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id); + UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id); + UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest); + UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number); + UniqueDbcryptKeysRevokedKeyDigestKey UniqueConstraint = "dbcrypt_keys_revoked_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_revoked_key_digest_key UNIQUE (revoked_key_digest); + UniqueFilesHashCreatedByKey UniqueConstraint = "files_hash_created_by_key" // ALTER TABLE ONLY files ADD CONSTRAINT files_hash_created_by_key UNIQUE (hash, created_by); + UniqueFilesPkey UniqueConstraint = "files_pkey" // ALTER TABLE ONLY files ADD CONSTRAINT files_pkey PRIMARY KEY (id); + UniqueGitAuthLinksProviderIDUserIDKey UniqueConstraint = "git_auth_links_provider_id_user_id_key" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_provider_id_user_id_key UNIQUE (provider_id, user_id); + UniqueGitSSHKeysPkey UniqueConstraint = "gitsshkeys_pkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_pkey PRIMARY KEY (user_id); + UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); + UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); + UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); + UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); + UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppCodesAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_codes_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); + UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); + UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppTokensAppSecretIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_tokens_app_secret_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_hashed_secret_key UNIQUE (app_secret_id, hashed_secret); + UniqueOauth2ProviderAppTokensPkey UniqueConstraint = "oauth2_provider_app_tokens_pkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppsNameKey UniqueConstraint = "oauth2_provider_apps_name_key" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name); + UniqueOauth2ProviderAppsPkey UniqueConstraint = "oauth2_provider_apps_pkey" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_pkey PRIMARY KEY (id); + UniqueOrganizationMembersPkey UniqueConstraint = "organization_members_pkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id); + UniqueOrganizationsPkey UniqueConstraint = "organizations_pkey" // ALTER TABLE ONLY organizations ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); + UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name); + UniqueParameterSchemasPkey UniqueConstraint = "parameter_schemas_pkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id); + UniqueParameterValuesPkey UniqueConstraint = "parameter_values_pkey" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id); + UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name); + UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id); + UniqueProvisionerJobLogsPkey UniqueConstraint = "provisioner_job_logs_pkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id); + UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id); + UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key); + UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id); + UniqueTailnetClientsPkey UniqueConstraint = "tailnet_clients_pkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetCoordinatorsPkey UniqueConstraint = "tailnet_coordinators_pkey" // ALTER TABLE ONLY tailnet_coordinators ADD CONSTRAINT tailnet_coordinators_pkey PRIMARY KEY (id); + UniqueTailnetPeersPkey UniqueConstraint = "tailnet_peers_pkey" // ALTER TABLE ONLY tailnet_peers ADD CONSTRAINT tailnet_peers_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetTunnelsPkey UniqueConstraint = "tailnet_tunnels_pkey" // ALTER TABLE ONLY tailnet_tunnels ADD CONSTRAINT tailnet_tunnels_pkey PRIMARY KEY (coordinator_id, src_id, dst_id); + UniqueTemplateVersionParametersTemplateVersionIDNameKey UniqueConstraint = "template_version_parameters_template_version_id_name_key" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_name_key UNIQUE (template_version_id, name); + UniqueTemplateVersionVariablesTemplateVersionIDNameKey UniqueConstraint = "template_version_variables_template_version_id_name_key" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_name_key UNIQUE (template_version_id, name); + UniqueTemplateVersionsPkey UniqueConstraint = "template_versions_pkey" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_pkey PRIMARY KEY (id); + UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name); + UniqueTemplatesPkey UniqueConstraint = "templates_pkey" // ALTER TABLE ONLY templates ADD CONSTRAINT templates_pkey PRIMARY KEY (id); + UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); + UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); + UniqueWorkspaceAgentLogSourcesPkey UniqueConstraint = "workspace_agent_log_sources_pkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id); + UniqueWorkspaceAgentMetadataPkey UniqueConstraint = "workspace_agent_metadata_pkey" // ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); + UniqueWorkspaceAgentStartupLogsPkey UniqueConstraint = "workspace_agent_startup_logs_pkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id); + UniqueWorkspaceAgentsPkey UniqueConstraint = "workspace_agents_pkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); + UniqueWorkspaceAppStatsPkey UniqueConstraint = "workspace_app_stats_pkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_pkey PRIMARY KEY (id); + UniqueWorkspaceAppStatsUserIDAgentIDSessionIDKey UniqueConstraint = "workspace_app_stats_user_id_agent_id_session_id_key" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_user_id_agent_id_session_id_key UNIQUE (user_id, agent_id, session_id); + UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug); + UniqueWorkspaceAppsPkey UniqueConstraint = "workspace_apps_pkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id); + UniqueWorkspaceBuildParametersWorkspaceBuildIDNameKey UniqueConstraint = "workspace_build_parameters_workspace_build_id_name_key" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_name_key UNIQUE (workspace_build_id, name); + UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); + UniqueWorkspaceBuildsPkey UniqueConstraint = "workspace_builds_pkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_pkey PRIMARY KEY (id); + UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number); + UniqueWorkspaceProxiesPkey UniqueConstraint = "workspace_proxies_pkey" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_pkey PRIMARY KEY (id); + UniqueWorkspaceProxiesRegionIDUnique UniqueConstraint = "workspace_proxies_region_id_unique" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_region_id_unique UNIQUE (region_id); + UniqueWorkspaceResourceMetadataName UniqueConstraint = "workspace_resource_metadata_name" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key); + UniqueWorkspaceResourceMetadataPkey UniqueConstraint = "workspace_resource_metadata_pkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id); + UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id); + UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id); + UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type); + UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name); + UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name)); + UniqueIndexProvisionerDaemonsNameOwnerKey UniqueConstraint = "idx_provisioner_daemons_name_owner_key" // CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower(COALESCE((tags ->> 'owner'::text), ''::text))); + UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false); + UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false); + UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); + UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); + UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); + UniqueWorkspaceProxiesLowerNameIndex UniqueConstraint = "workspace_proxies_lower_name_idx" // CREATE UNIQUE INDEX workspace_proxies_lower_name_idx ON workspace_proxies USING btree (lower(name)) WHERE (deleted = false); + UniqueWorkspacesOwnerIDLowerIndex UniqueConstraint = "workspaces_owner_id_lower_idx" // CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false); ) diff --git a/coderd/rbac/object.go b/coderd/rbac/object.go index 373c5b6b2df17..f130142f48f65 100644 --- a/coderd/rbac/object.go +++ b/coderd/rbac/object.go @@ -207,12 +207,20 @@ var ( Type: "oauth2_app", } - // ResourceOAuth2ProviderAppSecrets CRUD. + // ResourceOAuth2ProviderAppSecret CRUD. // create/delete = Make or delete an OAuth2 app secret. // update = Update last used date. // read = Read OAuth2 app hashed or truncated secret. ResourceOAuth2ProviderAppSecret = Object{ - Type: "oauth2_app_secrets", + Type: "oauth2_app_secret", + } + + // ResourceOAuth2ProviderAppCodeToken CRUD. + // create/delete = Make or delete an OAuth2 app code or token. + // update = None + // read = Check if OAuth2 app code or token exists. + ResourceOAuth2ProviderAppCodeToken = Object{ + Type: "oauth2_app_code_token", } ) diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index aadf3fa1edcb7..4833a14b30239 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -12,6 +12,7 @@ func AllResources() []Object { ResourceGroup, ResourceLicense, ResourceOAuth2ProviderApp, + ResourceOAuth2ProviderAppCodeToken, ResourceOAuth2ProviderAppSecret, ResourceOrgRoleAssignment, ResourceOrganization, diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index 7f8e0b2759547..5c8af956945b7 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -148,6 +148,8 @@ func ReloadBuiltinRoles(opts *RoleOptions) { ResourceRoleAssignment.Type: {ActionRead}, // All users can see the provisioner daemons. ResourceProvisionerDaemon.Type: {ActionRead}, + // All users can see OAuth2 provider applications. + ResourceOAuth2ProviderApp.Type: {ActionRead}, }), Org: map[string][]Permission{}, User: append(allPermsExcept(ResourceWorkspaceDormant, ResourceUser, ResourceOrganizationMember), From 7b118ac8d3ebb7294d10ae15efaf5b1c7104396d Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 23 Jan 2024 17:10:00 -0900 Subject: [PATCH 2/8] Check indexes in dbmem Might have no match, and a -1 will panic. --- coderd/database/dbmem/dbmem.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 95287b50bd48a..c75070f213f96 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -1310,7 +1310,8 @@ func (q *FakeQuerier) DeleteOAuth2ProviderAppTokensByAppAndUserID(_ context.Cont keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool { return key.ID == token.APIKeyID }) - if q.oauth2ProviderAppSecrets[secretIdx].AppID == arg.AppID && q.apiKeys[keyIdx].UserID == arg.UserID { + if secretIdx != -1 && q.oauth2ProviderAppSecrets[secretIdx].AppID == arg.AppID && + keyIdx != -1 && q.apiKeys[keyIdx].UserID == arg.UserID { keyIDsToDelete = append(keyIDsToDelete, token.APIKeyID) } else { tokens = append(tokens, token) @@ -2312,7 +2313,7 @@ func (q *FakeQuerier) GetOAuth2ProviderAppsByUserID(_ context.Context, userID uu keyIdx := slices.IndexFunc(q.apiKeys, func(key database.APIKey) bool { return key.ID == token.APIKeyID }) - if q.apiKeys[keyIdx].UserID == userID { + if keyIdx != -1 && q.apiKeys[keyIdx].UserID == userID { tokens = append(tokens, token) } } From b0df9650f8360611f1d0a1ea5f7fc85ed34f2669 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 24 Jan 2024 09:41:15 -0600 Subject: [PATCH 3/8] minor dbauthz changes --- coderd/database/dbauthz/dbauthz.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a9abce9712ae5..dadf0a61d2f1a 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -826,7 +826,7 @@ func (q *querier) DeleteOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.U if err != nil { return err } - if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(code.UserID.String())); err != nil { + if err := q.authorizeContext(ctx, rbac.ActionDelete, code); err != nil { return err } return q.db.DeleteOAuth2ProviderAppCodeByID(ctx, id) @@ -1222,7 +1222,7 @@ func (q *querier) GetOAuth2ProviderApps(ctx context.Context) ([]database.OAuth2P } func (q *querier) GetOAuth2ProviderAppsByUserID(ctx context.Context, userID uuid.UUID) ([]database.GetOAuth2ProviderAppsByUserIDRow, error) { - // These two authz checks make sure the caller can read all their own tokens. + // This authz check is to make sure the caller can read all their own tokens. if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(userID.String())); err != nil { return []database.GetOAuth2ProviderAppsByUserIDRow{}, err From cc366f6bbf9690bc8577e7393a6bb8fd5e0843ff Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 24 Jan 2024 09:43:28 -0600 Subject: [PATCH 4/8] use model struct over rbac.ResourceXX --- coderd/database/dbauthz/dbauthz_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 79a22081b0bae..37b68e68f8841 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2337,7 +2337,7 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() { AppID: app.ID, UserID: user.ID, }) - check.Args(code.ID).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead).Returns(code) + check.Args(code.ID).Asserts(code, rbac.ActionRead).Returns(code) })) s.Run("GetOAuth2ProviderAppCodeByAppIDAndSecret", s.Subtest(func(db database.Store, check *expects) { user := dbgen.User(s.T(), db, database.User{}) @@ -2349,7 +2349,7 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() { check.Args(database.GetOAuth2ProviderAppCodeByAppIDAndSecretParams{ AppID: app.ID, HashedSecret: code.HashedSecret, - }).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionRead).Returns(code) + }).Asserts(code, rbac.ActionRead).Returns(code) })) s.Run("InsertOAuth2ProviderAppCode", s.Subtest(func(db database.Store, check *expects) { user := dbgen.User(s.T(), db, database.User{}) @@ -2366,7 +2366,7 @@ func (s *MethodTestSuite) TestOAuth2ProviderAppCodes() { AppID: app.ID, UserID: user.ID, }) - check.Args(code.ID).Asserts(rbac.ResourceOAuth2ProviderAppCodeToken.WithOwner(user.ID.String()), rbac.ActionDelete) + check.Args(code.ID).Asserts(code, rbac.ActionDelete) })) s.Run("DeleteOAuth2ProviderAppCodesByAppAndUserID", s.Subtest(func(db database.Store, check *expects) { user := dbgen.User(s.T(), db, database.User{}) From be2ad5753cde4da70d0d2f97346a4da5e8b6e549 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 24 Jan 2024 11:12:40 -0900 Subject: [PATCH 5/8] Rename token hashed_secret to refresh_hash --- coderd/database/dbgen/dbgen.go | 12 +- coderd/database/dbmem/dbmem.go | 12 +- coderd/database/dump.sql | 6 +- .../000186_oauth2_provider_codes.up.sql | 6 +- coderd/database/models.go | 14 +- coderd/database/queries.sql.go | 20 +-- coderd/database/queries/oauth2.sql | 2 +- coderd/database/unique_constraint.go | 150 +++++++++--------- 8 files changed, 111 insertions(+), 111 deletions(-) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index accc593712658..1908b157d1365 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -719,12 +719,12 @@ func OAuth2ProviderAppCode(t testing.TB, db database.Store, seed database.OAuth2 func OAuth2ProviderAppToken(t testing.TB, db database.Store, seed database.OAuth2ProviderAppToken) database.OAuth2ProviderAppToken { token, err := db.InsertOAuth2ProviderAppToken(genCtx, database.InsertOAuth2ProviderAppTokenParams{ - ID: takeFirst(seed.ID, uuid.New()), - CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()), - ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()), - HashedSecret: takeFirstSlice(seed.HashedSecret, []byte("hashed-secret")), - AppSecretID: takeFirst(seed.AppSecretID, uuid.New()), - APIKeyID: takeFirst(seed.APIKeyID, uuid.New().String()), + ID: takeFirst(seed.ID, uuid.New()), + CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()), + ExpiresAt: takeFirst(seed.CreatedAt, dbtime.Now()), + RefreshHash: takeFirstSlice(seed.RefreshHash, []byte("hashed-secret")), + AppSecretID: takeFirst(seed.AppSecretID, uuid.New()), + APIKeyID: takeFirst(seed.APIKeyID, uuid.New().String()), }) require.NoError(t, err, "insert oauth2 app token") return token diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index c75070f213f96..6502af4fd0acb 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5372,12 +5372,12 @@ func (q *FakeQuerier) InsertOAuth2ProviderAppToken(_ context.Context, arg databa if secret.ID == arg.AppSecretID { //nolint:gosimple // Go wants database.OAuth2ProviderAppToken(arg), but we cannot be sure the structs will remain identical. token := database.OAuth2ProviderAppToken{ - ID: arg.ID, - CreatedAt: arg.CreatedAt, - ExpiresAt: arg.ExpiresAt, - HashedSecret: arg.HashedSecret, - APIKeyID: arg.APIKeyID, - AppSecretID: arg.AppSecretID, + ID: arg.ID, + CreatedAt: arg.CreatedAt, + ExpiresAt: arg.ExpiresAt, + RefreshHash: arg.RefreshHash, + APIKeyID: arg.APIKeyID, + AppSecretID: arg.AppSecretID, } q.oauth2ProviderAppTokens = append(q.oauth2ProviderAppTokens, token) return token, nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f29ae4b47a83f..ba229b6422eb3 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -496,12 +496,12 @@ CREATE TABLE oauth2_provider_app_tokens ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, expires_at timestamp with time zone NOT NULL, - hashed_secret bytea NOT NULL, + refresh_hash bytea NOT NULL, app_secret_id uuid NOT NULL, api_key_id text NOT NULL ); -COMMENT ON TABLE oauth2_provider_app_tokens IS 'Refresh tokens both provide a way to refresh an access tokens (API keys) and a way to link API keys with the OAuth2 app and secret that generated them.'; +COMMENT ON COLUMN oauth2_provider_app_tokens.refresh_hash IS 'Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key.'; CREATE TABLE oauth2_provider_apps ( id uuid NOT NULL, @@ -1342,7 +1342,7 @@ ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); ALTER TABLE ONLY oauth2_provider_app_tokens - ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_hashed_secret_key UNIQUE (app_secret_id, hashed_secret); + ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_refresh_hash_key UNIQUE (app_secret_id, refresh_hash); ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id); diff --git a/coderd/database/migrations/000186_oauth2_provider_codes.up.sql b/coderd/database/migrations/000186_oauth2_provider_codes.up.sql index 48295361a0238..f3c5f2d2131cf 100644 --- a/coderd/database/migrations/000186_oauth2_provider_codes.up.sql +++ b/coderd/database/migrations/000186_oauth2_provider_codes.up.sql @@ -15,14 +15,14 @@ CREATE TABLE oauth2_provider_app_tokens ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, expires_at timestamp with time zone NOT NULL, - hashed_secret bytea NOT NULL, + refresh_hash bytea NOT NULL, app_secret_id uuid NOT NULL REFERENCES oauth2_provider_app_secrets (id) ON DELETE CASCADE, api_key_id text NOT NULL REFERENCES api_keys (id) ON DELETE CASCADE, PRIMARY KEY (id), - UNIQUE(app_secret_id, hashed_secret) + UNIQUE(app_secret_id, refresh_hash) ); -COMMENT ON TABLE oauth2_provider_app_tokens IS 'Refresh tokens both provide a way to refresh an access tokens (API keys) and a way to link API keys with the OAuth2 app and secret that generated them.'; +COMMENT ON COLUMN oauth2_provider_app_tokens.refresh_hash IS 'Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key.'; -- When we delete a token, delete the API key associated with it. CREATE FUNCTION delete_deleted_oauth2_provider_app_token_api_key() RETURNS trigger diff --git a/coderd/database/models.go b/coderd/database/models.go index 29ec66275e875..d84f8d679280b 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1821,14 +1821,14 @@ type OAuth2ProviderAppSecret struct { AppID uuid.UUID `db:"app_id" json:"app_id"` } -// Refresh tokens both provide a way to refresh an access tokens (API keys) and a way to link API keys with the OAuth2 app and secret that generated them. type OAuth2ProviderAppToken struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - ExpiresAt time.Time `db:"expires_at" json:"expires_at"` - HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` - AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"` - APIKeyID string `db:"api_key_id" json:"api_key_id"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ExpiresAt time.Time `db:"expires_at" json:"expires_at"` + // Refresh tokens provide a way to refresh an access token (API key). An expired API key can be refreshed if this token is not yet expired, meaning this expiry can outlive an API key. + RefreshHash []byte `db:"refresh_hash" json:"refresh_hash"` + AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"` + APIKeyID string `db:"api_key_id" json:"api_key_id"` } type Organization struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4741c3a1c7803..6644e08dacf77 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3035,7 +3035,7 @@ INSERT INTO oauth2_provider_app_tokens ( id, created_at, expires_at, - hashed_secret, + refresh_hash, app_secret_id, api_key_id ) VALUES( @@ -3045,16 +3045,16 @@ INSERT INTO oauth2_provider_app_tokens ( $4, $5, $6 -) RETURNING id, created_at, expires_at, hashed_secret, app_secret_id, api_key_id +) RETURNING id, created_at, expires_at, refresh_hash, app_secret_id, api_key_id ` type InsertOAuth2ProviderAppTokenParams struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - ExpiresAt time.Time `db:"expires_at" json:"expires_at"` - HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"` - AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"` - APIKeyID string `db:"api_key_id" json:"api_key_id"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ExpiresAt time.Time `db:"expires_at" json:"expires_at"` + RefreshHash []byte `db:"refresh_hash" json:"refresh_hash"` + AppSecretID uuid.UUID `db:"app_secret_id" json:"app_secret_id"` + APIKeyID string `db:"api_key_id" json:"api_key_id"` } func (q *sqlQuerier) InsertOAuth2ProviderAppToken(ctx context.Context, arg InsertOAuth2ProviderAppTokenParams) (OAuth2ProviderAppToken, error) { @@ -3062,7 +3062,7 @@ func (q *sqlQuerier) InsertOAuth2ProviderAppToken(ctx context.Context, arg Inser arg.ID, arg.CreatedAt, arg.ExpiresAt, - arg.HashedSecret, + arg.RefreshHash, arg.AppSecretID, arg.APIKeyID, ) @@ -3071,7 +3071,7 @@ func (q *sqlQuerier) InsertOAuth2ProviderAppToken(ctx context.Context, arg Inser &i.ID, &i.CreatedAt, &i.ExpiresAt, - &i.HashedSecret, + &i.RefreshHash, &i.AppSecretID, &i.APIKeyID, ) diff --git a/coderd/database/queries/oauth2.sql b/coderd/database/queries/oauth2.sql index c0c0c7dab6928..f2eb2c9db5781 100644 --- a/coderd/database/queries/oauth2.sql +++ b/coderd/database/queries/oauth2.sql @@ -98,7 +98,7 @@ INSERT INTO oauth2_provider_app_tokens ( id, created_at, expires_at, - hashed_secret, + refresh_hash, app_secret_id, api_key_id ) VALUES( diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index 039172c012a35..2a3c794db7418 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -6,79 +6,79 @@ type UniqueConstraint string // UniqueConstraint enums. const ( - UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id); - UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id); - UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id); - UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest); - UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number); - UniqueDbcryptKeysRevokedKeyDigestKey UniqueConstraint = "dbcrypt_keys_revoked_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_revoked_key_digest_key UNIQUE (revoked_key_digest); - UniqueFilesHashCreatedByKey UniqueConstraint = "files_hash_created_by_key" // ALTER TABLE ONLY files ADD CONSTRAINT files_hash_created_by_key UNIQUE (hash, created_by); - UniqueFilesPkey UniqueConstraint = "files_pkey" // ALTER TABLE ONLY files ADD CONSTRAINT files_pkey PRIMARY KEY (id); - UniqueGitAuthLinksProviderIDUserIDKey UniqueConstraint = "git_auth_links_provider_id_user_id_key" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_provider_id_user_id_key UNIQUE (provider_id, user_id); - UniqueGitSSHKeysPkey UniqueConstraint = "gitsshkeys_pkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_pkey PRIMARY KEY (user_id); - UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); - UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); - UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); - UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); - UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); - UniqueOauth2ProviderAppCodesAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_codes_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); - UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); - UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); - UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); - UniqueOauth2ProviderAppTokensAppSecretIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_tokens_app_secret_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_hashed_secret_key UNIQUE (app_secret_id, hashed_secret); - UniqueOauth2ProviderAppTokensPkey UniqueConstraint = "oauth2_provider_app_tokens_pkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id); - UniqueOauth2ProviderAppsNameKey UniqueConstraint = "oauth2_provider_apps_name_key" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name); - UniqueOauth2ProviderAppsPkey UniqueConstraint = "oauth2_provider_apps_pkey" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_pkey PRIMARY KEY (id); - UniqueOrganizationMembersPkey UniqueConstraint = "organization_members_pkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id); - UniqueOrganizationsPkey UniqueConstraint = "organizations_pkey" // ALTER TABLE ONLY organizations ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); - UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name); - UniqueParameterSchemasPkey UniqueConstraint = "parameter_schemas_pkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id); - UniqueParameterValuesPkey UniqueConstraint = "parameter_values_pkey" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id); - UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name); - UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id); - UniqueProvisionerJobLogsPkey UniqueConstraint = "provisioner_job_logs_pkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id); - UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id); - UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key); - UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id); - UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id); - UniqueTailnetClientsPkey UniqueConstraint = "tailnet_clients_pkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_pkey PRIMARY KEY (id, coordinator_id); - UniqueTailnetCoordinatorsPkey UniqueConstraint = "tailnet_coordinators_pkey" // ALTER TABLE ONLY tailnet_coordinators ADD CONSTRAINT tailnet_coordinators_pkey PRIMARY KEY (id); - UniqueTailnetPeersPkey UniqueConstraint = "tailnet_peers_pkey" // ALTER TABLE ONLY tailnet_peers ADD CONSTRAINT tailnet_peers_pkey PRIMARY KEY (id, coordinator_id); - UniqueTailnetTunnelsPkey UniqueConstraint = "tailnet_tunnels_pkey" // ALTER TABLE ONLY tailnet_tunnels ADD CONSTRAINT tailnet_tunnels_pkey PRIMARY KEY (coordinator_id, src_id, dst_id); - UniqueTemplateVersionParametersTemplateVersionIDNameKey UniqueConstraint = "template_version_parameters_template_version_id_name_key" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_name_key UNIQUE (template_version_id, name); - UniqueTemplateVersionVariablesTemplateVersionIDNameKey UniqueConstraint = "template_version_variables_template_version_id_name_key" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_name_key UNIQUE (template_version_id, name); - UniqueTemplateVersionsPkey UniqueConstraint = "template_versions_pkey" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_pkey PRIMARY KEY (id); - UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name); - UniqueTemplatesPkey UniqueConstraint = "templates_pkey" // ALTER TABLE ONLY templates ADD CONSTRAINT templates_pkey PRIMARY KEY (id); - UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); - UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); - UniqueWorkspaceAgentLogSourcesPkey UniqueConstraint = "workspace_agent_log_sources_pkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id); - UniqueWorkspaceAgentMetadataPkey UniqueConstraint = "workspace_agent_metadata_pkey" // ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); - UniqueWorkspaceAgentStartupLogsPkey UniqueConstraint = "workspace_agent_startup_logs_pkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id); - UniqueWorkspaceAgentsPkey UniqueConstraint = "workspace_agents_pkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); - UniqueWorkspaceAppStatsPkey UniqueConstraint = "workspace_app_stats_pkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_pkey PRIMARY KEY (id); - UniqueWorkspaceAppStatsUserIDAgentIDSessionIDKey UniqueConstraint = "workspace_app_stats_user_id_agent_id_session_id_key" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_user_id_agent_id_session_id_key UNIQUE (user_id, agent_id, session_id); - UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug); - UniqueWorkspaceAppsPkey UniqueConstraint = "workspace_apps_pkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id); - UniqueWorkspaceBuildParametersWorkspaceBuildIDNameKey UniqueConstraint = "workspace_build_parameters_workspace_build_id_name_key" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_name_key UNIQUE (workspace_build_id, name); - UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); - UniqueWorkspaceBuildsPkey UniqueConstraint = "workspace_builds_pkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_pkey PRIMARY KEY (id); - UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number); - UniqueWorkspaceProxiesPkey UniqueConstraint = "workspace_proxies_pkey" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_pkey PRIMARY KEY (id); - UniqueWorkspaceProxiesRegionIDUnique UniqueConstraint = "workspace_proxies_region_id_unique" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_region_id_unique UNIQUE (region_id); - UniqueWorkspaceResourceMetadataName UniqueConstraint = "workspace_resource_metadata_name" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key); - UniqueWorkspaceResourceMetadataPkey UniqueConstraint = "workspace_resource_metadata_pkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id); - UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id); - UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id); - UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type); - UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name); - UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name)); - UniqueIndexProvisionerDaemonsNameOwnerKey UniqueConstraint = "idx_provisioner_daemons_name_owner_key" // CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower(COALESCE((tags ->> 'owner'::text), ''::text))); - UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false); - UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false); - UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); - UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); - UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); - UniqueWorkspaceProxiesLowerNameIndex UniqueConstraint = "workspace_proxies_lower_name_idx" // CREATE UNIQUE INDEX workspace_proxies_lower_name_idx ON workspace_proxies USING btree (lower(name)) WHERE (deleted = false); - UniqueWorkspacesOwnerIDLowerIndex UniqueConstraint = "workspaces_owner_id_lower_idx" // CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false); + UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id); + UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id); + UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id); + UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest); + UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number); + UniqueDbcryptKeysRevokedKeyDigestKey UniqueConstraint = "dbcrypt_keys_revoked_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_revoked_key_digest_key UNIQUE (revoked_key_digest); + UniqueFilesHashCreatedByKey UniqueConstraint = "files_hash_created_by_key" // ALTER TABLE ONLY files ADD CONSTRAINT files_hash_created_by_key UNIQUE (hash, created_by); + UniqueFilesPkey UniqueConstraint = "files_pkey" // ALTER TABLE ONLY files ADD CONSTRAINT files_pkey PRIMARY KEY (id); + UniqueGitAuthLinksProviderIDUserIDKey UniqueConstraint = "git_auth_links_provider_id_user_id_key" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_provider_id_user_id_key UNIQUE (provider_id, user_id); + UniqueGitSSHKeysPkey UniqueConstraint = "gitsshkeys_pkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_pkey PRIMARY KEY (user_id); + UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); + UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); + UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); + UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); + UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppCodesAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_codes_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); + UniqueOauth2ProviderAppCodesPkey UniqueConstraint = "oauth2_provider_app_codes_pkey" // ALTER TABLE ONLY oauth2_provider_app_codes ADD CONSTRAINT oauth2_provider_app_codes_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); + UniqueOauth2ProviderAppSecretsPkey UniqueConstraint = "oauth2_provider_app_secrets_pkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppTokensAppSecretIDRefreshHashKey UniqueConstraint = "oauth2_provider_app_tokens_app_secret_id_refresh_hash_key" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_app_secret_id_refresh_hash_key UNIQUE (app_secret_id, refresh_hash); + UniqueOauth2ProviderAppTokensPkey UniqueConstraint = "oauth2_provider_app_tokens_pkey" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT oauth2_provider_app_tokens_pkey PRIMARY KEY (id); + UniqueOauth2ProviderAppsNameKey UniqueConstraint = "oauth2_provider_apps_name_key" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_name_key UNIQUE (name); + UniqueOauth2ProviderAppsPkey UniqueConstraint = "oauth2_provider_apps_pkey" // ALTER TABLE ONLY oauth2_provider_apps ADD CONSTRAINT oauth2_provider_apps_pkey PRIMARY KEY (id); + UniqueOrganizationMembersPkey UniqueConstraint = "organization_members_pkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id); + UniqueOrganizationsPkey UniqueConstraint = "organizations_pkey" // ALTER TABLE ONLY organizations ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); + UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name); + UniqueParameterSchemasPkey UniqueConstraint = "parameter_schemas_pkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id); + UniqueParameterValuesPkey UniqueConstraint = "parameter_values_pkey" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id); + UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name); + UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id); + UniqueProvisionerJobLogsPkey UniqueConstraint = "provisioner_job_logs_pkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id); + UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id); + UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key); + UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id); + UniqueTailnetClientsPkey UniqueConstraint = "tailnet_clients_pkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetCoordinatorsPkey UniqueConstraint = "tailnet_coordinators_pkey" // ALTER TABLE ONLY tailnet_coordinators ADD CONSTRAINT tailnet_coordinators_pkey PRIMARY KEY (id); + UniqueTailnetPeersPkey UniqueConstraint = "tailnet_peers_pkey" // ALTER TABLE ONLY tailnet_peers ADD CONSTRAINT tailnet_peers_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetTunnelsPkey UniqueConstraint = "tailnet_tunnels_pkey" // ALTER TABLE ONLY tailnet_tunnels ADD CONSTRAINT tailnet_tunnels_pkey PRIMARY KEY (coordinator_id, src_id, dst_id); + UniqueTemplateVersionParametersTemplateVersionIDNameKey UniqueConstraint = "template_version_parameters_template_version_id_name_key" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_name_key UNIQUE (template_version_id, name); + UniqueTemplateVersionVariablesTemplateVersionIDNameKey UniqueConstraint = "template_version_variables_template_version_id_name_key" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_name_key UNIQUE (template_version_id, name); + UniqueTemplateVersionsPkey UniqueConstraint = "template_versions_pkey" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_pkey PRIMARY KEY (id); + UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name); + UniqueTemplatesPkey UniqueConstraint = "templates_pkey" // ALTER TABLE ONLY templates ADD CONSTRAINT templates_pkey PRIMARY KEY (id); + UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); + UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); + UniqueWorkspaceAgentLogSourcesPkey UniqueConstraint = "workspace_agent_log_sources_pkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id); + UniqueWorkspaceAgentMetadataPkey UniqueConstraint = "workspace_agent_metadata_pkey" // ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); + UniqueWorkspaceAgentStartupLogsPkey UniqueConstraint = "workspace_agent_startup_logs_pkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id); + UniqueWorkspaceAgentsPkey UniqueConstraint = "workspace_agents_pkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); + UniqueWorkspaceAppStatsPkey UniqueConstraint = "workspace_app_stats_pkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_pkey PRIMARY KEY (id); + UniqueWorkspaceAppStatsUserIDAgentIDSessionIDKey UniqueConstraint = "workspace_app_stats_user_id_agent_id_session_id_key" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_user_id_agent_id_session_id_key UNIQUE (user_id, agent_id, session_id); + UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug); + UniqueWorkspaceAppsPkey UniqueConstraint = "workspace_apps_pkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id); + UniqueWorkspaceBuildParametersWorkspaceBuildIDNameKey UniqueConstraint = "workspace_build_parameters_workspace_build_id_name_key" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_name_key UNIQUE (workspace_build_id, name); + UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); + UniqueWorkspaceBuildsPkey UniqueConstraint = "workspace_builds_pkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_pkey PRIMARY KEY (id); + UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number); + UniqueWorkspaceProxiesPkey UniqueConstraint = "workspace_proxies_pkey" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_pkey PRIMARY KEY (id); + UniqueWorkspaceProxiesRegionIDUnique UniqueConstraint = "workspace_proxies_region_id_unique" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_region_id_unique UNIQUE (region_id); + UniqueWorkspaceResourceMetadataName UniqueConstraint = "workspace_resource_metadata_name" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key); + UniqueWorkspaceResourceMetadataPkey UniqueConstraint = "workspace_resource_metadata_pkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id); + UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id); + UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id); + UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type); + UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name); + UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name)); + UniqueIndexProvisionerDaemonsNameOwnerKey UniqueConstraint = "idx_provisioner_daemons_name_owner_key" // CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower(COALESCE((tags ->> 'owner'::text), ''::text))); + UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false); + UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false); + UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false); + UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false); + UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); + UniqueWorkspaceProxiesLowerNameIndex UniqueConstraint = "workspace_proxies_lower_name_idx" // CREATE UNIQUE INDEX workspace_proxies_lower_name_idx ON workspace_proxies USING btree (lower(name)) WHERE (deleted = false); + UniqueWorkspacesOwnerIDLowerIndex UniqueConstraint = "workspaces_owner_id_lower_idx" // CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false); ) From 836854810cccb2da7e62f0a22afa6e6283d5d3b3 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 24 Jan 2024 11:15:04 -0900 Subject: [PATCH 6/8] Bump migration number 186 exists in main now. --- ...vider_codes.down.sql => 000187_oauth2_provider_codes.down.sql} | 0 ..._provider_codes.up.sql => 000187_oauth2_provider_codes.up.sql} | 0 ..._provider_codes.up.sql => 000187_oauth2_provider_codes.up.sql} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000186_oauth2_provider_codes.down.sql => 000187_oauth2_provider_codes.down.sql} (100%) rename coderd/database/migrations/{000186_oauth2_provider_codes.up.sql => 000187_oauth2_provider_codes.up.sql} (100%) rename coderd/database/migrations/testdata/fixtures/{000186_oauth2_provider_codes.up.sql => 000187_oauth2_provider_codes.up.sql} (100%) diff --git a/coderd/database/migrations/000186_oauth2_provider_codes.down.sql b/coderd/database/migrations/000187_oauth2_provider_codes.down.sql similarity index 100% rename from coderd/database/migrations/000186_oauth2_provider_codes.down.sql rename to coderd/database/migrations/000187_oauth2_provider_codes.down.sql diff --git a/coderd/database/migrations/000186_oauth2_provider_codes.up.sql b/coderd/database/migrations/000187_oauth2_provider_codes.up.sql similarity index 100% rename from coderd/database/migrations/000186_oauth2_provider_codes.up.sql rename to coderd/database/migrations/000187_oauth2_provider_codes.up.sql diff --git a/coderd/database/migrations/testdata/fixtures/000186_oauth2_provider_codes.up.sql b/coderd/database/migrations/testdata/fixtures/000187_oauth2_provider_codes.up.sql similarity index 100% rename from coderd/database/migrations/testdata/fixtures/000186_oauth2_provider_codes.up.sql rename to coderd/database/migrations/testdata/fixtures/000187_oauth2_provider_codes.up.sql From 29a9e26a761c6e1c601f36e6e30c760e69ff2c87 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 24 Jan 2024 11:17:26 -0900 Subject: [PATCH 7/8] Format oauth2.sql --- coderd/database/queries.sql.go | 15 ++++++++++----- coderd/database/queries/oauth2.sql | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 6644e08dacf77..0e8fb2bade966 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2653,8 +2653,10 @@ func (q *sqlQuerier) DeleteOAuth2ProviderAppSecretByID(ctx context.Context, id u } const deleteOAuth2ProviderAppTokensByAppAndUserID = `-- name: DeleteOAuth2ProviderAppTokensByAppAndUserID :exec -DELETE FROM oauth2_provider_app_tokens -USING oauth2_provider_app_secrets, api_keys +DELETE FROM + oauth2_provider_app_tokens +USING + oauth2_provider_app_secrets, api_keys WHERE oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id AND api_keys.id = oauth2_provider_app_tokens.api_key_id @@ -2841,7 +2843,8 @@ func (q *sqlQuerier) GetOAuth2ProviderApps(ctx context.Context) ([]OAuth2Provide } const getOAuth2ProviderAppsByUserID = `-- name: GetOAuth2ProviderAppsByUserID :many -SELECT COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count, +SELECT + COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count, oauth2_provider_apps.id, oauth2_provider_apps.created_at, oauth2_provider_apps.updated_at, oauth2_provider_apps.name, oauth2_provider_apps.icon, oauth2_provider_apps.callback_url FROM oauth2_provider_app_tokens INNER JOIN oauth2_provider_app_secrets @@ -2850,8 +2853,10 @@ FROM oauth2_provider_app_tokens ON oauth2_provider_apps.id = oauth2_provider_app_secrets.app_id INNER JOIN api_keys ON api_keys.id = oauth2_provider_app_tokens.api_key_id -WHERE api_keys.user_id = $1 -GROUP BY oauth2_provider_apps.id +WHERE + api_keys.user_id = $1 +GROUP BY + oauth2_provider_apps.id ` type GetOAuth2ProviderAppsByUserIDRow struct { diff --git a/coderd/database/queries/oauth2.sql b/coderd/database/queries/oauth2.sql index f2eb2c9db5781..cf4d7d0f9f643 100644 --- a/coderd/database/queries/oauth2.sql +++ b/coderd/database/queries/oauth2.sql @@ -111,7 +111,8 @@ INSERT INTO oauth2_provider_app_tokens ( ) RETURNING *; -- name: GetOAuth2ProviderAppsByUserID :many -SELECT COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count, +SELECT + COUNT(DISTINCT oauth2_provider_app_tokens.id) as token_count, sqlc.embed(oauth2_provider_apps) FROM oauth2_provider_app_tokens INNER JOIN oauth2_provider_app_secrets @@ -120,12 +121,16 @@ FROM oauth2_provider_app_tokens ON oauth2_provider_apps.id = oauth2_provider_app_secrets.app_id INNER JOIN api_keys ON api_keys.id = oauth2_provider_app_tokens.api_key_id -WHERE api_keys.user_id = $1 -GROUP BY oauth2_provider_apps.id; +WHERE + api_keys.user_id = $1 +GROUP BY + oauth2_provider_apps.id; -- name: DeleteOAuth2ProviderAppTokensByAppAndUserID :exec -DELETE FROM oauth2_provider_app_tokens -USING oauth2_provider_app_secrets, api_keys +DELETE FROM + oauth2_provider_app_tokens +USING + oauth2_provider_app_secrets, api_keys WHERE oauth2_provider_app_secrets.id = oauth2_provider_app_tokens.app_secret_id AND api_keys.id = oauth2_provider_app_tokens.api_key_id From c15f851678406a9ae959eb5ad2c79ac9a93cf701 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 24 Jan 2024 11:46:36 -0900 Subject: [PATCH 8/8] fixup! Rename token hashed_secret to refresh_hash --- .../testdata/fixtures/000187_oauth2_provider_codes.up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/testdata/fixtures/000187_oauth2_provider_codes.up.sql b/coderd/database/migrations/testdata/fixtures/000187_oauth2_provider_codes.up.sql index f4532a4523c6d..92294c76612fc 100644 --- a/coderd/database/migrations/testdata/fixtures/000187_oauth2_provider_codes.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000187_oauth2_provider_codes.up.sql @@ -10,7 +10,7 @@ VALUES ( ); INSERT INTO oauth2_provider_app_tokens - (id, created_at, expires_at, hashed_secret, app_secret_id, api_key_id) + (id, created_at, expires_at, refresh_hash, app_secret_id, api_key_id) VALUES ( 'd0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '2023-06-15 10:25:33+00',