Why do double the work eh?
This commit is contained in:
parent
3e7d43e068
commit
4f0834c3a0
11 changed files with 213 additions and 33 deletions
74
README.md
74
README.md
|
@ -1,6 +1,74 @@
|
|||
# sudoscientist
|
||||
|
||||
Sudo Scientist blog
|
||||
## sudoscientist blog
|
||||
|
||||
API_PORT=8080 DBHOST="postgres.localhost" DBPORT="5432" DBUSER="asara" DBPW="PW" DBNAME="sudoscientist" \
|
||||
go run main.go
|
||||
### Setup
|
||||
|
||||
Install steps are for Debian 9 (stretch)
|
||||
|
||||
1. Install docker-ce
|
||||
```
|
||||
# stolen from https://docs.docker.com/install/linux/docker-ce/debian/
|
||||
sudo apt-get remove docker docker-engine docker.io containerd runc
|
||||
sudo apt-get update
|
||||
sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
|
||||
# verify the key's fingerprint
|
||||
# ----------
|
||||
sudo apt-key fingerprint 0EBFCD88
|
||||
pub 4096R/0EBFCD88 2017-02-22
|
||||
Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88
|
||||
Docker Release (CE deb) <docker@docker.com>
|
||||
sub 4096R/F273FCD8 2017-02-22
|
||||
# ----------
|
||||
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
|
||||
sudo apt-get update
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io
|
||||
```
|
||||
|
||||
2. Install golang 1.11
|
||||
```
|
||||
# stretch doesn't have the latest golang so we install backports
|
||||
sudo add-apt-repository "deb http://deb.debian.org/debian stretch-backports main"
|
||||
sudo apt-get update
|
||||
sudo apt-get -t stretch-backports install golang
|
||||
# set the gopath manually for the rest of the setup
|
||||
export GOPATH=${HOME}/go
|
||||
```
|
||||
|
||||
3. Clone repo and configure the settings
|
||||
```
|
||||
mkdir -p ${GOPATH}/src/git.minhas.io/asara
|
||||
cd ${GOPATH}/src/git.minhas.io/asara
|
||||
git clone https://git.minhas.io/asara/sudoscientist
|
||||
# iterate through the environment files in the settings directory and set them appropriately
|
||||
# make sure the extension is .env (db.env, secrets.env, website.env... etc.)
|
||||
```
|
||||
|
||||
4. Configure docker postgres for testing
|
||||
```
|
||||
# make sure your user is in the docker group
|
||||
sudo usermod -aG docker $(whoami)
|
||||
# make sure you have some postgres client installed
|
||||
sudo apt-get install postgres-client
|
||||
docker pull postgres
|
||||
docker run --name sudosci-db -e POSTGRES_PASWORD=${DB_ADMIN_PW} -d postgres # please set the db admin pw manually
|
||||
# Initalize the postgres DB
|
||||
cd ${GOPATH}/src/git.minhas.io/asara/sudoscientist
|
||||
for i in settings/*; do source $i; done
|
||||
export DB_HOST=$(docker inspect -f "{{ .NetworkSettings.IPAddress }}" sudosci-db)
|
||||
psql -d postgres -U postgres -h ${DB_HOST} << EOF
|
||||
CREATE DATABASE ${DB_NAME};
|
||||
CREATE USER ${DB_USER} WITH ENCRYPTED PASSWORD '${DB_PW}';
|
||||
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
|
||||
EOF
|
||||
```
|
||||
|
||||
5. Run the application!
|
||||
```
|
||||
cd ${GOPATH}/src/git.minhas.io/asara/sudoscientist
|
||||
for i in settings/*; do source $i; done
|
||||
export DB_HOST=$(docker inspect -f "{{ .NetworkSettings.IPAddress }}" sudosci-db)
|
||||
go get
|
||||
go run main.go
|
||||
```
|
||||
|
|
16
db_reset.sh
Executable file
16
db_reset.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
docker stop sudosci-db
|
||||
docker rm sudosci-db
|
||||
export DB_ADMIN_PW=test
|
||||
docker run --name sudosci-db -e POSTGRES_PASWORD=${DB_ADMIN_PW} -d postgres
|
||||
cd ${GOPATH}/src/git.minhas.io/asara/sudoscientist
|
||||
for i in settings/*; do source $i; done
|
||||
export DB_HOST=$(docker inspect -f "{{ .NetworkSettings.IPAddress }}" sudosci-db)
|
||||
# instance takes a while to setup and configure postgis
|
||||
sleep 10
|
||||
psql -d postgres -U postgres -h ${DB_HOST} << EOF
|
||||
CREATE DATABASE ${DB_NAME};
|
||||
CREATE USER ${DB_USER} WITH ENCRYPTED PASSWORD '${DB_PW}';
|
||||
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
|
||||
EOF
|
||||
echo Done
|
|
@ -1,16 +1,18 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.minhas.io/asara/sudoscientist/packages/users"
|
||||
"github.com/badoux/checkmail"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"github.com/go-chi/render"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -18,13 +20,32 @@ var (
|
|||
TokenAuth *jwtauth.JWTAuth
|
||||
)
|
||||
|
||||
type Credentials struct {
|
||||
type RegistrationError struct {
|
||||
Message string `json:"error"`
|
||||
}
|
||||
|
||||
type SignUpCredentials struct {
|
||||
Username string `json:"username", db:"username"`
|
||||
Email string `json:"email", db:"email"`
|
||||
Password string `json:"password", db:"password"`
|
||||
}
|
||||
|
||||
type SignInCredentials struct {
|
||||
Username string `json:"username", db:"username"`
|
||||
Password string `json:"password", db:"password"`
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
Username string `json:"username"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
type ReturnToken struct {
|
||||
JWT string `json:"jwt"`
|
||||
}
|
||||
|
||||
func Init() {
|
||||
DB.Exec("CREATE TABLE IF NOT EXISTS users (username text primary key, password text, admin boolean);" )
|
||||
DB.Exec("CREATE TABLE IF NOT EXISTS users (username text primary key, email text, password text, admin boolean);")
|
||||
}
|
||||
|
||||
func Routes() *chi.Mux {
|
||||
|
@ -35,26 +56,66 @@ func Routes() *chi.Mux {
|
|||
}
|
||||
|
||||
func signup(w http.ResponseWriter, r *http.Request) {
|
||||
creds := &Credentials{}
|
||||
returnError := RegistrationError{}
|
||||
creds := &SignUpCredentials{}
|
||||
err := json.NewDecoder(r.Body).Decode(creds)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
if creds.Username == "" {
|
||||
returnError.Message = "username is required"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, returnError)
|
||||
return
|
||||
}
|
||||
if creds.Password == "" {
|
||||
returnError.Message = "password is required"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, returnError)
|
||||
return
|
||||
}
|
||||
if creds.Email == "" {
|
||||
returnError.Message = "email is required"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, returnError)
|
||||
return
|
||||
}
|
||||
err = checkmail.ValidateFormat(creds.Email)
|
||||
if err != nil {
|
||||
returnError.Message = "email not valid"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
render.JSON(w, r, returnError)
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(creds.Password), 10)
|
||||
s := `INSERT INTO users (username, password, admin)
|
||||
VALUES ($1, $2, $3)`
|
||||
if _, err = DB.Exec(s, creds.Username, string(hashedPassword), false); err != nil {
|
||||
s := `INSERT INTO users (username, email, password, admin)
|
||||
VALUES ($1, $2, $3, $4)`
|
||||
if _, err = DB.Exec(s, creds.Username, creds.Email, string(hashedPassword), false); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
users.CreateProfile(creds.Username)
|
||||
users.CreateProfile(creds.Username, creds.Email)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
expirationTime := time.Now().Add(6 * time.Hour)
|
||||
claims := &Claims{
|
||||
Username: creds.Username,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: expirationTime.Unix(),
|
||||
},
|
||||
}
|
||||
_, tokenString, _ := TokenAuth.Encode(claims)
|
||||
token := ReturnToken{
|
||||
JWT: tokenString,
|
||||
}
|
||||
render.JSON(w, r, token)
|
||||
}
|
||||
|
||||
func signin(w http.ResponseWriter, r *http.Request) {
|
||||
creds := &Credentials{}
|
||||
creds := &SignInCredentials{}
|
||||
err := json.NewDecoder(r.Body).Decode(creds)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
@ -65,7 +126,7 @@ func signin(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
storedCreds := &Credentials{}
|
||||
storedCreds := &SignInCredentials{}
|
||||
err = result.Scan(&storedCreds.Password)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -78,9 +139,17 @@ func signin(w http.ResponseWriter, r *http.Request) {
|
|||
if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(creds.Password)); err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
_, tokenString, _ := TokenAuth.Encode(jwt.MapClaims{
|
||||
"username": creds.Username,
|
||||
})
|
||||
w.WriteHeader(http.StatusOK)
|
||||
render.JSON(w, r, tokenString)
|
||||
expirationTime := time.Now().Add(5 * time.Hour)
|
||||
claims := &Claims{
|
||||
Username: creds.Username,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: expirationTime.Unix(),
|
||||
},
|
||||
}
|
||||
_, tokenString, _ := TokenAuth.Encode(claims)
|
||||
token := ReturnToken{
|
||||
JWT: tokenString,
|
||||
}
|
||||
render.JSON(w, r, token)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func NewDB() (*sql.DB, error) {
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DBHOST"), os.Getenv("DBPORT"), os.Getenv("DBUSER"), os.Getenv("DBPW"), os.Getenv("DBNAME"))
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PW"), os.Getenv("DB_NAME"), os.Getenv("DB_SSL"))
|
||||
db, err := sql.Open("postgres", psqlInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"github.com/go-chi/render"
|
||||
|
@ -15,9 +15,10 @@ var (
|
|||
)
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username,string"`
|
||||
Country string `json:"country,string"`
|
||||
Bio string `json:"bio,string"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Country string `json:"country"`
|
||||
Bio string `json:"bio"`
|
||||
}
|
||||
|
||||
func Init() {
|
||||
|
@ -25,6 +26,7 @@ func Init() {
|
|||
CREATE TABLE IF NOT EXISTS user_profiles
|
||||
(id SERIAL PRIMARY KEY,
|
||||
username text REFERENCES users (username),
|
||||
email text,
|
||||
country text,
|
||||
bio text)`
|
||||
DB.Exec(dbCreateStatement)
|
||||
|
@ -35,29 +37,39 @@ func Routes() *chi.Mux {
|
|||
r.Group(func(r chi.Router) {
|
||||
r.Use(jwtauth.Verifier(TokenAuth))
|
||||
r.Use(jwtauth.Authenticator)
|
||||
r.Get("/{username}", getUser)
|
||||
r.Put("/{username}", updateUser)
|
||||
})
|
||||
r.Post("/{username}", updateUser)
|
||||
r.Get("/{username}", getUser)
|
||||
return r
|
||||
}
|
||||
|
||||
func getUser(w http.ResponseWriter, r *http.Request) {
|
||||
username := chi.URLParam(r, "username")
|
||||
user := User{
|
||||
Username: username,
|
||||
result := DB.QueryRow("SELECT username, email, country, bio FROM user_profiles WHERE username=$1", username)
|
||||
user := User{}
|
||||
err := result.Scan(&user.Username, &user.Email, &user.Country, &user.Bio)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, user)
|
||||
}
|
||||
|
||||
func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||
_, claims, _ := jwtauth.FromContext(r.Context())
|
||||
username := claims["username"]
|
||||
searchname := chi.URLParam(r, "username")
|
||||
if username != searchname {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateProfile(username string) {
|
||||
fmt.Println("CREATING PROFILE")
|
||||
func CreateProfile(username string, email string) {
|
||||
blankProfileStatement := `
|
||||
INSERT INTO user_profiles (username, country, bio)
|
||||
VALUES ($1, $2, $3)`
|
||||
DB.Exec(blankProfileStatement, username, "", "")
|
||||
fmt.Println("CREATED")
|
||||
INSERT INTO user_profiles (username, email, country, bio)
|
||||
VALUES ($1, $2, $3, $4)`
|
||||
DB.Exec(blankProfileStatement, username, email, "", "")
|
||||
}
|
||||
|
|
4
settings/db.env
Normal file
4
settings/db.env
Normal file
|
@ -0,0 +1,4 @@
|
|||
export DB_PORT="5432"
|
||||
export DB_USER="sudosci"
|
||||
export DB_NAME="sudosci"
|
||||
export DB_SSL="disable"
|
4
settings/db.env-sample
Normal file
4
settings/db.env-sample
Normal file
|
@ -0,0 +1,4 @@
|
|||
export DB_PORT="5432"
|
||||
export DB_USER="sudosci"
|
||||
export DB_NAME="sudosci"
|
||||
export DB_SSL="disable"
|
1
settings/secrets.env
Normal file
1
settings/secrets.env
Normal file
|
@ -0,0 +1 @@
|
|||
export DB_PW="CHANGEME"
|
1
settings/secrets.env-sample
Normal file
1
settings/secrets.env-sample
Normal file
|
@ -0,0 +1 @@
|
|||
export DB_PW="CHANGEME"
|
2
settings/website.env
Normal file
2
settings/website.env
Normal file
|
@ -0,0 +1,2 @@
|
|||
export API_PORT="8080"
|
||||
export JWT_SECRET="CHANGEMEALSO"
|
2
settings/website.env-sample
Normal file
2
settings/website.env-sample
Normal file
|
@ -0,0 +1,2 @@
|
|||
export API_PORT="8080"
|
||||
export JWT_SECRET="CHANGEMEALSO"
|
Reference in a new issue