send authentication emailo

This commit is contained in:
tavo-wasd 2024-09-03 12:50:49 -06:00
parent 8433d662ad
commit ecb0e58cae
8 changed files with 201 additions and 35 deletions

View file

@ -4,9 +4,12 @@ SRC = ${SRCDIR}/init.go \
${SRCDIR}/main.go \ ${SRCDIR}/main.go \
${SRCDIR}/paypal.go \ ${SRCDIR}/paypal.go \
${SRCDIR}/db.go \ ${SRCDIR}/db.go \
${SRCDIR}/auth.go \
GOFILES = ${SRCDIR}/go.sum ${SRCDIR}/go.mod GOFILES = ${SRCDIR}/go.sum ${SRCDIR}/go.mod
GOMODS = github.com/joho/godotenv github.com/lib/pq GOMODS = github.com/joho/godotenv \
github.com/lib/pq \
gopkg.in/gomail.v2 \
all: ${BIN} all: ${BIN}

View file

@ -22,7 +22,7 @@ CREATE DATABASE iterone OWNER conex;
DROP TABLE IF EXISTS changes; DROP TABLE IF EXISTS changes;
DROP TABLE IF EXISTS payments; DROP TABLE IF EXISTS payments;
DROP TABLE IF EXISTS sites; DROP TABLE IF EXISTS sites;
CREATE TABLE sites ( CREATE TABLE sites (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
folder VARCHAR(35) UNIQUE NOT NULL, folder VARCHAR(35) UNIQUE NOT NULL,
@ -33,7 +33,9 @@ CREATE TABLE sites (
email VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL,
phone VARCHAR(20), phone VARCHAR(20),
code VARCHAR(2), code VARCHAR(2),
raw JSONB NOT NULL raw JSONB NOT NULL,
auth INTEGER,
valid TIMESTAMPTZ
); );
#+END_SRC #+END_SRC
@ -42,8 +44,8 @@ SELECT * FROM sites;
#+END_SRC #+END_SRC
#+RESULTS: #+RESULTS:
| id | folder | status | due | name | sur | email | phone | code | raw | | id | folder | status | due | name | sur | email | phone | code | raw | auth | valid |
|----+--------+--------+-----+------+-----+-------+-------+------+-----| |----+--------+--------+-----+------+-----+-------+-------+------+-----+------+-------|
** Payments table ** Payments table
@ -58,7 +60,7 @@ CREATE TABLE payments (
amount DECIMAL(10, 2) NOT NULL, amount DECIMAL(10, 2) NOT NULL,
currency VARCHAR(3) NOT NULL, currency VARCHAR(3) NOT NULL,
status VARCHAR(18) NOT NULL, -- PayPal capture status length -- https://developer.paypal.com/docs/api/orders/v2/#orders_capture status VARCHAR(18) NOT NULL, -- PayPal capture status length -- https://developer.paypal.com/docs/api/orders/v2/#orders_capture
date DATE NOT NULL date TIMESTAMPTZ NOT NULL
); );
#+END_SRC #+END_SRC
@ -76,13 +78,12 @@ SELECT * FROM payments;
DROP TABLE IF EXISTS changes; DROP TABLE IF EXISTS changes;
CREATE TABLE changes ( CREATE TABLE changes (
id INTEGER PRIMARY KEY, id SERIAL PRIMARY KEY,
by VARCHAR(20) NOT NULL, by VARCHAR(20) NOT NULL,
site INTEGER REFERENCES sites(id), site INTEGER REFERENCES sites(id),
payment INTEGER REFERENCES payments(id), payment INTEGER REFERENCES payments(id),
col VARCHAR(6) NOT NULL, col VARCHAR(6) NOT NULL,
prev VARCHAR(8) NOT NULL, descrip VARCHAR(120) NOT NULL,
next VARCHAR(8) NOT NULL,
date DATE NOT NULL date DATE NOT NULL
); );
#+END_SRC #+END_SRC
@ -92,7 +93,8 @@ SELECT * FROM changes;
#+END_SRC #+END_SRC
#+RESULTS: #+RESULTS:
|---| | id | by | site | payment | col | descrip | date |
|----+----+------+---------+-----+---------+------|
** Types of changes ** Types of changes
@ -111,6 +113,7 @@ SELECT * FROM changes;
- email: prev/next - email: prev/next
- phone: prev/next - phone: prev/next
- code: prev/next - code: prev/next
- raw: prev/next
* Error codes * Error codes

0
public/client.js Normal file
View file

View file

@ -20,7 +20,7 @@
<!-- Load Editor.js's Core --> <!-- Load Editor.js's Core -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script> <script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<!-- EDITORJS --> <!-- EDITORJS -->
<script src="/app.js"></script> <script src="/paypal.js"></script>
<!-- <script src="/static/js/config.editor.js"></script> --> <!-- <script src="/static/js/config.editor.js"></script> -->
<script> <script>
// Initialize Editor.js // Initialize Editor.js

39
server/auth.go Normal file
View file

@ -0,0 +1,39 @@
package main
import (
"fmt"
"math/rand"
"os"
"strconv"
"time"
"gopkg.in/gomail.v2"
)
func GenerateCode() string {
rand.Seed(time.Now().UnixNano())
return fmt.Sprintf("%06d", rand.Intn(1000000))
}
func SendAuthEmail(recipient string, code string) error {
smtpHost := os.Getenv("EMAIL_HOST")
smtpPortStr := os.Getenv("EMAIL_PORT")
smtpUser := os.Getenv("EMAIL_USER")
smtpPass := os.Getenv("EMAIL_PASS")
smtpPort, _ := strconv.Atoi(smtpPortStr)
subject := os.Getenv("EMAIL_SUBJECT")
body := os.Getenv("EMAIL_BODY")
m := gomail.NewMessage()
m.SetHeader("From", smtpUser)
m.SetHeader("To", recipient)
m.SetHeader("Subject", subject)
m.SetBody("text/plain", body+code)
d := gomail.NewDialer(smtpHost, smtpPort, smtpUser, smtpPass)
if err := d.DialAndSend(m); err != nil {
return err
}
return nil
}

View file

@ -10,17 +10,20 @@ import (
) )
const ( const (
errDBRegisterSite = "Error: db.go (sites): Register site" errDBRegisterSite = "db.go (sites): Register site"
errDBUpdateDue = "Error: db.go (sites): Update due date" errDBUpdateDue = "db.go (sites): Update due date"
errDBRegisterPayment = "Error: db.go (payments): Register payment" errDBRegisterPayment = "db.go (payments): Register payment"
errDBTXBeginUpdateSite = "Error: db.go: Begin transaction" errDBTXBeginUpdateSite = "db.go: Begin transaction"
errDBTXCommitUpdateSite = "Error: db.go: Commit transaction" errDBTXCommitUpdateSite = "db.go: Commit transaction"
errDBGetPrevRaw = "Error: db.go (sites): Query old raw json" errDBGetPrevRaw = "db.go (sites): Query old raw json"
errDBUpdateRaw = "Error: db.go (sites): Update raw json" errDBUpdateRaw = "db.go (sites): Update raw json"
errDBChangesRaw = "Error: db.go (changes): Register raw json change" errDBChangesRaw = "db.go (changes): Register raw json change"
errDBUpdateSiteAuth = "db.go (sites): Auth"
) )
func RegisterSitePayment(capture Capture, directory string, editorData json.RawMessage) error { func RegisterSitePayment(
capture Capture, directory string, editorData json.RawMessage,
) error {
var ( var (
// Payment // Payment
id string id string
@ -60,7 +63,8 @@ func RegisterSitePayment(capture Capture, directory string, editorData json.RawM
if newSite == sql.ErrNoRows { if newSite == sql.ErrNoRows {
if err := db.QueryRow(` if err := db.QueryRow(`
INSERT INTO sites (folder, status, due, name, sur, email, phone, code, raw) INSERT INTO sites
(folder, status, due, name, sur, email, phone, code, raw)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id RETURNING id
`, directory, wstatus, due, `, directory, wstatus, due,
@ -79,7 +83,8 @@ func RegisterSitePayment(capture Capture, directory string, editorData json.RawM
} }
if _, err := db.Exec(` if _, err := db.Exec(`
INSERT INTO payments (capture, site, amount, currency, date, status) INSERT INTO payments
(capture, site, amount, currency, date, status)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
`, id, pkey, amount, currency, date, pstatus); err != nil { `, id, pkey, amount, currency, date, pstatus); err != nil {
return fmt.Errorf("%s: %v", errDBRegisterPayment, err) return fmt.Errorf("%s: %v", errDBRegisterPayment, err)
@ -88,7 +93,7 @@ func RegisterSitePayment(capture Capture, directory string, editorData json.RawM
return nil return nil
} }
func UpdateSite(by string, pkey int, editorData json.RawMessage) error { func UpdateSite(pkey int, editorData json.RawMessage) error {
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
return fmt.Errorf("%s: %v", errDBTXBeginUpdateSite, err) return fmt.Errorf("%s: %v", errDBTXBeginUpdateSite, err)
@ -114,9 +119,10 @@ func UpdateSite(by string, pkey int, editorData json.RawMessage) error {
} }
if _, err = tx.Exec(` if _, err = tx.Exec(`
INSERT INTO changes (by, site, payment, col, prev, next, date) INSERT INTO changes
VALUES ($1, $2, NULL, 'raw', $3, $4, CURRENT_DATE); (by, site, payment, col, descrip, date)
`, by, pkey, prev, editorData); err != nil { VALUES ($1, $2, NULL, 'raw', $3, CURRENT_DATE);
`, "server", pkey, "Cambio automatizado de sitio"); err != nil {
return fmt.Errorf("%s: %v", errDBChangesRaw, err) return fmt.Errorf("%s: %v", errDBChangesRaw, err)
} }
@ -126,3 +132,55 @@ func UpdateSite(by string, pkey int, editorData json.RawMessage) error {
return nil return nil
} }
func UpdateSiteAuth(folder string, code string) (string, error) {
valid := time.Now().Add(5 * time.Minute)
var email string
if err := db.QueryRow(`
UPDATE sites
SET auth = $1, valid = $2
WHERE folder = $3
RETURNING email;
`, code, valid, folder).Scan(&email); err != nil {
return "", fmt.Errorf("%s: %v", errDBUpdateSiteAuth, err)
}
return email, nil
}
func ValidateSiteAuth(folder string, code string) (int, error) {
var dbCode string
var validTime time.Time
err := db.QueryRow(`
SELECT auth, valid FROM sites
WHERE folder = $1;
`, folder).Scan(&dbCode, &validTime)
if err != nil {
if err == sql.ErrNoRows {
return 0, fmt.Errorf("%s: %v", "No such directory", err)
}
return 0, fmt.Errorf("%s: %v", "Failed to query DB", err)
}
if code != dbCode {
return 0, fmt.Errorf("%s", "Incorrect code")
}
if time.Now().After(validTime) {
return 0, fmt.Errorf("%s", "Auth expired")
}
var pkey int
if err := db.QueryRow(`
UPDATE sites
SET valid = $1
WHERE folder = $2
RETURNING id;
`, time.Now(), folder).Scan(&pkey); err != nil {
return 0, fmt.Errorf("%s: %v", "Void used code", err)
}
return pkey, nil
}

View file

@ -21,6 +21,10 @@ const (
errRegisterSite = "Error: main.go: Register site in database" errRegisterSite = "Error: main.go: Register site in database"
errEncodeResponse = "Error: main.go: Encode response" errEncodeResponse = "Error: main.go: Encode response"
errCreateOrder = "Error: main.go: Obtain orderID" errCreateOrder = "Error: main.go: Obtain orderID"
errAuthGen = "Error: main.go: Gen and register auth"
errAuthEmail = "Error: main.go: Send auth email"
errAuthValidate = "Error: main.go: Validate changes"
errUpdateSite = "Error: main.go: Updating site data"
) )
func main() { func main() {
@ -28,7 +32,8 @@ func main() {
http.HandleFunc("/api/orders", CreateOrderHandler) http.HandleFunc("/api/orders", CreateOrderHandler)
http.HandleFunc("/api/orders/", CaptureOrderHandler) http.HandleFunc("/api/orders/", CaptureOrderHandler)
// http.HandleFunc("/api/updateadfasad/", UpdateSiteHandler) http.HandleFunc("/api/update", UpdateSiteHandler)
http.HandleFunc("/api/confirm", ConfirmChangesHandler)
http.Handle("/", http.FileServer(http.Dir("./public"))) http.Handle("/", http.FileServer(http.Dir("./public")))
stop := make(chan os.Signal, 1) stop := make(chan os.Signal, 1)
@ -52,9 +57,11 @@ func msg(notice string) {
log.Println(notice) log.Println(notice)
} }
func fail(w http.ResponseWriter, err error, notice string) { func httpErrorAndLog(w http.ResponseWriter,
err error, notice string, client string,
) {
log.Printf("%s: %v", notice, err) log.Printf("%s: %v", notice, err)
http.Error(w, notice, http.StatusInternalServerError) http.Error(w, client, http.StatusInternalServerError)
} }
func fatal(err error, notice string) { func fatal(err error, notice string) {
@ -65,7 +72,7 @@ func fatal(err error, notice string) {
func CreateOrderHandler(w http.ResponseWriter, r *http.Request) { func CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
orderID, err := CreateOrder() orderID, err := CreateOrder()
if err != nil { if err != nil {
fail(w, err, errCreateOrder) httpErrorAndLog(w, err, errCreateOrder, "Error creating order")
return return
} }
@ -80,12 +87,14 @@ func CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
} }
func CaptureOrderHandler(w http.ResponseWriter, r *http.Request) { func CaptureOrderHandler(w http.ResponseWriter, r *http.Request) {
errClientNotice := "Error capturing order"
var cart struct { var cart struct {
Directory string `json:"directory"` Directory string `json:"directory"`
EditorData json.RawMessage `json:"editor_data"` EditorData json.RawMessage `json:"editor_data"`
} }
if err := json.NewDecoder(r.Body).Decode(&cart); err != nil { if err := json.NewDecoder(r.Body).Decode(&cart); err != nil {
fail(w, err, errReadBody) httpErrorAndLog(w, err, errReadBody, errClientNotice)
return return
} }
@ -93,24 +102,78 @@ func CaptureOrderHandler(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
orderID := parts[0] orderID := parts[0]
if orderID == "" { if orderID == "" {
fail(w, nil, errGetOrderID) httpErrorAndLog(w, nil, errGetOrderID, errClientNotice)
return return
} }
capture, receipt, err := CaptureOrder(orderID) capture, receipt, err := CaptureOrder(orderID)
if err != nil { if err != nil {
fail(w, err, errCaptureOrder) httpErrorAndLog(w, err, errCaptureOrder, errClientNotice)
return return
} }
if err := RegisterSitePayment(capture, cart.Directory, cart.EditorData); err != nil { if err := RegisterSitePayment(capture, cart.Directory, cart.EditorData); err != nil {
fail(w, err, errRegisterSite+": "+cart.Directory) httpErrorAndLog(w, err, errRegisterSite+": "+cart.Directory, errClientNotice)
return return
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(receipt); err != nil { if err := json.NewEncoder(w).Encode(receipt); err != nil {
fail(w, err, errEncodeResponse) httpErrorAndLog(w, err, errEncodeResponse, errClientNotice)
return
}
return
}
func UpdateSiteHandler(w http.ResponseWriter, r *http.Request) {
errClientNotice := "Error handling update request"
var cart struct {
Directory string `json:"directory"`
}
if err := json.NewDecoder(r.Body).Decode(&cart); err != nil {
httpErrorAndLog(w, err, errReadBody, errClientNotice)
return
}
code := GenerateCode()
email, err := UpdateSiteAuth(cart.Directory, code)
if err != nil {
httpErrorAndLog(w, err, errAuthGen, errClientNotice)
return
}
if err := SendAuthEmail(email, code); err != nil {
httpErrorAndLog(w, err, errAuthEmail, errClientNotice)
return
}
return
}
func ConfirmChangesHandler(w http.ResponseWriter, r *http.Request) {
errClientNotice := "Error handling confirm changes request"
var cart struct {
Directory string `json:"directory"`
Code string `json:"auth_code"`
EditorData json.RawMessage `json:"editor_data"`
}
if err := json.NewDecoder(r.Body).Decode(&cart); err != nil {
httpErrorAndLog(w, err, errReadBody, errClientNotice)
return
}
pkey, err := ValidateSiteAuth(cart.Directory, cart.Code)
if err != nil {
httpErrorAndLog(w, err, errAuthValidate, errClientNotice)
return
}
if err := UpdateSite(pkey, cart.EditorData); err != nil {
httpErrorAndLog(w, err, errUpdateSite, errClientNotice)
return return
} }