mirror of
https://github.com/tavo-wasd-gh/conex-builder.git
synced 2025-06-07 12:13:30 -06:00
send authentication emailo
This commit is contained in:
parent
8433d662ad
commit
ecb0e58cae
8 changed files with 201 additions and 35 deletions
5
Makefile
5
Makefile
|
@ -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}
|
||||||
|
|
||||||
|
|
19
README.org
19
README.org
|
@ -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
0
public/client.js
Normal 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
39
server/auth.go
Normal 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
|
||||||
|
}
|
88
server/db.go
88
server/db.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue