Compare commits

..

5 Commits

Author SHA1 Message Date
4ec1d8c5a2 Auth 2024-08-13 13:47:56 +02:00
011b3a8c0b Create a writeJSON helper 2024-08-13 13:34:53 +02:00
b37dcf89fd Create route and handler for authentication 2024-08-13 13:26:38 +02:00
aab0ef380a writing the stub js to authenticate against the back end 2024-08-12 21:44:49 +02:00
846282db74 create login page 2024-08-12 21:38:12 +02:00
10 changed files with 246 additions and 0 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ docker/docker-compose.yml
cred.txt
dist/
.air.toml
tmp/

View File

@ -230,3 +230,39 @@ func (app *application) SaveOrder(order models.Order) (int, error) {
return id, nil
}
func (app *application) CreateAuthToken(w http.ResponseWriter, r *http.Request) {
var userInput struct {
Email string `json:"email"`
Password string `json:"password"`
}
err := app.readJSON(w, r, &userInput)
if err != nil {
app.badRequest(w, r, err)
return
}
// get the user from the db by email, send error if invalid email
user, err := app.DB.GetUserByEmail(userInput.Email)
if err != nil {
app.invalidCredentials(w)
return
}
// validate the password, send error if invalid password
// generate the token
// send response
var payload struct {
Error bool `json:"error"`
Message string `json:"message"`
}
payload.Error = false
payload.Message = "Success!"
_ = app.writeJSON(w, http.StatusOK, payload)
}

85
cmd/api/helpers.go Normal file
View File

@ -0,0 +1,85 @@
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
)
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, data interface{}) error {
maxBytes := 1048576
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
err := dec.Decode(data)
if err != nil {
return err
}
// Make sure there is only one entry.
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must only have a single JSON value")
}
return nil
}
// writeJSON writes arbitrary data out as JSON
func (app *application) writeJSON(
w http.ResponseWriter,
status int, data interface{},
headers ...http.Header,
) error {
out, err := json.MarshalIndent(data, "", "\t")
if err != nil {
return err
}
if len(headers) > 0 {
for k, v := range headers[0] {
w.Header()[k] = v
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write(out)
return nil
}
func (app *application) badRequest(w http.ResponseWriter, r *http.Request, err error) error {
var payload struct {
Error bool `json:"error"`
Message string `json:"message"`
}
payload.Error = true
payload.Message = err.Error()
out, err := json.MarshalIndent(payload, "", "\t")
if err != nil {
return err
}
w.Header().Set("Content-Type", "application/json")
w.Write(out)
return nil
}
func (app *application) invalidCredentials(w http.ResponseWriter) error {
var payload struct {
Error bool `json:"error"`
Message string `json:"message"`
}
payload.Error = true
payload.Message = "invalid authentication credentials"
err := app.writeJSON(w, http.StatusUnauthorized, payload)
if err != nil {
return err
}
return nil
}

View File

@ -21,5 +21,8 @@ func (app *application) routes() http.Handler {
mux.Post("/api/payment-intent", app.GetPaymentIntent)
mux.Get("/api/widget/{id}", app.GetWidgetByID)
mux.Post("/api/create-customer-and-subscribe-to-plan", app.CreateCustomerAndSubscribeToPlan)
mux.Post("/api/authenticate", app.CreateAuthToken)
return mux
}

View File

@ -283,3 +283,9 @@ func (app *application) BronzePlanReceipt(w http.ResponseWriter, r *http.Request
app.errorLog.Println(err)
}
}
func (app *application) LoginPage(w http.ResponseWriter, r *http.Request) {
if err := app.renderTemplate(w, r, "login", &templateData{}); err != nil {
app.errorLog.Println(err)
}
}

View File

@ -22,6 +22,9 @@ func (app *application) routes() http.Handler {
mux.Get("/plans/bronze", app.BronzePlan)
mux.Get("/receipt/bronze", app.BronzePlanReceipt)
// auth routes
mux.Get("/login", app.LoginPage)
fileServer := http.FileServer(http.Dir("./static"))
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))

View File

@ -51,6 +51,11 @@
</ul>
</li>
</ul>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a href="/login" class="nav-link">Login</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,44 @@
{{ template "base" . }}
{{ define "title" }}
Login
{{ end }}
{{ define "content" }}
<form action=""
method="post"
name="login-form"
id="login-form"
class="d-blick needs-validation"
autocomplete="off"
novalidate="">
<h2 class="mt-2 mb-3 text-center">Login</h2>
<hr>
<div class="mb-3">
<label for="-email" class="form-label">Email</label>
<input type="text"
id="email"
name="email"
autocomplete="email-new"
required=""
class="form-control">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password"
id="password"
name="password"
autocomplete="password-new"
required=""
class="form-control">
</div>
<hr>
<a href="javascript:void(0)" id="login-btn" class="btn btn-primary">Login</a>
</form>
{{ end }}
{{define "js"}}
<script type="module">
import {val} from "/static/js/login.js"
document.getElementById("login-btn").addEventListener("click", () => {
val({{.API}});
})
</script>
{{end}}

View File

@ -3,6 +3,7 @@ package models
import (
"context"
"database/sql"
"strings"
"time"
)
@ -223,3 +224,32 @@ func (m *DBModel) InsertCustomer(customer Customer) (int, error) {
}
return int(id), nil
}
// GetUserByEmail gets a user by email address
func (m *DBModel) GetUserByEmail(email string) (User, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
email = strings.ToLower(email)
var u User
query := `SELECT id, first_name, last_name, email, password, created_at, updated_at
FROM users
WHERE email = ?`
row := m.DB.QueryRowContext(ctx, query, email)
err := row.Scan(
&u.ID,
&u.FirstName,
&u.LastName,
&u.Email,
&u.CreatedAt,
&u.UpdatedAt,
)
if err != nil {
return User{}, err
}
return u, nil
}

33
static/js/login.js Normal file
View File

@ -0,0 +1,33 @@
export function val(api) {
let form = document.getElementById("login-form");
if (form.checkValidity() === false) {
this.event.preventDefault();
this.event.stopPropagation();
form.classList.add("was-validated");
return;
}
form.classList.add("was-validated");
let payload = {
email: document.getElementById("email").value,
password: document.getElementById("password").value,
};
const requestOptions = {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
};
fetch(api + "/api/authenticate", requestOptions)
.then(response => response.json())
.then(response => {
console.log(response)
});
}