github.com/hzck/speedroute@v0.0.0-20201115191102-403b7d0e443f/app.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"log"
     7  	"net/http"
     8  	"os"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/alexedwards/argon2id"
    14  	"github.com/gorilla/mux"
    15  	"github.com/jackc/pgx/v4/pgxpool"
    16  	"github.com/joho/godotenv"
    17  
    18  	model "github.com/hzck/speedroute/model"
    19  )
    20  
    21  // App holds information about the running application.
    22  type App struct {
    23  	Router     *mux.Router
    24  	Dbpool     *pgxpool.Pool
    25  	HashParams *argon2id.Params
    26  }
    27  
    28  // InitLogFile initializes app output to "logfile"
    29  func (a *App) InitLogFile() func() {
    30  	// Creating logfile
    31  	f, err := os.OpenFile("logfile", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    32  	if err != nil {
    33  		panic(err)
    34  	}
    35  	log.SetOutput(f)
    36  	return func() {
    37  		if closeErr := f.Close(); closeErr != nil && err == nil {
    38  			panic(closeErr)
    39  		}
    40  	}
    41  }
    42  
    43  // InitConfigFile reads application configuration from "config.env"
    44  func (a *App) InitConfigFile() {
    45  	// Reading environment variables
    46  	err := godotenv.Load("config.env")
    47  	if err != nil {
    48  		log.Println("Error loading config.env file")
    49  		panic(err)
    50  	}
    51  	a.HashParams = &argon2id.Params{
    52  		Memory:      parseUInt32FromOsEnv("ARGON2ID_MEMORY"),
    53  		Iterations:  parseUInt32FromOsEnv("ARGON2ID_ITERATIONS"),
    54  		Parallelism: parseUInt8FromOsEnv("ARGON2ID_PARALLELISM"),
    55  		SaltLength:  parseUInt32FromOsEnv("ARGON2ID_SALT_LENGTH"),
    56  		KeyLength:   parseUInt32FromOsEnv("ARGON2ID_KEY_LENGTH"),
    57  	}
    58  }
    59  
    60  func parseUInt8FromOsEnv(key string) uint8 {
    61  	val, err := strconv.ParseUint(os.Getenv(key), 10, 8)
    62  	if err != nil {
    63  		log.Printf("Error parsing uint8 from config.env file with key %s\n", key)
    64  		panic(err)
    65  	}
    66  	return uint8(val)
    67  }
    68  
    69  func parseUInt32FromOsEnv(key string) uint32 {
    70  	val, err := strconv.ParseUint(os.Getenv(key), 10, 32)
    71  	if err != nil {
    72  		log.Printf("Error parsing uint32 from config.env file with key %s\n", key)
    73  		panic(err)
    74  	}
    75  	return uint32(val)
    76  }
    77  
    78  // InitDB connects to the database.
    79  func (a *App) InitDB() func() {
    80  	dbConnString := "postgresql://" +
    81  		os.Getenv("DB_USER") + ":" +
    82  		os.Getenv("DB_PASSWORD") + "@" +
    83  		os.Getenv("DB_URL") + ":" +
    84  		os.Getenv("DB_PORT") + "/speedroute?pool_max_conns=10"
    85  	var err error
    86  	a.Dbpool, err = pgxpool.Connect(context.Background(), dbConnString)
    87  	if err != nil {
    88  		log.Println("Unable to connect to database: ", err)
    89  		panic(err)
    90  	}
    91  	log.Println("Successfully connected to the DB")
    92  	return func() {
    93  		a.Dbpool.Close()
    94  	}
    95  }
    96  
    97  // InitRoutes initializes the routes used by the application
    98  func (a *App) InitRoutes() {
    99  	a.Router = mux.NewRouter()
   100  	a.Router.HandleFunc("/account", a.createAccount(a.Dbpool)).Methods("POST")
   101  }
   102  
   103  // Run starts the application.
   104  func (a *App) Run() {
   105  	log.Println("### Server started ###")
   106  	log.Println(http.ListenAndServe(":8001", a.Router))
   107  }
   108  
   109  func (a *App) createAccount(dbpool *pgxpool.Pool) http.HandlerFunc {
   110  	return func(w http.ResponseWriter, r *http.Request) {
   111  		defer r.Body.Close()
   112  		decoder := json.NewDecoder(r.Body)
   113  		newAccount := struct {
   114  			Username string `json:"username"`
   115  			Password string `json:"password"`
   116  		}{}
   117  		err := decoder.Decode(&newAccount)
   118  		match, _ := regexp.MatchString("^[\\w]{2,30}$", newAccount.Username)
   119  		if err != nil || !match || len(newAccount.Password) < 8 {
   120  			w.WriteHeader(http.StatusBadRequest)
   121  			return
   122  		}
   123  		var account model.Account
   124  		account.Username = strings.ToLower(newAccount.Username)
   125  
   126  		//TODO: If username already taken, no need to create hash
   127  		hash, err := argon2id.CreateHash(newAccount.Password, a.HashParams)
   128  		if err != nil {
   129  			log.Println(err)
   130  			w.WriteHeader(http.StatusInternalServerError)
   131  			return
   132  		}
   133  		account.Password = hash
   134  		err = account.CreateAccount(dbpool)
   135  		if err != nil {
   136  			w.WriteHeader(http.StatusConflict)
   137  			return
   138  		}
   139  
   140  		w.WriteHeader(http.StatusCreated)
   141  	}
   142  }