Makefile, fix env, bug fixes, tests work

main
Gabe Farrell 3 years ago
parent 62dbcd1135
commit 8bf90bea0b

@ -0,0 +1,9 @@
ALLOWED_ORIGINS=*
DB_URL=http://localhost:8000
DB_TABLE=my-db-table
DB_GSI_NAME=gsi1
DB_GSI_ATTR=gsi1pk
AWS_ACCESS_KEY_ID=SecretAwsKey
AWS_SECRET_ACCESS_KEY=SecretAwsSecret
ENVIRONMENT=local-or-staging-or-production
LISTEN_ADDR=127.0.0.1:3000

4
.gitignore vendored

@ -1,2 +1,4 @@
.env*
!.env.local
!.env.example
dev/db/data/*
!dev/db/data/.keep

@ -0,0 +1,30 @@
.PHONY: client
client:
@yarn --cwd ./client vite
api:
@echo 'Starting API Server...'
@go run ./main.go
test: test.api
test.api:
go test -v ./...
dynamo.start:
@cd ./dev/db && docker compose up -d
dynamo.stop:
@cd ./dev/db && docker compose down
dynamo.list:
@aws dynamodb list-tables --endpoint-url http://localhost:8000
dynamo.scan:
@aws dynamodb scan --table-name owltier-local --endpoint-url http://localhost:8000
dynamo.reset:
@cd ./dev/scripts && ./delete-table.sh
@sleep 1
@cd ./dev/scripts && ./create-table.sh

@ -0,0 +1,11 @@
version: '3.8'
services:
dynamodb-local:
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
image: "amazon/dynamodb-local:latest"
container_name: dynamodb-local
ports:
- "8000:8000"
volumes:
- "./data:/home/dynamodblocal/data"
working_dir: /home/dynamodblocal

@ -0,0 +1,2 @@
#!/bin/bash
aws dynamodb create-table --cli-input-json file://schema.json --endpoint-url http://localhost:8000 --output json

@ -0,0 +1,2 @@
#!/bin/bash
aws dynamodb delete-table --table-name 'owltier-local' --endpoint-url http://localhost:8000 --output json

@ -0,0 +1,5 @@
#!/bin/bash
# In first time setup, the data folder that dynamo's docker image binds
# to had permissions set incorrectly so dynamo didnt work. This fixes that.
sudo chown $USER ./dev/db/data -R
chmod 775 -R ./dev/db/data

@ -0,0 +1,35 @@
{
"AttributeDefinitions": [
{
"AttributeName": "pk",
"AttributeType": "S"
},
{
"AttributeName": "gsi1pk",
"AttributeType": "S"
}
],
"TableName": "owltier-local",
"KeySchema": [
{
"AttributeName": "pk",
"KeyType": "HASH"
}
],
"GlobalSecondaryIndexes": [
{
"IndexName": "gsi1",
"KeySchema": [
{
"AttributeName": "gsi1pk",
"KeyType": "HASH"
}
],
"Projection": {
"ProjectionType": "ALL"
}
}
],
"BillingMode": "PAY_PER_REQUEST",
"TableClass": "STANDARD"
}

@ -1,126 +0,0 @@
package auth_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/mnrva-dev/owltier.com/server/auth"
"github.com/mnrva-dev/owltier.com/server/db"
)
// TODO also write unit tests
type userdata struct {
Username string `json:"username"`
Password string `json:"password"`
}
var (
testuser = &db.UserSchema{
Username: "test",
Password: "testpassword1234!!",
}
permatestuser = &db.UserSchema{
Username: "user",
Password: "password1234!!",
}
)
func runTestServer() *httptest.Server {
return httptest.NewServer(auth.BuildRouter())
}
func TestMain(m *testing.M) {
m.Run()
}
func TestRegister(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/register", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Errorf("Expected status in 200-299 range, got %d", resp.StatusCode)
fmt.Println(string(body))
t.FailNow()
}
}
func TestLogin(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/login", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Errorf("Expected status in 200-299 range, got %d", resp.StatusCode)
fmt.Println(string(body))
t.FailNow()
}
}
func TestDeleteAccount(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/login", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Error("Failed to login")
t.FailNow()
}
// TODO: Fix this so that it uses session token
AccessToken := strings.Split(string(body), "\n")[0]
data = url.Values{}
data.Set("password", testuser.Password)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/delete", ts.URL), strings.NewReader(data.Encode()))
req.Header.Add("Authorization", "Bearer "+AccessToken)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if err != nil {
t.Fatal(err)
}
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
// fmt.Println(string(body))
if resp.StatusCode/100 != 2 {
t.Errorf("Expected status in 200-299 range, got %d", resp.StatusCode)
fmt.Println(string(body))
t.FailNow()
}
}

@ -1 +1,123 @@
package auth_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/mnrva-dev/owltier.com/server/auth"
"github.com/mnrva-dev/owltier.com/server/db"
)
// TODO also write unit tests
type userdata struct {
Username string `json:"username"`
Password string `json:"password"`
}
var (
testuser = &db.UserSchema{
Username: "test",
Password: "testpassword1234!!",
}
)
func runTestServer() *httptest.Server {
return httptest.NewServer(auth.BuildRouter())
}
func TestMain(m *testing.M) {
m.Run()
}
func TestRegister(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/register", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Errorf("Expected status in 200-299 range, got %d", resp.StatusCode)
fmt.Println(string(body))
t.FailNow()
}
}
func TestLogin(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/login", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Errorf("Expected status in 200-299 range, got %d", resp.StatusCode)
fmt.Println(string(body))
t.FailNow()
}
}
func TestDeleteAccount(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/login", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Error("Failed to login")
t.FailNow()
}
// TODO: Fix this so that it uses session token
sessionC := resp.Cookies()[0]
data = url.Values{}
data.Set("password", testuser.Password)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/delete", ts.URL), strings.NewReader(data.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.AddCookie(sessionC)
fmt.Println("* Got session token ", sessionC.Value)
if err != nil {
t.Fatal(err)
}
resp, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
// fmt.Println(string(body))
if resp.StatusCode/100 != 2 {
t.Errorf("Expected status in 200-299 range, got %d", resp.StatusCode)
fmt.Println(string(body))
t.FailNow()
}
}

@ -0,0 +1,3 @@
package auth_test
// TODO: test form validation

@ -1,6 +1,7 @@
package auth
import (
"log"
"net/http"
"time"
@ -37,8 +38,19 @@ func Login(w http.ResponseWriter, r *http.Request) {
// prepare login information for the client
session := uuid.NewString()
db.Update(user, "session", session)
err = db.Update(user, "session_key", session)
if err != nil {
log.Println(err)
}
// TODO: This is awful. Fix it later
err = db.Update(user, "gsi1pk", "session_key#"+session)
if err != nil {
log.Println(err)
}
db.Update(user, "last_login_at", time.Now().Unix())
if err != nil {
log.Println(err)
}
http.SetCookie(w, &http.Cookie{
Name: SESSION_COOKIE,
Value: session,

@ -13,7 +13,7 @@ import (
func Register(w http.ResponseWriter, r *http.Request) {
var form = &RequestForm{}
if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
jsend.ErrorWithCode(w, 400, "invalid form data")
return
}
@ -24,8 +24,9 @@ func Register(w http.ResponseWriter, r *http.Request) {
}, user)
// if we didnt get NotFound error...
if err == nil {
w.WriteHeader(http.StatusConflict)
w.Write([]byte("user already exists"))
jsend.Fail(w, http.StatusConflict, map[string]interface{}{
"username": "user with this username already exists",
})
return
} // TODO There is probably a better way to make sure this is just a
// "Not Found" error and not an actual error
@ -34,7 +35,7 @@ func Register(w http.ResponseWriter, r *http.Request) {
user.LastLoginAt = time.Now().Unix()
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
jsend.Error(w, "internal server error")
return
}
user.Password = string(hashedPassword)
@ -42,6 +43,7 @@ func Register(w http.ResponseWriter, r *http.Request) {
session := uuid.NewString()
user.Session = session
user.Username = form.Username
err = db.Create(user)
if err != nil {

@ -18,7 +18,7 @@ func loadEnv() {
currentWorkDirectory, _ := os.Getwd()
rootPath := projectName.Find([]byte(currentWorkDirectory))
err := godotenv.Load(string(rootPath) + `/.env.local`)
err := godotenv.Load(string(rootPath) + `/.env`)
if err != nil {
log.Println("* Error loading .env file")

@ -7,7 +7,7 @@ import (
type UserSchema struct {
Pk string `dynamodbav:"pk"`
Gsi1pk string `dynamodbav:"gsi1pk"`
Session string `dynamodbav:"session"`
Session string `dynamodbav:"session_key"`
Username string `dynamodbav:"username"`
Password string `dynamodbav:"password"`
CreatedAt int64 `dynamodbav:"created_at"`
@ -16,7 +16,7 @@ type UserSchema struct {
func (u *UserSchema) buildKeys() {
u.Pk = "user#" + u.Username
u.Gsi1pk = "session#" + u.Session
u.Gsi1pk = "session_key#" + u.Session
}
func (u *UserSchema) getKey() map[string]types.AttributeValue {

@ -1,6 +1,9 @@
package list
import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
type List struct {
Pk string `dynamodbav:"pk"`
Id string `json:"id"`
CreatedAt int64 `json:"created_at"`
CreatedBy string `json:"created_by"`
@ -10,3 +13,18 @@ type List struct {
APAC []string `json:"apac"`
Combined []string `json:"combined"`
}
func (u *List) buildKeys() {
u.Pk = "list#" + u.Id
}
func (u *List) getKey() map[string]types.AttributeValue {
u.buildKeys()
k := make(map[string]types.AttributeValue)
k["pk"] = &types.AttributeValueMemberS{Value: u.Pk}
return k
}
func (u *List) getGsi() map[string]types.AttributeValue {
return nil
}

Loading…
Cancel
Save