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 }