diff --git a/README.md b/README.md index d5d338f..af168ef 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ - [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 - [ ] get api key approved + - [ ] automatically clean up access tokens and state when expired + - [ ] access tokens + - [ ] state - [?] 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) - [ ] Auth0 @@ -15,9 +18,9 @@ - [x] Get new access token using refresh token flow - [ ] test - [ ] Social connections login - - [ ] automatically clean up access tokens and state when expired - - [ ] access tokens - - [ ] state + - [x] automatically clean up access tokens and state when expired + - [x] access tokens + - [x] state - [ ] Complete this design document? - [ ] Complete defining this roadmap checklist - [ ] Website displaying an audit of store events diff --git a/internal/domains/authentication/auth.go b/internal/domains/authentication/auth.go index bf09109..8982821 100644 --- a/internal/domains/authentication/auth.go +++ b/internal/domains/authentication/auth.go @@ -81,6 +81,24 @@ func New(ctx context.Context, db *pgxpool.Pool) (*Authenticator, error) { }, 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. func (a *Authenticator) Exchange(ctx context.Context, state, code string) (accessToken string, expiration time.Time, err error) { // validate state diff --git a/main.go b/main.go index e06f407..0b1d578 100644 --- a/main.go +++ b/main.go @@ -48,13 +48,17 @@ func runApp(ctx context.Context) error { return fmt.Errorf("failed to initialize database connection pool: %w", err) } - // start http server + // start background processes auth, err := authentication.New(ctx, connPool) if err != nil { return fmt.Errorf("failed to construct authenticator: %w", err) } + authErrCh := runAuthProcesses(ctx, auth) + + // start http server + srvErrCh := runServer(ctx, connPool, auth) // 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) signal.Notify(osSignalCh, syscall.SIGINT, syscall.SIGTERM) - var serverAlreadyShutdown bool + var ( + alreadyShutdown struct { + server bool + authProcesses bool + } + ) select { case s := <-osSignalCh: fmt.Println("application received shutdown signal:", s) 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: - serverAlreadyShutdown = true + alreadyShutdown.server = true fmt.Println("server shutdown unexpectedly") if err != nil { fmt.Println("server encountered error:", err) @@ -81,7 +96,14 @@ func runApp(ctx context.Context) 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 { 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...) } +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 { srv := &http.Server{ Addr: ":8082", // local