package db import ( "me-fit/types" "me-fit/utils" "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 } func NewSession(id string, userId uuid.UUID, createdAt time.Time) *Session { return &Session{ Id: id, UserId: userId, CreatedAt: createdAt, } } type Token struct { UserId uuid.UUID Token string Type string CreatedAt time.Time ExpiresAt time.Time } 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 AuthDb interface { InsertUser(user *User) error UpdateUser(user *User) error GetUser(email string) (*User, error) GetUserById(userId uuid.UUID) (*User, error) DeleteUser(userId uuid.UUID) error InsertEmailVerificationToken(userId uuid.UUID, token string) error InsertForgotPasswordToken(email string, token string) error GetEmailVerificationToken(userId uuid.UUID) (string, error) GetToken(token string) (*Token, error) DeleteToken(token string) error VerifyEmail(token string) error InsertSession(session *Session) error GetSession(sessionId string) (*Session, error) DeleteSession(sessionId string) error DeleteOldSessions(userId uuid.UUID) error } type AuthDbSqlite struct { db *sql.DB } func NewAuthDbSqlite(db *sql.DB) *AuthDbSqlite { return &AuthDbSqlite{db: db} } func (db AuthDbSqlite) InsertUser(user *User) error { _, err := db.db.Exec(` INSERT INTO user (user_uuid, 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 } utils.LogError("SQL error InsertUser", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) UpdateUser(user *User) error { _, err := db.db.Exec(` UPDATE user SET email_verified = ?, email_verified_at = ?, password = ? WHERE user_uuid = ?`, user.EmailVerified, user.EmailVerifiedAt, user.Password, user.Id) if err != nil { utils.LogError("SQL error UpdateUser", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) GetUser(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_uuid, 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 { utils.LogError("SQL error GetUser", err) return nil, types.ErrInternal } } return NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil } func (db AuthDbSqlite) GetUserById(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_uuid = ?`, userId).Scan(&email, &emailVerified, &emailVerifiedAt, &password, &salt, &createdAt) if err != nil { if err == sql.ErrNoRows { return nil, ErrNotFound } else { utils.LogError("SQL error GetUser", err) return nil, types.ErrInternal } } return NewUser(userId, email, emailVerified, emailVerifiedAt, isAdmin, password, salt, createdAt), nil } func (db AuthDbSqlite) DeleteUser(userId uuid.UUID) error { tx, err := db.db.Begin() if err != nil { utils.LogError("Could not start transaction", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM workout WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() utils.LogError("Could not delete workouts", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM user_token WHERE user_uuid = ?", userId) if err != nil { _ = tx.Rollback() utils.LogError("Could not delete user tokens", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM session WHERE user_uuid = ?", userId) if err != nil { _ = tx.Rollback() utils.LogError("Could not delete sessions", err) return types.ErrInternal } _, err = tx.Exec("DELETE FROM user WHERE user_uuid = ?", userId) if err != nil { _ = tx.Rollback() utils.LogError("Could not delete user", err) return types.ErrInternal } err = tx.Commit() if err != nil { utils.LogError("Could not commit transaction", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) InsertEmailVerificationToken(userId uuid.UUID, token string) error { _, err := db.db.Exec(` INSERT INTO user_token (user_uuid, type, token, created_at) VALUES (?, 'email_verify', ?, datetime())`, userId, token) if err != nil { utils.LogError("Could not insert token", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) GetEmailVerificationToken(userId uuid.UUID) (string, error) { var token string err := db.db.QueryRow(` SELECT token FROM user_token WHERE user_uuid = ? AND type = 'email_verify'`, userId).Scan(&token) if err != nil && err != sql.ErrNoRows { utils.LogError("Could not get token", err) return "", types.ErrInternal } return token, nil } func (db AuthDbSqlite) 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_uuid, type, created_at, expires_at FROM user_token WHERE token = ? AND type = 'email_verify'`, token).Scan(&userId, &tokenType, &createdAtStr, &expiresAtStr) if err != nil && err != sql.ErrNoRows { utils.LogError("Could not get token", err) return nil, types.ErrInternal } createdAt, err = time.Parse(time.RFC3339, createdAtStr) if err != nil { utils.LogError("Could not parse token.created_at", err) return nil, types.ErrInternal } expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) if err != nil { utils.LogError("Could not parse token.expires_at", err) return nil, types.ErrInternal } return NewToken(userId, token, tokenType, createdAt, expiresAt), nil } func (db AuthDbSqlite) DeleteToken(token string) error { _, err := db.db.Exec("DELETE FROM user_token WHERE token = ?", token) if err != nil { utils.LogError("Could not delete token", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) InsertSession(session *Session) error { _, err := db.db.Exec(` INSERT INTO session (session_id, user_uuid, created_at) VALUES (?, ?, ?)`, session.Id, session.UserId, session.CreatedAt) if err != nil { utils.LogError("Could not insert new session", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) GetSession(sessionId string) (*Session, error) { var ( userId uuid.UUID sessionCreatedAt time.Time ) err := db.db.QueryRow(` SELECT u.user_uuid, s.created_at FROM session s INNER JOIN user u ON s.user_uuid = u.user_uuid WHERE session_id = ?`, sessionId).Scan(&userId, &sessionCreatedAt) if err != nil { return nil, ErrNotFound } return NewSession(sessionId, userId, sessionCreatedAt), nil } func (db AuthDbSqlite) 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_uuid = ?", userId) if err != nil { utils.LogError("Could not delete old sessions", err) return types.ErrInternal } return nil } func (db AuthDbSqlite) DeleteSession(sessionId string) error { if sessionId != "" { _, err := db.db.Exec("DELETE FROM session WHERE session_id = ?", sessionId) if err != nil { utils.LogError("Could not delete session", err) return types.ErrInternal } } return nil } func (db AuthDbSqlite) VerifyEmail(token string) error { result, err := db.db.Exec(` UPDATE user SET email_verified = true, email_verified_at = datetime() WHERE user_uuid = ( SELECT user_uuid FROM user_token WHERE type = "email_verify" AND token = ? ); `, token) if err != nil { utils.LogError("Could not update user on verify response", err) return types.ErrInternal } i, err := result.RowsAffected() if err != nil { utils.LogError("Could not get rows affected on verify response", err) return types.ErrInternal } if i == 0 { return types.ErrInternal } return nil } func (db AuthDbSqlite) InsertForgotPasswordToken(email string, token string) error { res, err := db.db.Exec(` INSERT INTO user_token (user_uuid, type, token, created_at, expires_at) SELECT user_uuid, 'password_reset', ?, datetime(), datetime('now', '+15 minute') FROM user WHERE email = ? `, token, email) if err != nil { utils.LogError("Could not insert token", err) return types.ErrInternal } i, err := res.RowsAffected() if err != nil { utils.LogError("Could not get rows affected", err) return types.ErrInternal } if i == 0 { return ErrNotFound } return nil }