refactor: save dates as unix timestamps in database

This commit is contained in:
Elias Schneider
2024-10-23 10:02:11 +02:00
parent 0a07344139
commit b39bc4f79a
12 changed files with 135 additions and 24 deletions

View File

@@ -2,7 +2,9 @@ package dto
import (
"errors"
"github.com/stonith404/pocket-id/backend/internal/model/types"
"reflect"
"time"
)
// MapStructList maps a list of source structs to a list of destination structs
@@ -95,9 +97,20 @@ func mapStructInternal(sourceVal reflect.Value, destVal reflect.Value) error {
if err := mapStructInternal(sourceField, destField); err != nil {
return err
}
} else {
// Type switch for specific type conversions
switch sourceField.Interface().(type) {
case datatype.DateTime:
// Convert datatype.DateTime to time.Time
if sourceField.Type() == reflect.TypeOf(datatype.DateTime{}) && destField.Type() == reflect.TypeOf(time.Time{}) {
dateValue := sourceField.Interface().(datatype.DateTime)
destField.Set(reflect.ValueOf(dateValue.ToTime()))
}
}
}
}
}
return nil
}

View File

@@ -4,7 +4,6 @@ import (
"github.com/go-co-op/gocron/v2"
"github.com/google/uuid"
"github.com/stonith404/pocket-id/backend/internal/model"
"github.com/stonith404/pocket-id/backend/internal/utils"
"gorm.io/gorm"
"log"
"time"
@@ -30,22 +29,22 @@ type Jobs struct {
// ClearWebauthnSessions deletes WebAuthn sessions that have expired
func (j *Jobs) clearWebauthnSessions() error {
return j.db.Delete(&model.WebauthnSession{}, "expires_at < ?", utils.FormatDateForDb(time.Now())).Error
return j.db.Delete(&model.WebauthnSession{}, "expires_at < ?", time.Now().Unix()).Error
}
// ClearOneTimeAccessTokens deletes one-time access tokens that have expired
func (j *Jobs) clearOneTimeAccessTokens() error {
return j.db.Debug().Delete(&model.OneTimeAccessToken{}, "expires_at < ?", utils.FormatDateForDb(time.Now())).Error
return j.db.Debug().Delete(&model.OneTimeAccessToken{}, "expires_at < ?", time.Now().Unix()).Error
}
// ClearOidcAuthorizationCodes deletes OIDC authorization codes that have expired
func (j *Jobs) clearOidcAuthorizationCodes() error {
return j.db.Delete(&model.OidcAuthorizationCode{}, "expires_at < ?", utils.FormatDateForDb(time.Now())).Error
return j.db.Delete(&model.OidcAuthorizationCode{}, "expires_at < ?", time.Now().Unix()).Error
}
// ClearAuditLogs deletes audit logs older than 90 days
func (j *Jobs) clearAuditLogs() error {
return j.db.Delete(&model.AuditLog{}, "created_at < ?", utils.FormatDateForDb(time.Now().AddDate(0, 0, -90))).Error
return j.db.Delete(&model.AuditLog{}, "created_at < ?", time.Now().AddDate(0, 0, -90).Unix()).Error
}
func registerJob(scheduler gocron.Scheduler, name string, interval string, job func() error) {

View File

@@ -2,6 +2,7 @@ package model
import (
"github.com/google/uuid"
model "github.com/stonith404/pocket-id/backend/internal/model/types"
"gorm.io/gorm"
"time"
)
@@ -9,12 +10,13 @@ import (
// Base contains common columns for all tables.
type Base struct {
ID string `gorm:"primaryKey;not null"`
CreatedAt time.Time
CreatedAt model.DateTime
}
func (b *Base) BeforeCreate(_ *gorm.DB) (err error) {
if b.ID == "" {
b.ID = uuid.New().String()
}
b.CreatedAt = model.DateTime(time.Now())
return
}

View File

@@ -4,8 +4,8 @@ import (
"database/sql/driver"
"encoding/json"
"errors"
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
"gorm.io/gorm"
"time"
)
type UserAuthorizedOidcClient struct {
@@ -23,7 +23,7 @@ type OidcAuthorizationCode struct {
Code string
Scope string
Nonce string
ExpiresAt time.Time
ExpiresAt datatype.DateTime
UserID string
User User

View File

@@ -0,0 +1,47 @@
package datatype
import (
"database/sql/driver"
"time"
)
// DateTime custom type for time.Time to store date as unix timestamp in the database
type DateTime time.Time
func (date *DateTime) Scan(value interface{}) (err error) {
*date = DateTime(value.(time.Time))
return
}
func (date DateTime) Value() (driver.Value, error) {
return time.Time(date).Unix(), nil
}
func (date DateTime) UTC() time.Time {
return time.Time(date).UTC()
}
func (date DateTime) ToTime() time.Time {
return time.Time(date)
}
// GormDataType gorm common data type
func (date DateTime) GormDataType() string {
return "date"
}
func (date DateTime) GobEncode() ([]byte, error) {
return time.Time(date).GobEncode()
}
func (date *DateTime) GobDecode(b []byte) error {
return (*time.Time)(date).GobDecode(b)
}
func (date DateTime) MarshalJSON() ([]byte, error) {
return time.Time(date).MarshalJSON()
}
func (date *DateTime) UnmarshalJSON(b []byte) error {
return (*time.Time)(date).UnmarshalJSON(b)
}

View File

@@ -3,7 +3,7 @@ package model
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"time"
"github.com/stonith404/pocket-id/backend/internal/model/types"
)
type User struct {
@@ -61,7 +61,7 @@ func (u User) WebAuthnCredentialDescriptors() (descriptors []protocol.Credential
type OneTimeAccessToken struct {
Base
Token string
ExpiresAt time.Time
ExpiresAt datatype.DateTime
UserID string
User User

View File

@@ -6,6 +6,7 @@ import (
"github.com/stonith404/pocket-id/backend/internal/common"
"github.com/stonith404/pocket-id/backend/internal/dto"
"github.com/stonith404/pocket-id/backend/internal/model"
datatype "github.com/stonith404/pocket-id/backend/internal/model/types"
"github.com/stonith404/pocket-id/backend/internal/utils"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
@@ -115,7 +116,7 @@ func (s *OidcService) CreateTokens(code, grantType, clientID, clientSecret strin
return "", "", common.ErrOidcInvalidAuthorizationCode
}
if authorizationCodeMetaData.ClientID != clientID && authorizationCodeMetaData.ExpiresAt.Before(time.Now()) {
if authorizationCodeMetaData.ClientID != clientID && authorizationCodeMetaData.ExpiresAt.ToTime().Before(time.Now()) {
return "", "", common.ErrOidcInvalidAuthorizationCode
}
@@ -350,7 +351,7 @@ func (s *OidcService) createAuthorizationCode(clientID string, userID string, sc
}
oidcAuthorizationCode := model.OidcAuthorizationCode{
ExpiresAt: time.Now().Add(15 * time.Minute),
ExpiresAt: datatype.DateTime(time.Now().Add(15 * time.Minute)),
Code: randomString,
ClientID: clientID,
UserID: userID,

View File

@@ -6,6 +6,7 @@ import (
"encoding/base64"
"fmt"
"github.com/fxamacker/cbor/v2"
"github.com/stonith404/pocket-id/backend/internal/model/types"
"log"
"os"
"time"
@@ -111,7 +112,7 @@ func (s *TestService) SeedDatabase() error {
Code: "auth-code",
Scope: "openid profile",
Nonce: "nonce",
ExpiresAt: time.Now().Add(1 * time.Hour),
ExpiresAt: datatype.DateTime(time.Now().Add(1 * time.Hour)),
UserID: users[0].ID,
ClientID: oidcClients[0].ID,
}
@@ -121,7 +122,7 @@ func (s *TestService) SeedDatabase() error {
accessToken := model.OneTimeAccessToken{
Token: "one-time-token",
ExpiresAt: time.Now().Add(1 * time.Hour),
ExpiresAt: datatype.DateTime(time.Now().Add(1 * time.Hour)),
UserID: users[0].ID,
}
if err := tx.Create(&accessToken).Error; err != nil {

View File

@@ -5,6 +5,7 @@ import (
"github.com/stonith404/pocket-id/backend/internal/common"
"github.com/stonith404/pocket-id/backend/internal/dto"
"github.com/stonith404/pocket-id/backend/internal/model"
"github.com/stonith404/pocket-id/backend/internal/model/types"
"github.com/stonith404/pocket-id/backend/internal/utils"
"gorm.io/gorm"
"time"
@@ -95,7 +96,7 @@ func (s *UserService) CreateOneTimeAccessToken(userID string, expiresAt time.Tim
oneTimeAccessToken := model.OneTimeAccessToken{
UserID: userID,
ExpiresAt: expiresAt,
ExpiresAt: datatype.DateTime(expiresAt),
Token: randomString,
}
@@ -108,7 +109,7 @@ func (s *UserService) CreateOneTimeAccessToken(userID string, expiresAt time.Tim
func (s *UserService) ExchangeOneTimeAccessToken(token string) (model.User, string, error) {
var oneTimeAccessToken model.OneTimeAccessToken
if err := s.db.Where("token = ? AND expires_at > ?", token, utils.FormatDateForDb(time.Now())).Preload("User").First(&oneTimeAccessToken).Error; err != nil {
if err := s.db.Where("token = ? AND expires_at > ?", token, time.Now().Unix()).Preload("User").First(&oneTimeAccessToken).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return model.User{}, "", common.ErrTokenInvalidOrExpired
}

View File

@@ -1,8 +0,0 @@
package utils
import "time"
func FormatDateForDb(time time.Time) string {
const layout = "2006-01-02 15:04:05.000-07:00"
return time.Format(layout)
}

View File

@@ -0,0 +1,28 @@
-- Convert the Unix timestamps back to DATETIME format
UPDATE user_groups
SET created_at = datetime(created_at, 'unixepoch');
UPDATE users
SET created_at = datetime(created_at, 'unixepoch');
UPDATE audit_logs
SET created_at = datetime(created_at, 'unixepoch');
UPDATE oidc_authorization_codes
SET created_at = datetime(created_at, 'unixepoch'),
expires_at = datetime(expires_at, 'unixepoch');
UPDATE oidc_clients
SET created_at = datetime(created_at, 'unixepoch');
UPDATE one_time_access_tokens
SET created_at = datetime(created_at, 'unixepoch'),
expires_at = datetime(expires_at, 'unixepoch');
UPDATE webauthn_credentials
SET created_at = datetime(created_at, 'unixepoch');
UPDATE webauthn_sessions
SET created_at = datetime(created_at, 'unixepoch'),
expires_at = datetime(expires_at, 'unixepoch');

View File

@@ -0,0 +1,27 @@
-- Convert the DATETIME fields to Unix timestamps (in seconds)
UPDATE user_groups
SET created_at = strftime('%s', created_at);
UPDATE users
SET created_at = strftime('%s', created_at);
UPDATE audit_logs
SET created_at = strftime('%s', created_at);
UPDATE oidc_authorization_codes
SET created_at = strftime('%s', created_at),
expires_at = strftime('%s', expires_at);
UPDATE oidc_clients
SET created_at = strftime('%s', created_at);
UPDATE one_time_access_tokens
SET created_at = strftime('%s', created_at),
expires_at = strftime('%s', expires_at);
UPDATE webauthn_credentials
SET created_at = strftime('%s', created_at);
UPDATE webauthn_sessions
SET created_at = strftime('%s', created_at),
expires_at = strftime('%s', expires_at);