oauth: automatically clean up expired oauth state in db

This commit is contained in:
2026-01-05 15:56:53 -07:00
parent 55a7fcfede
commit e3e996ca7a
3 changed files with 63 additions and 7 deletions
+6 -3
View File
@@ -8,6 +8,9 @@
- [x] move auth state stuff to database (out of cache) - [x] move auth state stuff to database (out of cache)
- [x] only generate a sign up link IF they click the link on the accounts page - [x] only generate a sign up link IF they click the link on the accounts page
- [ ] get api key approved - [ ] get api key approved
- [ ] automatically clean up access tokens and state when expired
- [ ] access tokens
- [ ] state
- [?] Get new access token using refresh token flow - [?] Get new access token using refresh token flow
- [ ] make a FK between the etsy_store_events table and etsy_users table (store_id columns don't match types) - [ ] make a FK between the etsy_store_events table and etsy_users table (store_id columns don't match types)
- [ ] Auth0 - [ ] Auth0
@@ -15,9 +18,9 @@
- [x] Get new access token using refresh token flow - [x] Get new access token using refresh token flow
- [ ] test - [ ] test
- [ ] Social connections login - [ ] Social connections login
- [ ] automatically clean up access tokens and state when expired - [x] automatically clean up access tokens and state when expired
- [ ] access tokens - [x] access tokens
- [ ] state - [x] state
- [ ] Complete this design document? - [ ] Complete this design document?
- [ ] Complete defining this roadmap checklist - [ ] Complete defining this roadmap checklist
- [ ] Website displaying an audit of store events - [ ] Website displaying an audit of store events
+18
View File
@@ -81,6 +81,24 @@ func New(ctx context.Context, db *pgxpool.Pool) (*Authenticator, error) {
}, nil }, nil
} }
func (a *Authenticator) RunBackgroundCleanup(ctx context.Context) error {
for {
if _, err := a.db.Exec(ctx, "DELETE FROM oauth_tokens WHERE expiry < NOW()"); err != nil {
return fmt.Errorf("failed to delete all oauth_tokens rows that are expired: %w", err)
}
if _, err := a.db.Exec(ctx, "DELETE FROM oauth_login_states WHERE expiration < NOW()"); err != nil {
return fmt.Errorf("failed to delete all oauth_login_states rows that are expired: %w", err)
}
select {
case <-ctx.Done():
return nil
case <-time.After(time.Minute):
}
}
}
// Exchange exchanges an auth code for an access token. // Exchange exchanges an auth code for an access token.
func (a *Authenticator) Exchange(ctx context.Context, state, code string) (accessToken string, expiration time.Time, err error) { func (a *Authenticator) Exchange(ctx context.Context, state, code string) (accessToken string, expiration time.Time, err error) {
// validate state // validate state
+39 -4
View File
@@ -48,13 +48,17 @@ func runApp(ctx context.Context) error {
return fmt.Errorf("failed to initialize database connection pool: %w", err) return fmt.Errorf("failed to initialize database connection pool: %w", err)
} }
// start http server // start background processes
auth, err := authentication.New(ctx, connPool) auth, err := authentication.New(ctx, connPool)
if err != nil { if err != nil {
return fmt.Errorf("failed to construct authenticator: %w", err) return fmt.Errorf("failed to construct authenticator: %w", err)
} }
authErrCh := runAuthProcesses(ctx, auth)
// start http server
srvErrCh := runServer(ctx, connPool, auth) srvErrCh := runServer(ctx, connPool, auth)
// wait for interrupt signal or unrecoverable failure, then shutdown // wait for interrupt signal or unrecoverable failure, then shutdown
@@ -62,13 +66,24 @@ func runApp(ctx context.Context) error {
osSignalCh := make(chan os.Signal, 1) osSignalCh := make(chan os.Signal, 1)
signal.Notify(osSignalCh, syscall.SIGINT, syscall.SIGTERM) signal.Notify(osSignalCh, syscall.SIGINT, syscall.SIGTERM)
var serverAlreadyShutdown bool var (
alreadyShutdown struct {
server bool
authProcesses bool
}
)
select { select {
case s := <-osSignalCh: case s := <-osSignalCh:
fmt.Println("application received shutdown signal:", s) fmt.Println("application received shutdown signal:", s)
fmt.Println("shutting down") fmt.Println("shutting down")
case err := <-authErrCh:
alreadyShutdown.authProcesses = true
fmt.Println("auth processes shutdown unexpectedly")
if err != nil {
fmt.Println("auth processes encountered error:", err)
}
case err := <-srvErrCh: case err := <-srvErrCh:
serverAlreadyShutdown = true alreadyShutdown.server = true
fmt.Println("server shutdown unexpectedly") fmt.Println("server shutdown unexpectedly")
if err != nil { if err != nil {
fmt.Println("server encountered error:", err) fmt.Println("server encountered error:", err)
@@ -81,7 +96,14 @@ func runApp(ctx context.Context) error {
var errs []error var errs []error
if !serverAlreadyShutdown { if !alreadyShutdown.authProcesses {
if err := <-authErrCh; err != nil {
errs = append(errs, fmt.Errorf("auth processes experienced an error: %w", err))
}
fmt.Println("auth processes shut down")
}
if !alreadyShutdown.server {
if err := <-srvErrCh; err != nil { if err := <-srvErrCh; err != nil {
errs = append(errs, fmt.Errorf("server experienced an error: %w", err)) errs = append(errs, fmt.Errorf("server experienced an error: %w", err))
} }
@@ -91,6 +113,19 @@ func runApp(ctx context.Context) error {
return errors.Join(errs...) return errors.Join(errs...)
} }
func runAuthProcesses(ctx context.Context, auth *authentication.Authenticator) <-chan error {
errCh := make(chan error, 1)
go func() {
defer close(errCh)
if err := auth.RunBackgroundCleanup(ctx); err != nil {
errCh <- err
}
}()
return errCh
}
func runServer(ctx context.Context, connPool *pgxpool.Pool, auth *authentication.Authenticator) <-chan error { func runServer(ctx context.Context, connPool *pgxpool.Pool, auth *authentication.Authenticator) <-chan error {
srv := &http.Server{ srv := &http.Server{
Addr: ":8082", // local Addr: ":8082", // local