diff --git a/go.mod b/go.mod index 2cfc923..45cb701 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.kundeng.us/phoenix/textsender-auth go 1.25.4 require ( - git.kundeng.us/phoenix/textsender-models v0.0.12 + git.kundeng.us/phoenix/textsender-models v0.1.5-30-6f9e112b72-556 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/cors v1.2.2 github.com/golang-jwt/jwt/v5 v5.3.0 diff --git a/go.sum b/go.sum index c5407c6..042367d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.kundeng.us/phoenix/textsender-models v0.0.12 h1:ps9H3FS5LyCwQhAiIvg4vYyfLZZ64dex1y9ytb0o9C4= -git.kundeng.us/phoenix/textsender-models v0.0.12/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0= +git.kundeng.us/phoenix/textsender-models v0.1.5-30-6f9e112b72-556 h1:Ispsun00898NAWwnTfTAQQxbbVz+Te0bTl8z6t9E+gs= +git.kundeng.us/phoenix/textsender-models v0.1.5-30-6f9e112b72-556/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/internal/handler/endpoint/endpoint.go b/internal/handler/endpoint/endpoint.go index 5b3b3b0..c198bde 100644 --- a/internal/handler/endpoint/endpoint.go +++ b/internal/handler/endpoint/endpoint.go @@ -3,7 +3,9 @@ package endpoint // Endpoint for registering a user const Register = "/api/v1/register" const Login = "/api/v1/login" +const UpdatePassword = "/api/v1/user/password/update" + const CreateServiceUser = "/api/v1/service/register" const LoginServiceUser = "/api/v1/service/login" + const TokenRefresh = "/api/v1/token/refresh" -const UpdatePassword = "/api/v1/user/password/update" diff --git a/internal/handler/login.go b/internal/handler/login.go index 460fece..3ddab7a 100644 --- a/internal/handler/login.go +++ b/internal/handler/login.go @@ -3,6 +3,7 @@ package handler import ( "log" "net/http" + "time" "git.kundeng.us/phoenix/textsender-models/tx0/token" "git.kundeng.us/phoenix/textsender-models/tx0/user" @@ -75,6 +76,7 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) { resp.Message = err.Error() } else { if hashing.CheckPasswordHash(req.Password, user.Password) { + lastLogin := time.Now() var tokGen utility.TokenGenerator secretKey := config.GetSecretKey() tokGen.SetSecretKey(secretKey) @@ -83,9 +85,16 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) { statusCode = http.StatusInternalServerError resp.Message = "Error generating token" } else { - statusCode = http.StatusOK - resp.Data = append(resp.Data, *myToken) - resp.Message = "Successful" + log.Println("Updating user's last login") + if rowsAffected, err := l.UserStore.UpdateLastLogin(ctx, user.Id, lastLogin); err != nil { + statusCode = http.StatusInternalServerError + resp.Message = err.Error() + } else { + log.Println("Rows updated:", rowsAffected) + statusCode = http.StatusOK + resp.Data = append(resp.Data, *myToken) + resp.Message = "Successful" + } } } else { statusCode = http.StatusNotFound diff --git a/internal/handler/service.go b/internal/handler/service.go index 09e5770..6e2dd9f 100644 --- a/internal/handler/service.go +++ b/internal/handler/service.go @@ -1,7 +1,9 @@ package handler import ( + "log" "net/http" + "time" "git.kundeng.us/phoenix/textsender-models/tx0/token" "git.kundeng.us/phoenix/textsender-models/tx0/user" @@ -153,6 +155,7 @@ func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) { statusCode = http.StatusInternalServerError resp.Message = "Not valid" } else { + lastLogin := time.Now() var tokGen utility.TokenGenerator tokGen.SetHourOffset(8) secretKey := config.GetSecretKey() @@ -162,9 +165,16 @@ func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) { statusCode = http.StatusInternalServerError resp.Message = err.Error() } else { - statusCode = http.StatusOK - resp.Data = append(resp.Data, myToken) - resp.Message = "Successful" + log.Println("Updating service user's last login") + if rowsAffected, err := s.ServiceStore.UpdateLastLogin(ctx, serviceUser.Id, lastLogin); err != nil { + statusCode = http.StatusInternalServerError + resp.Message = err.Error() + } else { + log.Println("Rows updated:", rowsAffected) + statusCode = http.StatusOK + resp.Data = append(resp.Data, myToken) + resp.Message = "Successful" + } } } } diff --git a/internal/store/mock/service_store.go b/internal/store/mock/service_store.go index 90416ad..c19a71b 100644 --- a/internal/store/mock/service_store.go +++ b/internal/store/mock/service_store.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sync" + "time" "git.kundeng.us/phoenix/textsender-models/tx0/user" "github.com/google/uuid" @@ -101,3 +102,24 @@ func (m *MockServiceUserStore) GetWithId(ctx context.Context, id uuid.UUID) (*us return nil, nil } + +func (m *MockServiceUserStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.Error != nil { + return 0, m.Error + } + + serviceUser, exists := m.ServiceUsers[id] + if !exists { + return 0, errors.New("Service user not found") + } + + serviceUser.LastLogin = &lastLogin + + m.ServiceUsers[id] = serviceUser + m.ServiceUsersByUsername[serviceUser.Username] = serviceUser + + return 1, nil +} diff --git a/internal/store/mock/user_store.go b/internal/store/mock/user_store.go index 3164eb1..3380592 100644 --- a/internal/store/mock/user_store.go +++ b/internal/store/mock/user_store.go @@ -136,3 +136,24 @@ func (m *MockUserStore) UpdatePassword(ctx context.Context, id uuid.UUID, passwo return 1, nil } + +func (m *MockUserStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.Error != nil { + return 0, m.Error + } + + user, exists := m.Users[id] + if !exists { + return 0, errors.New("User not found") + } + + user.LastLogin = &lastLogin + + m.Users[id] = user + m.UsersByUsername[user.Username] = user + + return 1, nil +} diff --git a/internal/store/service.go b/internal/store/service.go index 274259b..f61d280 100644 --- a/internal/store/service.go +++ b/internal/store/service.go @@ -3,6 +3,7 @@ package store import ( "context" "fmt" + "time" "git.kundeng.us/phoenix/textsender-models/tx0/user" "github.com/google/uuid" @@ -10,11 +11,13 @@ import ( "github.com/jackc/pgx/v5/pgxpool" ) +// TODO: Rename this to ServiceUserStore type ServiceStore interface { CheckWithUsername(ctx context.Context, username string) (bool, error) GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error) GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) Create(ctx context.Context, serviceUser *user.ServiceUser) error + UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) } type PGServiceStore struct { @@ -81,3 +84,12 @@ func (s *PGServiceStore) Create(ctx context.Context, serviceUser *user.ServiceUs &serviceUser.Id, &serviceUser.Created, ) } + +func (s *PGServiceStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { + query := `UPDATE service_users SET last_login = $1 WHERE id = $2` + if affected, err := s.db.Exec(ctx, query, lastLogin, id); err != nil { + return 0, err + } else { + return affected.RowsAffected(), nil + } +} diff --git a/internal/store/user.go b/internal/store/user.go index ecba5e5..e06197e 100644 --- a/internal/store/user.go +++ b/internal/store/user.go @@ -3,6 +3,7 @@ package store import ( "context" "fmt" + "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" @@ -18,6 +19,7 @@ type UserStore interface { GetAllUsers(ctx context.Context) ([]*user.User, error) UserExists(ctx context.Context, username string) (bool, error) UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error) + UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) } type PGUserStore struct { @@ -123,3 +125,12 @@ func (s *PGUserStore) UpdatePassword(ctx context.Context, id uuid.UUID, password return affected.RowsAffected(), nil } } + +func (s *PGUserStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { + query := `UPDATE users SET last_login = $1 WHERE id = $2` + if affected, err := s.db.Exec(ctx, query, lastLogin, id); err != nil { + return 0, err + } else { + return affected.RowsAffected(), nil + } +} diff --git a/migrations/schema.sql b/migrations/schema.sql index d6e84ca..fe980ea 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -8,12 +8,14 @@ CREATE TABLE users ( phone_number TEXT NOT NULL, username TEXT NOT NULL, password TEXT NOT NULL, - created timestamptz DEFAULT now() + created timestamptz DEFAULT now(), + last_login timestamptz NULL ); CREATE TABLE service_users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username TEXT NOT NULL, passphrase TEXT NOT NULL, - created timestamptz DEFAULT now() + created timestamptz DEFAULT now(), + last_login timestamptz NULL );