Skip to content

Commit dae9dfa

Browse files
committed
feat: add cleanup for expired OAuth2 provider app codes and tokens
Change-Id: I07e7c229efa6e92282885464d2193dfc4c2e1c98 Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent e24c4b5 commit dae9dfa

File tree

8 files changed

+229
-0
lines changed

8 files changed

+229
-0
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,22 @@ func (q *querier) DeleteCustomRole(ctx context.Context, arg database.DeleteCusto
14461446
return q.db.DeleteCustomRole(ctx, arg)
14471447
}
14481448

1449+
func (q *querier) DeleteExpiredOAuth2ProviderAppCodes(ctx context.Context) error {
1450+
// System operation - only system can clean up expired authorization codes
1451+
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
1452+
return err
1453+
}
1454+
return q.db.DeleteExpiredOAuth2ProviderAppCodes(ctx)
1455+
}
1456+
1457+
func (q *querier) DeleteExpiredOAuth2ProviderAppTokens(ctx context.Context) error {
1458+
// System operation - only system can clean up expired access tokens
1459+
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
1460+
return err
1461+
}
1462+
return q.db.DeleteExpiredOAuth2ProviderAppTokens(ctx)
1463+
}
1464+
14491465
func (q *querier) DeleteExpiredOAuth2ProviderDeviceCodes(ctx context.Context) error {
14501466
// System operation - only system can clean up expired device codes
14511467
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {

coderd/database/dbmetrics/querymetrics.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbmock/dbmock.go

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbpurge/dbpurge.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, clk quartz.
6262
if err := tx.DeleteOldNotificationMessages(ctx); err != nil {
6363
return xerrors.Errorf("failed to delete old notification messages: %w", err)
6464
}
65+
if err := tx.DeleteExpiredOAuth2ProviderAppCodes(ctx); err != nil {
66+
return xerrors.Errorf("failed to delete expired oauth2 provider app codes: %w", err)
67+
}
68+
if err := tx.DeleteExpiredOAuth2ProviderAppTokens(ctx); err != nil {
69+
return xerrors.Errorf("failed to delete expired oauth2 provider app tokens: %w", err)
70+
}
71+
if err := tx.DeleteExpiredOAuth2ProviderDeviceCodes(ctx); err != nil {
72+
return xerrors.Errorf("failed to delete expired oauth2 provider device codes: %w", err)
73+
}
6574

6675
logger.Debug(ctx, "purged old database entries", slog.F("duration", clk.Since(start)))
6776

coderd/database/dbpurge/dbpurge_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,135 @@ func containsProvisionerDaemon(daemons []database.ProvisionerDaemon, name string
490490
return d.Name == name
491491
})
492492
}
493+
494+
//nolint:paralleltest // It uses LockIDDBPurge.
495+
func TestDeleteExpiredOAuth2ProviderAppCodes(t *testing.T) {
496+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
497+
defer cancel()
498+
499+
clk := quartz.NewMock(t)
500+
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
501+
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
502+
503+
now := dbtime.Now()
504+
clk.Set(now).MustWait(ctx)
505+
506+
// Create test data
507+
user := dbgen.User(t, db, database.User{})
508+
app := dbgen.OAuth2ProviderApp(t, db, database.OAuth2ProviderApp{
509+
Name: fmt.Sprintf("test-codes-%d", time.Now().UnixNano()),
510+
})
511+
512+
// Create expired authorization code (should be deleted)
513+
expiredCode := dbgen.OAuth2ProviderAppCode(t, db, database.OAuth2ProviderAppCode{
514+
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
515+
AppID: app.ID,
516+
UserID: user.ID,
517+
})
518+
519+
// Verify code exists initially
520+
_, err := db.GetOAuth2ProviderAppCodeByID(ctx, expiredCode.ID)
521+
require.NoError(t, err)
522+
523+
// Run cleanup
524+
done := awaitDoTick(ctx, t, clk)
525+
closer := dbpurge.New(ctx, logger, db, clk)
526+
defer closer.Close()
527+
<-done
528+
529+
// Verify expired code is deleted
530+
_, err = db.GetOAuth2ProviderAppCodeByID(ctx, expiredCode.ID)
531+
require.Error(t, err)
532+
require.ErrorIs(t, err, sql.ErrNoRows)
533+
}
534+
535+
//nolint:paralleltest // It uses LockIDDBPurge.
536+
func TestDeleteExpiredOAuth2ProviderAppTokens(t *testing.T) {
537+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
538+
defer cancel()
539+
540+
clk := quartz.NewMock(t)
541+
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
542+
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
543+
544+
now := dbtime.Now()
545+
clk.Set(now).MustWait(ctx)
546+
547+
// Create test data
548+
user := dbgen.User(t, db, database.User{})
549+
app := dbgen.OAuth2ProviderApp(t, db, database.OAuth2ProviderApp{
550+
Name: fmt.Sprintf("test-tokens-%d", time.Now().UnixNano()),
551+
})
552+
appSecret := dbgen.OAuth2ProviderAppSecret(t, db, database.OAuth2ProviderAppSecret{
553+
AppID: app.ID,
554+
})
555+
556+
// Create API keys for the tokens
557+
expiredAPIKey, _ := dbgen.APIKey(t, db, database.APIKey{
558+
UserID: user.ID,
559+
ExpiresAt: now.Add(-1 * time.Hour),
560+
})
561+
562+
// Create expired access token (should be deleted)
563+
expiredToken := dbgen.OAuth2ProviderAppToken(t, db, database.OAuth2ProviderAppToken{
564+
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
565+
AppSecretID: appSecret.ID,
566+
APIKeyID: expiredAPIKey.ID,
567+
UserID: user.ID,
568+
})
569+
570+
// Verify token exists initially
571+
_, err := db.GetOAuth2ProviderAppTokenByPrefix(ctx, expiredToken.HashPrefix)
572+
require.NoError(t, err)
573+
574+
// Run cleanup
575+
done := awaitDoTick(ctx, t, clk)
576+
closer := dbpurge.New(ctx, logger, db, clk)
577+
defer closer.Close()
578+
<-done
579+
580+
// Verify expired token is deleted
581+
_, err = db.GetOAuth2ProviderAppTokenByPrefix(ctx, expiredToken.HashPrefix)
582+
require.Error(t, err)
583+
require.ErrorIs(t, err, sql.ErrNoRows)
584+
}
585+
586+
//nolint:paralleltest // It uses LockIDDBPurge.
587+
func TestDeleteExpiredOAuth2ProviderDeviceCodes(t *testing.T) {
588+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
589+
defer cancel()
590+
591+
clk := quartz.NewMock(t)
592+
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
593+
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
594+
595+
now := dbtime.Now()
596+
clk.Set(now).MustWait(ctx)
597+
598+
// Create test data
599+
app := dbgen.OAuth2ProviderApp(t, db, database.OAuth2ProviderApp{
600+
Name: fmt.Sprintf("test-device-%d", time.Now().UnixNano()),
601+
})
602+
603+
// Create expired device code with pending status (should be deleted)
604+
expiredDeviceCode := dbgen.OAuth2ProviderDeviceCode(t, db, database.OAuth2ProviderDeviceCode{
605+
ExpiresAt: now.Add(-1 * time.Hour), // Expired 1 hour ago
606+
ClientID: app.ID,
607+
Status: database.OAuth2DeviceStatusPending,
608+
})
609+
610+
// Verify device code exists initially
611+
_, err := db.GetOAuth2ProviderDeviceCodeByID(ctx, expiredDeviceCode.ID)
612+
require.NoError(t, err)
613+
614+
// Run cleanup
615+
done := awaitDoTick(ctx, t, clk)
616+
closer := dbpurge.New(ctx, logger, db, clk)
617+
defer closer.Close()
618+
<-done
619+
620+
// Verify expired pending device code is deleted
621+
_, err = db.GetOAuth2ProviderDeviceCodeByID(ctx, expiredDeviceCode.ID)
622+
require.Error(t, err)
623+
require.ErrorIs(t, err, sql.ErrNoRows)
624+
}

coderd/database/querier.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/oauth2.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,14 @@ DELETE FROM oauth2_provider_device_codes WHERE id = $1;
307307
DELETE FROM oauth2_provider_device_codes
308308
WHERE expires_at < NOW() AND status = 'pending';
309309

310+
-- name: DeleteExpiredOAuth2ProviderAppCodes :exec
311+
DELETE FROM oauth2_provider_app_codes
312+
WHERE expires_at < NOW();
313+
314+
-- name: DeleteExpiredOAuth2ProviderAppTokens :exec
315+
DELETE FROM oauth2_provider_app_tokens
316+
WHERE expires_at < NOW();
317+
310318
-- name: GetOAuth2ProviderDeviceCodesByClientID :many
311319
SELECT * FROM oauth2_provider_device_codes
312320
WHERE client_id = $1

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy