From e39eb5824b4b1da3decb6b1dd5f15063d6b3b526 Mon Sep 17 00:00:00 2001 From: Johan Siebens Date: Mon, 12 Feb 2024 10:27:58 +0100 Subject: [PATCH] improvement: set last authentication timestamp on user and use it to check ssh access --- .../m202402120800_user_last_authenticated.go | 23 +++++++++++++++ internal/database/migration/migrations.go | 1 + internal/domain/account.go | 7 ++--- internal/domain/repository.go | 1 + internal/domain/user.go | 29 ++++++++++++++----- internal/handlers/authentication.go | 20 ++++++++++++- internal/handlers/ssh_action.go | 4 +-- 7 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 internal/database/migration/m202402120800_user_last_authenticated.go diff --git a/internal/database/migration/m202402120800_user_last_authenticated.go b/internal/database/migration/m202402120800_user_last_authenticated.go new file mode 100644 index 0000000..a454d88 --- /dev/null +++ b/internal/database/migration/m202402120800_user_last_authenticated.go @@ -0,0 +1,23 @@ +package migration + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" + "time" +) + +func m202402120800_user_last_authenticated() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "202402120800", + Migrate: func(db *gorm.DB) error { + type User struct { + LastAuthenticated *time.Time + } + + return db.AutoMigrate( + &User{}, + ) + }, + Rollback: nil, + } +} diff --git a/internal/database/migration/migrations.go b/internal/database/migration/migrations.go index aeaafd6..9da4d79 100644 --- a/internal/database/migration/migrations.go +++ b/internal/database/migration/migrations.go @@ -19,6 +19,7 @@ func Migrations() []*gormigrate.Migration { m202312271200_account_last_authenticated(), m202312290900_machine_indeces(), m202401061400_machine_indeces(), + m202402120800_user_last_authenticated(), } return migrations } diff --git a/internal/domain/account.go b/internal/domain/account.go index b591177..5bf22f7 100644 --- a/internal/domain/account.go +++ b/internal/domain/account.go @@ -9,10 +9,9 @@ import ( ) type Account struct { - ID uint64 `gorm:"primary_key"` - ExternalID string - LoginName string - LastAuthenticated *time.Time + ID uint64 `gorm:"primary_key"` + ExternalID string + LoginName string } func (r *repository) GetOrCreateAccount(ctx context.Context, externalID, loginName string) (*Account, bool, error) { diff --git a/internal/domain/repository.go b/internal/domain/repository.go index f6e61c5..6b95c69 100644 --- a/internal/domain/repository.go +++ b/internal/domain/repository.go @@ -55,6 +55,7 @@ type Repository interface { DeleteUser(ctx context.Context, userID uint64) error ListUsers(ctx context.Context, tailnetID uint64) (Users, error) DeleteUsersByTailnet(ctx context.Context, tailnetID uint64) error + SetUserLastAuthenticated(ctx context.Context, userID uint64, timestamp time.Time) error SaveMachine(ctx context.Context, m *Machine) error DeleteMachine(ctx context.Context, id uint64) (bool, error) diff --git a/internal/domain/user.go b/internal/domain/user.go index 42d59e2..70767f0 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/jsiebens/ionscale/internal/util" "gorm.io/gorm" + "time" ) type SystemRole string @@ -38,13 +39,14 @@ func (s UserRole) IsAdmin() bool { } type User struct { - ID uint64 `gorm:"primary_key"` - Name string - UserType UserType - TailnetID uint64 - Tailnet Tailnet - AccountID *uint64 - Account *Account + ID uint64 `gorm:"primary_key"` + Name string + UserType UserType + LastAuthenticated *time.Time + TailnetID uint64 + Tailnet Tailnet + AccountID *uint64 + Account *Account } type Users []User @@ -117,3 +119,16 @@ func (r *repository) DeleteUser(ctx context.Context, userID uint64) error { tx := r.withContext(ctx).Delete(&User{ID: userID}) return tx.Error } + +func (r *repository) SetUserLastAuthenticated(ctx context.Context, userID uint64, timestamp time.Time) error { + tx := r.withContext(ctx). + Model(User{}). + Where("id = ?", userID). + Updates(map[string]interface{}{"last_authenticated": ×tamp}) + + if tx.Error != nil { + return tx.Error + } + + return nil +} diff --git a/internal/handlers/authentication.go b/internal/handlers/authentication.go index 1f50a62..ce1ac2d 100644 --- a/internal/handlers/authentication.go +++ b/internal/handlers/authentication.go @@ -193,9 +193,20 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error { if !machine.HasTags() && machine.User.AccountID != nil && *machine.User.AccountID == account.ID { sshActionReq.Action = "accept" - if err := h.repository.SaveSSHActionRequest(ctx, sshActionReq); err != nil { + + err := h.repository.Transaction(func(rp domain.Repository) error { + if err := rp.SetUserLastAuthenticated(ctx, machine.UserID, time.Now().UTC()); err != nil { + return err + } + if err := rp.SaveSSHActionRequest(ctx, sshActionReq); err != nil { + return err + } + return nil + }) + if err != nil { return logError(err) } + return c.Redirect(http.StatusFound, "/a/success") } @@ -361,6 +372,9 @@ func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, form E req.TailnetID = &tailnet.ID err = h.repository.Transaction(func(rp domain.Repository) error { + if err := rp.SetUserLastAuthenticated(ctx, user.ID, time.Now().UTC()); err != nil { + return err + } if err := rp.SaveApiKey(ctx, apiKey); err != nil { return err } @@ -522,6 +536,10 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, form registrationRequest.Error = "" registrationRequest.UserID = user.ID + if err := rp.SetUserLastAuthenticated(ctx, m.UserID, time.Now().UTC()); err != nil { + return err + } + if err := rp.SaveMachine(ctx, m); err != nil { return err } diff --git a/internal/handlers/ssh_action.go b/internal/handlers/ssh_action.go index 7fb9a61..93a7e20 100644 --- a/internal/handlers/ssh_action.go +++ b/internal/handlers/ssh_action.go @@ -52,8 +52,8 @@ func (h *SSHActionHandlers) StartAuth(c echo.Context) error { return logError(err) } - if machine.User.Account != nil && machine.User.Account.LastAuthenticated != nil { - sinceLastAuthentication := time.Since(*machine.User.Account.LastAuthenticated) + if machine.User.Account != nil && machine.User.LastAuthenticated != nil { + sinceLastAuthentication := time.Since(*machine.User.LastAuthenticated) if sinceLastAuthentication < checkPeriod { resp := &tailcfg.SSHAction{