package db import ( "me-fit/log" "me-fit/types" "database/sql" "errors" "strings" "time" "github.com/google/uuid" ) var ( ErrNotFound = errors.New("value not found") ErrUserExists = errors.New("user already exists") ) type User struct { Id uuid.UUID Email string EmailVerified bool EmailVerifiedAt *time.Time IsAdmin bool Password []byte Salt []byte CreateAt time.Time } func NewUser(id uuid.UUID, email string, emailVerified bool, emailVerifiedAt *time.Time, isAdmin bool, password []byte, salt []byte, createAt time.Time) *User { return &User{ Id: id, Email: email, EmailVerified: emailVerified, EmailVerifiedAt: emailVerifiedAt, IsAdmin: isAdmin, Password: password, Salt: salt, CreateAt: createAt, } } type Session struct { Id string UserId uuid.UUID CreatedAt time.Time ExpiresAt time.Time } func NewSession(id string, userId uuid.UUID, createdAt time.Time, expiresAt time.Time) *Session { return &Session{ Id: id, UserId: userId, CreatedAt: createdAt, ExpiresAt: expiresAt, } } type Token struct { UserId uuid.UUID Token string Type string CreatedAt time.Time ExpiresAt time.Time } var ( TokenTypeEmailVerify = "email_verify" TokenTypePasswordReset = "password_reset" ) func NewToken(userId uuid.UUID, token string, tokenType string, createdAt time.Time, expiresAt time.Time) *Token { return &Token{ UserId: userId, Token: token, Type: tokenType, CreatedAt: createdAt, ExpiresAt: expiresAt, } } type Auth interface { InsertUser(user *User) error UpdateUser(user *User) error GetUserByEmail(email string) (*User, error) GetUser(userId uuid.UUID) (*User, error) DeleteUser(userId uuid.UUID) error InsertToken(token *Token) error GetToken(token string) (*Token, error) GetTokensByUserIdAndType(userId uuid.UUID, tokenType string) ([]*Token, error) DeleteToken(token string) error InsertSession(session *Session) error GetSession(sessionId string) (*Session, error) DeleteSession(sessionId string) error DeleteOldSessions(userId uuid.UUID) error } type AuthSqlite struct { db *sql.DB } func NewAuthSqlite(db *sql.DB) *AuthSqlite { return &AuthSqlite{db: db} } func (db AuthSqlite) InsertUser(user *User) error { _, err := db.db.Exec(` INSERT INTO user (user_id, email, email_verified, email_verified_at, is_admin, password, salt, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, user.Id, user.Email, user.EmailVerified, user.EmailVerifiedAt, user.IsAdmin, user.Password, user.Salt, user.CreateAt) if err != nil { if strings.Contains(err.Error(), "email") { return ErrUserExists } log.Error("SQL error InsertUser: %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) UpdateUser(user *User) error { _, err := db.db.Exec(` UPDATE user SET email_verified = ?, email_verified_at = ?, password = ? WHERE user_id = ?`, user.EmailVerified, user.EmailVerifiedAt, user.Password, user.Id) if err != nil { log.Error("SQL error UpdateUser: %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) GetUserByEmail(email string) (*User, error) { var ( userId uuid.UUID emailVerified bool emailVerifiedAt *time.Time isAdmin bool password []byte salt []byte createdAt time.Time ) err := db.db.QueryRow(` SELECT user_id, email_verified, email_verified_at, password, salt, created_at FROM user WHERE email = ?`, email).Scan(&userId, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt) if err != nil { if err == sql.ErrNoRows { return nil, ErrNotFound } else { log.Error("SQL error GetUser: %v", err) return nil, types.ErrInternal } } return NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil } func (db AuthSqlite) GetUser(userId uuid.UUID) (*User, error) { var ( email string emailVerified bool emailVerifiedAt *time.Time isAdmin bool password []byte salt []byte createdAt time.Time ) err := db.db.QueryRow(` SELECT email, email_verified, email_verified_at, password, salt, created_at FROM user WHERE user_id = ?`, userId).Scan(&email, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt) if err != nil { if err == sql.ErrNoRows { return nil, ErrNotFound } else { log.Error("SQL error GetUser %v", err) return nil, types.ErrInternal } } return NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil } func (db AuthSqlite) DeleteUser(userId uuid.UUID) error { tx, err := db.db.Begin() if err != nil { log.Error("Could not start transaction: %v", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM workout WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() log.Error("Could not delete workouts: %v", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM user_token WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() log.Error("Could not delete user tokens: %v", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM session WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() log.Error("Could not delete sessions: %v", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM user WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() log.Error("Could not delete user: %v", err) return types.ErrInternal } err = tx.Commit() if err != nil { log.Error("Could not commit transaction: %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) InsertToken(token *Token) error { _, err := db.db.Exec(` INSERT INTO user_token (user_id, type, token, created_at, expires_at) VALUES (?, ?, ?, ?, ?)`, token.UserId, token.Type, token.Token, token.CreatedAt, token.ExpiresAt) if err != nil { log.Error("Could not insert token: %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) GetToken(token string) (*Token, error) { var ( userId uuid.UUID tokenType string createdAtStr string expiresAtStr string createdAt time.Time expiresAt time.Time ) err := db.db.QueryRow(` SELECT user_id, type, created_at, expires_at FROM user_token WHERE token = ? AND type = 'email_verify'`, token).Scan(&userId, &tokenType, &createdAtStr, &expiresAtStr) if err != nil { if err == sql.ErrNoRows { log.Info("Token '%v' not found", token) return nil, ErrNotFound } else { log.Error("Could not get token: %v", err) return nil, types.ErrInternal } } createdAt, err = time.Parse(time.RFC3339, createdAtStr) if err != nil { log.Error("Could not parse token.created_at: %v", err) return nil, types.ErrInternal } expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) if err != nil { log.Error("Could not parse token.expires_at: %v", err) return nil, types.ErrInternal } return NewToken(userId, token, tokenType, createdAt, expiresAt), nil } func (db AuthSqlite) GetTokensByUserIdAndType(userId uuid.UUID, tokenType string) ([]*Token, error) { query, err := db.db.Query(` SELECT token, created_at, expires_at FROM user_token WHERE user_id = ? AND type = ?`, userId, tokenType) if err != nil { log.Error("Could not get token: %v", err) return nil, types.ErrInternal } var tokens []*Token for query.Next() { var ( token string createdAtStr string expiresAtStr string createdAt time.Time expiresAt time.Time ) err := query.Scan(&token, &createdAtStr, &expiresAtStr) if err != nil { log.Error("Could not scan token: %v", err) return nil, types.ErrInternal } createdAt, err = time.Parse(time.RFC3339, createdAtStr) if err != nil { log.Error("Could not parse token.created_at: %v", err) return nil, types.ErrInternal } expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) if err != nil { log.Error("Could not parse token.expires_at: %v", err) return nil, types.ErrInternal } tokens = append(tokens, NewToken(userId, token, tokenType, createdAt, expiresAt)) } return tokens, nil } func (db AuthSqlite) DeleteToken(token string) error { _, err := db.db.Exec("DELETE FROM user_token WHERE token = ?", token) if err != nil { log.Error("Could not delete token: %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) InsertSession(session *Session) error { _, err := db.db.Exec(` INSERT INTO session (session_id, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)`, session.Id, session.UserId, session.CreatedAt, session.ExpiresAt) if err != nil { log.Error("Could not insert new session %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) GetSession(sessionId string) (*Session, error) { var ( userId uuid.UUID createdAt time.Time expiresAt time.Time ) err := db.db.QueryRow(` SELECT user_id, created_at, expires_at FROM session WHERE session_id = ?`, sessionId).Scan(&userId, &createdAt, &expiresAt) if err != nil { return nil, ErrNotFound } return NewSession(sessionId, userId, createdAt, expiresAt), nil } func (db AuthSqlite) DeleteOldSessions(userId uuid.UUID) error { // Delete old inactive sessions _, err := db.db.Exec("DELETE FROM session WHERE created_at < datetime('now','-8 hours') AND user_id = ?", userId) if err != nil { log.Error("Could not delete old sessions: %v", err) return types.ErrInternal } return nil } func (db AuthSqlite) DeleteSession(sessionId string) error { if sessionId != "" { _, err := db.db.Exec("DELETE FROM session WHERE session_id = ?", sessionId) if err != nil { log.Error("Could not delete session: %v", err) return types.ErrInternal } } return nil }