Sfoglia il codice sorgente

Add API routing and login functions

master
Immanuel Onyeka 1 anno fa
parent
commit
ee2358d259
4 ha cambiato i file con 241 aggiunte e 69 eliminazioni
  1. +5
    -1
      go.mod
  2. +4
    -0
      go.sum
  3. +2
    -1
      migrations/0_29092022_create_main_tables.sql
  4. +230
    -67
      skouter.go

+ 5
- 1
go.mod Vedi File

@@ -2,4 +2,8 @@ module example.com/m

go 1.19

require github.com/go-sql-driver/mysql v1.6.0
require (
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.5.0
)

+ 4
- 0
go.sum Vedi File

@@ -1,2 +1,6 @@
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 h1:DNrExYwvyyI404SxdUCCANAj9TwnGjRfa3cYFMNY1AU=
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=

+ 2
- 1
migrations/0_29092022_create_main_tables.sql Vedi File

@@ -108,12 +108,13 @@ CREATE TABLE mi (
five_year_total INT DEFAULT 0,
initial_premium INT DEFAULT 0,
initial_rate INT DEFAULT 0,
initial_amount INT DEFAULT 0,
PRIMARY KEY (`id`),
FOREIGN KEY (loan_id) REFERENCES loan(id)
);

/* template = true fees are saved for users or branches. If template or default
* are true, estimate_id should be null.*/
* are true, estimate_id should be null. */
CREATE TABLE fee (
id INT AUTO_INCREMENT NOT NULL,
loan_id INT,


+ 230
- 67
skouter.go Vedi File

@@ -10,11 +10,21 @@ import (
_ "github.com/go-sql-driver/mysql"
"fmt"
"encoding/json"
// "io"
"strconv"
"bytes"
"time"
"errors"
"strings"
pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
"github.com/golang-jwt/jwt/v4"
)

type UserClaims struct {
Id int `json:"id"`
Role string `json:"role"`
Exp string `json:"exp"`
}

type Page struct {
tpl *template.Template
Title string
@@ -75,16 +85,16 @@ type Loan struct {
}

type MI struct {
Type string
Label string
Lender string
Rate float32
Premium float32
Upfront float32
FiveYearTotal float32
InitialAllInPremium float32
InitialAllInRate float32
InitialAmount float32
Type string
Label string
Lender string
Rate float32
Premium float32
Upfront float32
FiveYearTotal float32
InitialAllInPremium float32
InitialAllInRate float32
InitialAmount float32
}

type Estimate struct {
@@ -118,6 +128,22 @@ var pages = map[string]Page {
"app": cache("app", "App"),
}

var roles = map[string]int{
"guest": 1,
"employee": 2,
"admin": 3,
}

// Used to validate claim in JWT token body. Checks if user id is greater than
// zero and time format is valid
func (c UserClaims) Valid() error {
if c.Id < 1 { return errors.New("Invalid id") }
t, err := time.Parse(time.UnixDate, c.Exp)
if err != nil { return err }
if t.Before(time.Now()) { return errors.New("Token expired.") }
return err
}

func cache(name string, title string) Page {
var p = []string{"master.tpl", paths[name]}
tpl := template.Must(template.ParseFiles(p...))
@@ -482,6 +508,7 @@ func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {

resp, err := http.DefaultClient.Do(req)
var res map[string]interface{}
var result []MI

if resp.StatusCode != 200 {
log.Printf("the status: %v\nthe resp: %v\n the req: %v\n the body: %v\n",
@@ -489,40 +516,116 @@ func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI {
} else {
json.NewDecoder(resp.Body).Decode(&res)
// estimate.Loans[pos].Mi = res
// Parse res into result here
}

return result
}

func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var id int
var role string
var err error
r.ParseForm()

row := db.QueryRow(
`SELECT id, role FROM user WHERE email = ? AND password = sha2(?, 256)`,
r.PostFormValue("email"), r.PostFormValue("password"))

err = row.Scan(&id, &role)
if err != nil {
http.Error(w, "Invalid Credentials.", http.StatusUnauthorized)
return
}

return estimate
token := jwt.NewWithClaims(jwt.SigningMethodHS256,
UserClaims{ Id: id, Role: role,
Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})

tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
if err != nil {
log.Println("Token could not be signed: ", err, tokenStr)
http.Error(w, "Token generation error.", http.StatusInternalServerError)
return
}

cookie := http.Cookie{Name: "hound",
Value: tokenStr,
Path: "/",
Expires: time.Now().Add(time.Hour * 24)}
http.SetCookie(w, &cookie)
_, err = w.Write([]byte(tokenStr))
if err != nil {
http.Error(w,
"Could not complete token write.",
http.StatusInternalServerError)}
}

func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
claims, err := getClaims(r)
// Will verify existing signature and expiry time
token := jwt.NewWithClaims(jwt.SigningMethodHS256,
UserClaims{ Id: claims.Id, Role: claims.Role,
Exp: time.Now().Add(time.Minute * 30).Format(time.UnixDate)})

tokenStr, err := token.SignedString([]byte(config["JWT_SECRET"]))
if err != nil {
log.Println("Token could not be signed: ", err, tokenStr)
http.Error(w, "Token generation error.", http.StatusInternalServerError)
return
}

cookie := http.Cookie{Name: "hound",
Value: tokenStr,
Path: "/",
Expires: time.Now().Add(time.Hour * 24)}
http.SetCookie(w, &cookie)
_, err = w.Write([]byte(tokenStr))
if err != nil {
http.Error(w,
"Could not complete token write.",
http.StatusInternalServerError)}
}

func validateEstimate() {
return
}

func route(w http.ResponseWriter, r *http.Request) {
var page Page
var args []string
p := r.URL.Path
func getClaims(r *http.Request) (UserClaims, error) {
claims := new(UserClaims)
_, tokenStr, found := strings.Cut(r.Header.Get("Authorization"), " ")

switch {
case r.Method == "GET" && match(p, "/", &args):
page = pages[ "home" ]
case match(p, "/terms", &args):
page = pages[ "terms" ]
case match(p, "/app", &args):
page = pages[ "app" ]
case match(p, "/assets", &args):
page = pages[ "app" ]
default:
http.NotFound(w, r)
return
}
if !found {
return *claims, errors.New("Token not found")
}

page.Render(w)
// Pull token payload into UserClaims
_, err := jwt.ParseWithClaims(tokenStr, claims,
func(token *jwt.Token) (any, error) {
return []byte(config["JWT_SECRET"]), nil
})

if err != nil {
return *claims, err
}

if err = claims.Valid(); err != nil {
return *claims, err
}

return *claims, nil
}

func guard(r *http.Request, required int) bool {
claims, err := getClaims(r)
if err != nil { return false }
if roles[claims.Role] < required { return false }

return true
}

func api(w http.ResponseWriter, r *http.Request) {
var args []string
// var response string

p := r.URL.Path
db, err := sql.Open("mysql",
@@ -534,54 +637,114 @@ func api(w http.ResponseWriter, r *http.Request) {

err = db.Ping()
if err != nil {
print("Bad database configuration: %v", err)
fmt.Println("Bad database configuration: %v\n", err)
panic(err)
// maybe os.Exit(1) instead
}

switch {
case match(p, "/api/loans", &args):
resp, err := getLoanType(db, 0, 0, true)

if resp != nil {
json.NewEncoder(w).Encode(resp)
} else {
json.NewEncoder(w).Encode(err)
}

case match(p, "/api/fees", &args):
resp, err := getFeesTemp(db, 0)

if resp != nil {
json.NewEncoder(w).Encode(resp)
} else {
json.NewEncoder(w).Encode(err)
}
case match(p, "/api/login", &args) &&
r.Method == http.MethodPost:
login(w, db, r)
case match(p, "/api/token", &args) &&
r.Method == http.MethodGet && guard(r, 1):
getToken(w, db, r)
case match(p, "/api/users", &args) && // Array of all users
r.Method == http.MethodGet && guard(r, 2):
getUsers(w, db, r)
case match(p, "/api/user", &args) &&
r.Method == http.MethodGet, && guard(r, 1):
getUser(w, db, r)
case match(p, "/api/user", &args) &&
r.Method == http.MethodPost &&
guard(r, 3):
createUser(w, db, r)
case match(p, "/api/user", &args) &&
r.Method == http.MethodPatch &&
guard(r, 3): // For admin to modify any user
patchUser(w, db, r)
case match(p, "/api/user", &args) &&
r.Method == http.MethodPatch &&
guard(r, 2): // For employees to modify own accounts
patchSelf(w, db, r)
case match(p, "/api/user", &args) &&
r.Method == http.MethodDelete &&
guard(r, 3):
deleteUser(w, db, r)
case match(p, "/api/batch", &args) &&
r.Method == http.MethodGet &&
guard(r, 1):
getBatch(w, db, r)
case match(p, "/api/batch", &args) &&
r.Method == http.MethodPost &&
guard(r, 2):
openBatch(w, db, r)
case match(p, "/api/batch", &args) &&
r.Method == http.MethodPatch &&
guard(r, 2):
closeBatch(w, db, r)
case match(p, "/api/client", &args) &&
r.Method == http.MethodGet &&
guard(r, 1):
getClient(w, db, r)
case match(p, "/api/client", &args) &&
r.Method == http.MethodPost &&
guard(r, 2):
createClient(w, db, r)
case match(p, "/api/client", &args) &&
r.Method == http.MethodPatch &&
guard(r, 2):
patchClient(w, db, r)
case match(p, "/api/client", &args) &&
r.Method == http.MethodDelete &&
guard(r, 2):
deleteClient(w, db, r)
case match(p, "/api/ticket", &args) &&
r.Method == http.MethodPost &&
guard(r, 2):
openTicket(w, db, r)
case match(p, "/api/ticket", &args) &&
r.Method == http.MethodPatch &&
guard(r, 2):
closeTicket(w, db, r)
case match(p, "/api/ticket", &args) &&
r.Method == http.MethodDelete &&
guard(r, 2):
voidTicket(w, db, r)
case match(p, "/api/report/batch", &args) &&
r.Method == http.MethodGet &&
guard(r, 2):
reportBatch(w, db, r)
case match(p, "/api/report/summary", &args) &&
r.Method == http.MethodPost &&
guard(r, 2):
reportSummary(w, db, r)
}

case match(p, "/api/mi", &args):
var err error
est, err := getEstimate(db, 1)
if err != nil {
json.NewEncoder(w).Encode(err)
log.Println("error occured:", err)
break
}
db.Close()
}

json.NewEncoder(w).Encode(fetchMi(db, &est, 0).Loans[0].Mi)
func route(w http.ResponseWriter, r *http.Request) {
var page Page
var args []string
p := r.URL.Path

// if err != nil {
// json.NewEncoder(w).Encode(err)
// break
// } else {
// json.NewEncoder(w).Encode(resp)
// }
}
switch {
case r.Method == "GET" && match(p, "/", &args):
page = pages[ "home" ]
case match(p, "/terms", &args):
page = pages[ "terms" ]
case match(p, "/app", &args):
page = pages[ "app" ]
default:
http.NotFound(w, r)
return
}

page.Render(w)
}

func main() {
files := http.FileServer(http.Dir(""))

http.Handle("/assets/", files)
http.HandleFunc("/api/", api)


Loading…
Annulla
Salva