Quellcode durchsuchen

Login user through view

master
Immanuel Onyeka vor 1 Jahr
Ursprung
Commit
9c7ac0a199
6 geänderte Dateien mit 286 neuen und 24 gelöschten Zeilen
  1. +5
    -0
      assets/main.css
  2. +122
    -10
      components/app.vue
  3. +44
    -0
      components/login.vue
  4. +1
    -1
      migrations/0_29092022_setup_tables.sql
  5. +4
    -0
      migrations/seed.sql
  6. +110
    -13
      skouter.go

+ 5
- 0
assets/main.css Datei anzeigen

@@ -364,3 +364,8 @@ section.special {
border-radius: 3px;
padding: 25px 10px;
}

.loading span.error {
top: 40px;
position: absolute;
}

+ 122
- 10
components/app.vue Datei anzeigen

@@ -1,10 +1,12 @@
<template>
<div class="panel">

<side-bar :role="user.status" :active="active">
<template v-if="user">
<side-bar v-if="user" :role="user && user.status" :active="active">
</side-bar>

<div v-if="loading" class="page loading">
<span class="error" >{{loadingError}}</span>
<spinner></spinner>
</div>

@@ -13,6 +15,11 @@
<estimates :user="user" :fees="fees" v-else-if="active == 3" />
<settings :user="user" v-else-if="active == 4" />
<sign-out :user="user" v-else-if="active == 5" />
</template>

<template v-if="!user && active == 6">
<login @login="start" />
</template>

</div>
</template>
@@ -25,13 +32,75 @@ import NewEstimate from "./new.vue"
import Estimates from "./estimates.vue"
import Settings from "./settings.vue"
import SignOut from "./sign-out.vue"
import Login from "./login.vue"

function getCookie(name) {
var re = new RegExp(name + "=([^;]+)")
var value = re.exec(document.cookie)

return (value != null) ? unescape(value[1]) : null
}

function refreshToken() {
const token = getCookie("skouter")

const user = {
firstName: "test",
lastName: "user",
id: 12,
status: 1,
}
fetch(`/api/token`,
{method: 'GET',
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${token}`,
},
}).then(response => {
if (!response.ok) {
console.log("Error refreshing token.")
}
})

// Recursive refresh
setTimeout(this.refreshToken, 1000*60*25)
}

function getUser() {
const token = getCookie("skouter")

return fetch(`/api/user`,
{method: 'GET',
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${token}`,
},
}).then(response => {
if (response.ok) {
return response.json()
} else {
// Redirect to login if starting token is invalid
window.location.hash = '#login'
}
}).then (result => {
if (!result || !result.length) return // Exit if token is invalid
this.user = result[0]
})

}

function getFees() {
const token = getCookie("skouter")
return fetch(`/api/fees`,
{method: 'GET',
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${token}`,
},
}).then(response => {
if (response.ok) { return response.json() }
}).then (result => {
if (!result || !result.length) return // Exit if token is invalid or no fees are saved
this.fees = result
console.log("the result %O", result)
})

}

// The default fees of a new loan. Percentage values take precedent over amounts
const fees = [
@@ -60,11 +129,35 @@ function active() {
return 4
} else if (/^#sign-out\/?/.exec(this.hash)) {
return 5
} else if (/^#login\/?/.exec(this.hash)) {
return 6
} else {
return 0
}
}

// Fetch data before showing UI. If requests fail, assume token is expired.
function start() {
this.loading = true

let loaders = []
loaders.push(this.getUser())
loaders.push(this.getFees())
Promise.all(loaders).then((a, b) => {
this.loading = false
if (!b) {
// Time untill token expiration may have elapsed before the page
// reloaded
this.refreshToken()
}
}).catch(error => {
console.log("An error occured %O", error)
this.loadingError = "Could not initialize app."
})

}


export default {
components: {
SideBar,
@@ -73,17 +166,36 @@ export default {
NewEstimate,
Estimates,
Settings,
SignOut
SignOut,
Login
},
computed: { active },
methods: {
getCookie,
start,
getUser,
getFees,
refreshToken,
},
data() {
return {
loading: true, user: user, hash: window.location.hash,
fees: fees
loading: true,
user: null,
hash: window.location.hash,
fees: [],
loadingError: "",
}
},
created() {
window.onhashchange = () => this.hash = window.location.hash
this.token = this.getCookie("skouter")

if (!this.token) {
window.location.hash = 'login'
this.loading = false
return
}
this.start()
}
}
</script>

+ 44
- 0
components/login.vue Datei anzeigen

@@ -0,0 +1,44 @@
<template>
<div class="page">

<section class="form inputs">
<h3>Login</h3>
<label>Email</label>
<input v-model="email" required>
<label>Password</label>
<input v-model="password" type="password" required>
<button @click="login">Login</button>
<div class="error"> {{error}} </div>
</section>


</div>
</template>

<script>
function login() {
this.error = ""
return fetch(`/api/login`,
{method: 'POST',
body: JSON.stringify( {email: this.email, password: this.password} ),
}).then(response => {
if (response.ok) {
return response.text()
} else {
this.error = "Invalid credentials"
}
}).then(result => {
if (!result || !result.length) return // Exit if there is no token
this.$emit('login')
window.location.hash = ''
})

}
export default {
emits: [ 'login' ],
methods: { login },
data() {
return { email: "", password: "", error: ""}
},
}
</script>

+ 1
- 1
migrations/0_29092022_setup_tables.sql Datei anzeigen

@@ -27,7 +27,7 @@ CREATE TABLE user (
'Subscribed',
'Branch',
'Admin'),
role ENUM('User', 'Manager', 'Admin'),
role ENUM('User', 'Manager', 'Admin') NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (branch_id) REFERENCES branch(id)
);


+ 4
- 0
migrations/seed.sql Datei anzeigen

@@ -13,6 +13,7 @@ INSERT IGNORE INTO user (
title,
email,
verified,
role,
status
) VALUES

@@ -25,6 +26,7 @@ INSERT IGNORE INTO user (
'Loan Officer',
'test@example.com',
true,
'User',
'Free'
),

@@ -37,6 +39,7 @@ INSERT IGNORE INTO user (
'Mortgage Broker',
'unverified@example.com',
false,
'User',
'Free'
),

@@ -49,6 +52,7 @@ INSERT IGNORE INTO user (
'Branch Manager',
'manager@example.com',
true,
'Manager',
'Free'
);



+ 110
- 13
skouter.go Datei anzeigen

@@ -2,6 +2,7 @@ package main

import (
"net/http"
"net/mail"
"log"
"sync"
"regexp"
@@ -15,10 +16,24 @@ import (
"time"
"errors"
"strings"
pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
// pdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
"github.com/golang-jwt/jwt/v4"
)

type User struct {
Id int `json:"id"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
BranchId int `json:"branchId"`
Status string `json:"status"`
Country string `json:"country"`
Title string `json:"title"`
Verified bool `json:"verified"`
Role string `json:"role"`
Password string `json:"password,omitempty"`
}

type UserClaims struct {
Id int `json:"id"`
Role string `json:"role"`
@@ -292,14 +307,12 @@ func getFees(db *sql.DB, loan int) ([]Fee, error) {
return fees, nil
}

// Fetch fees from the database
func getFeesTemp(db *sql.DB, user int) ([]FeeTemplate, error) {
func fetchFeesTemp(db *sql.DB, user int, branch int) ([]FeeTemplate, error) {
var fees []FeeTemplate

rows, err := db.Query(
"SELECT * FROM fee_template " +
"WHERE user_id = ? OR user_id = 0",
user)
"WHERE user_id = ? OR branch_id = ?",
user, branch)
if err != nil {
return nil, fmt.Errorf("Fee template query error %v", err)
@@ -331,6 +344,18 @@ func getFeesTemp(db *sql.DB, user int) ([]FeeTemplate, error) {
return fees, nil
}

// Fetch fees from the database
func getFeesTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var fees []FeeTemplate
claims, err := getClaims(r)
if err != nil { w.WriteHeader(500); return }
users, err := queryUsers(db, claims.Id)
if err != nil { w.WriteHeader(422); return }

fees, err = fetchFeesTemp(db, claims.Id, users[0].BranchId)
json.NewEncoder(w).Encode(fees)
}

func getMi(db *sql.DB, loan int) (MI, error) {
var mi MI

@@ -526,11 +551,13 @@ func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var id int
var role string
var err error
r.ParseForm()
var user User
json.NewDecoder(r.Body).Decode(&user)

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

err = row.Scan(&id, &role)
if err != nil {
@@ -549,7 +576,7 @@ func login(w http.ResponseWriter, db *sql.DB, r *http.Request) {
return
}

cookie := http.Cookie{Name: "hound",
cookie := http.Cookie{Name: "skouter",
Value: tokenStr,
Path: "/",
Expires: time.Now().Add(time.Hour * 24)}
@@ -575,7 +602,7 @@ func getToken(w http.ResponseWriter, db *sql.DB, r *http.Request) {
return
}

cookie := http.Cookie{Name: "hound",
cookie := http.Cookie{Name: "skouter",
Value: tokenStr,
Path: "/",
Expires: time.Now().Add(time.Hour * 24)}
@@ -640,7 +667,6 @@ func queryUsers(db *sql.DB, id int) ( []User, error ) {
u.title,
u.status,
u.verified,
u.last_login,
u.role
FROM user u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END
`
@@ -666,7 +692,6 @@ func queryUsers(db *sql.DB, id int) ( []User, error ) {
&user.Title,
&user.Status,
&user.Verified,
&user.LastLogin,
&user.Role,
)
err != nil {
@@ -748,10 +773,78 @@ func getUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
claims, err := getClaims(r)
if err != nil { w.WriteHeader(500); return }
users, err := queryUsers(db, claims.Id)
if err != nil { w.WriteHeader(422); return }
if err != nil { w.WriteHeader(422); log.Println(err); return }
json.NewEncoder(w).Encode(users)
}

func getUsers(w http.ResponseWriter, db *sql.DB, r *http.Request) {
users, err := queryUsers(db, 0)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

json.NewEncoder(w).Encode(users)
}

// Updates a user using only specified values in the JSON body
func patchUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)

_, err = mail.ParseAddress(user.Email)
if err != nil { http.Error(w, "Invalid email.", 422); return }

if roles[user.Role] == 0 {
http.Error(w, "Invalid role.", 422)
return
}

err = updateUser(user, db)
if err != nil { http.Error(w, "Bad form values.", 422); return }

users, err := queryUsers(db, user.Id)
if err != nil { http.Error(w, "Bad form values.", 422); return }
json.NewEncoder(w).Encode(users[0])
}

// Update specified fields of the user specified in the claim
func patchSelf(w http.ResponseWriter, db *sql.DB, r *http.Request) {
claim, err := getClaims(r)
var user User
json.NewDecoder(r.Body).Decode(&user)

// First check that the target user to be updated is the same as the claim id, and
// their role is unchanged.
if err != nil || claim.Id != user.Id {
http.Error(w, "Target user's id does not match claim.", 401)
return
}

if claim.Role != user.Role && user.Role != "" {
http.Error(w, "Administrator required to escalate role.", 401)
return
}

patchUser(w, db, r)
}

func deleteUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, "Invalid fields.", 422)
return
}

query := `DELETE FROM user WHERE id = ?`
_, err = db.Exec(query, user.Id)

if err != nil {
http.Error(w, "Could not delete.", 500)
}
}

func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
@@ -816,6 +909,10 @@ func api(w http.ResponseWriter, r *http.Request) {
r.Method == http.MethodDelete &&
guard(r, 3):
deleteUser(w, db, r)
case match(p, "/api/fees", &args) &&
r.Method == http.MethodGet &&
guard(r, 1):
getFeesTemp(w, db, r)
}

db.Close()


Laden…
Abbrechen
Speichern