Some checks failed
Build Docker Image / Build-Docker-Image (push) Failing after 40s
473 lines
11 KiB
Go
473 lines
11 KiB
Go
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")
|
|
ErrAlreadyExists = errors.New("row 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
|
|
SessionId string
|
|
Token string
|
|
Type TokenType
|
|
CreatedAt time.Time
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
type TokenType string
|
|
|
|
var (
|
|
TokenTypeEmailVerify TokenType = "email_verify"
|
|
TokenTypePasswordReset TokenType = "password_reset"
|
|
TokenTypeCsrf TokenType = "csrf"
|
|
)
|
|
|
|
func NewToken(userId uuid.UUID, sessionId string, token string, tokenType TokenType, createdAt time.Time, expiresAt time.Time) *Token {
|
|
return &Token{
|
|
UserId: userId,
|
|
SessionId: sessionId,
|
|
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 TokenType) ([]*Token, error)
|
|
GetTokensBySessionIdAndType(sessionId string, tokenType TokenType) ([]*Token, error)
|
|
DeleteToken(token string) error
|
|
|
|
InsertSession(session *Session) error
|
|
GetSession(sessionId string) (*Session, error)
|
|
GetSessions(userId uuid.UUID) ([]*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 ErrAlreadyExists
|
|
}
|
|
|
|
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 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 token (user_id, session_id, type, token, created_at, expires_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`, token.UserId, token.SessionId, 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
|
|
sessionId string
|
|
tokenType TokenType
|
|
createdAtStr string
|
|
expiresAtStr string
|
|
createdAt time.Time
|
|
expiresAt time.Time
|
|
)
|
|
|
|
err := db.db.QueryRow(`
|
|
SELECT user_id, session_id, type, created_at, expires_at
|
|
FROM token
|
|
WHERE token = ?`, token).Scan(&userId, &sessionId, &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, sessionId, token, tokenType, createdAt, expiresAt), nil
|
|
}
|
|
|
|
func (db AuthSqlite) GetTokensByUserIdAndType(userId uuid.UUID, tokenType TokenType) ([]*Token, error) {
|
|
|
|
query, err := db.db.Query(`
|
|
SELECT token, created_at, expires_at
|
|
FROM token
|
|
WHERE user_id = ?
|
|
AND type = ?`, userId, tokenType)
|
|
|
|
if err != nil {
|
|
log.Error("Could not get token: %v", err)
|
|
return nil, types.ErrInternal
|
|
}
|
|
|
|
return getTokensFromQuery(query, userId, "", tokenType)
|
|
}
|
|
|
|
func (db AuthSqlite) GetTokensBySessionIdAndType(sessionId string, tokenType TokenType) ([]*Token, error) {
|
|
|
|
query, err := db.db.Query(`
|
|
SELECT token, created_at, expires_at
|
|
FROM token
|
|
WHERE session_id = ?
|
|
AND type = ?`, sessionId, tokenType)
|
|
|
|
if err != nil {
|
|
log.Error("Could not get token: %v", err)
|
|
return nil, types.ErrInternal
|
|
}
|
|
|
|
return getTokensFromQuery(query, uuid.Nil, sessionId, tokenType)
|
|
}
|
|
|
|
func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tokenType TokenType) ([]*Token, error) {
|
|
var tokens []*Token
|
|
|
|
hasRows := false
|
|
for query.Next() {
|
|
hasRows = true
|
|
|
|
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, sessionId, token, tokenType, createdAt, expiresAt))
|
|
}
|
|
|
|
if !hasRows {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return tokens, nil
|
|
}
|
|
|
|
func (db AuthSqlite) DeleteToken(token string) error {
|
|
_, err := db.db.Exec("DELETE FROM 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) GetSessions(userId uuid.UUID) ([]*Session, error) {
|
|
|
|
sessions, err := db.db.Query(`
|
|
SELECT session_id, created_at, expires_at
|
|
FROM session
|
|
WHERE user_id = ?`, userId)
|
|
if err != nil {
|
|
log.Error("Could not get sessions: %v", err)
|
|
return nil, types.ErrInternal
|
|
}
|
|
|
|
var result []*Session
|
|
|
|
for sessions.Next() {
|
|
var (
|
|
sessionId string
|
|
createdAt time.Time
|
|
expiresAt time.Time
|
|
)
|
|
|
|
sessions.Scan(&sessionId, &createdAt, &expiresAt)
|
|
|
|
session := NewSession(sessionId, userId, createdAt, expiresAt)
|
|
result = append(result, session)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (db AuthSqlite) DeleteOldSessions(userId uuid.UUID) error {
|
|
_, err := db.db.Exec(`
|
|
DELETE FROM session
|
|
WHERE expires_at < datetime('now')
|
|
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
|
|
}
|