Roughed in verify user. Will resist after restructuring users routes
This commit is contained in:
parent
e6733b2c2e
commit
05ded17aec
7 changed files with 126 additions and 7 deletions
|
@ -23,6 +23,7 @@ type Claims struct {
|
|||
Role string
|
||||
Iat int64
|
||||
Exp int64
|
||||
Ref string
|
||||
}
|
||||
|
||||
func Handler() http.Handler {
|
||||
|
@ -39,6 +40,7 @@ func Handler() http.Handler {
|
|||
"role": user.Role,
|
||||
"iat": currentTime.Unix(),
|
||||
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
|
||||
"ref": "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -78,6 +80,7 @@ func Handler() http.Handler {
|
|||
// Non-auth routes
|
||||
m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST")
|
||||
m.Handle("/users", errorHandler(handleCreater(userService))).Methods("POST")
|
||||
m.Handle("/users/verify/{Nonce}", http.HandlerFunc(handleUserVerify)).Methods("GET")
|
||||
|
||||
// Auth routes
|
||||
m.Handle("/users", j.Secure(errorHandler(handleLister(userService)), verifyClaims)).Methods("GET")
|
||||
|
|
11
helpers.go
11
helpers.go
|
@ -1,6 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -89,3 +91,12 @@ func verifyPassword(s string) (sevenOrMore, number, upper bool) {
|
|||
sevenOrMore = letters >= 7
|
||||
return
|
||||
}
|
||||
|
||||
func generateNonce() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
-- bactdb
|
||||
-- Matthew R Dillon
|
||||
|
||||
-- Need to include something to keep gomigrate happy.
|
||||
-- SELECT 1;
|
||||
|
||||
DROP TABLE users;
|
||||
|
||||
DROP TYPE e_roles;
|
||||
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
-- bactdb
|
||||
-- Matthew R Dillon
|
||||
|
||||
CREATE TYPE e_roles AS ENUM('R', 'W', 'A');
|
||||
-- 'R': read-only, default
|
||||
-- 'W': read-write
|
||||
-- 'A': administrator
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'e_roles') THEN
|
||||
CREATE TYPE e_roles AS ENUM('R', 'W', 'A');
|
||||
-- 'R': read-only, default
|
||||
-- 'W': read-write
|
||||
-- 'A': administrator
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
CREATE TABLE users (
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGSERIAL NOT NULL,
|
||||
email CHARACTER VARYING(254) NOT NULL UNIQUE,
|
||||
password CHARACTER(60) NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
role e_roles DEFAULT 'R' NOT NULL,
|
||||
verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
|
|
5
migrations/00011_AddVerification_down.sql
Normal file
5
migrations/00011_AddVerification_down.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
-- bactdb
|
||||
-- Matthew R Dillon
|
||||
|
||||
DROP TABLE verification;
|
||||
|
13
migrations/00011_AddVerification_up.sql
Normal file
13
migrations/00011_AddVerification_up.sql
Normal file
|
@ -0,0 +1,13 @@
|
|||
-- bactdb
|
||||
-- Matthew R Dillon
|
||||
|
||||
CREATE TABLE verification (
|
||||
user_id BIGINT NOT NULL,
|
||||
nonce CHARACTER(60) NOT NULL UNIQUE,
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
|
||||
CONSTRAINT verification_pkey PRIMARY KEY (user_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
81
users.go
81
users.go
|
@ -4,10 +4,15 @@ import (
|
|||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
@ -17,6 +22,8 @@ var (
|
|||
ErrUserNotUpdated = errors.New("User not updated")
|
||||
ErrUserNotUpdatedJSON = newJSONError(ErrUserNotUpdated, http.StatusBadRequest)
|
||||
ErrInvalidEmailOrPassword = errors.New("Invalid email or password")
|
||||
ErrEmailAddressTaken = errors.New("Email address already registered")
|
||||
ErrEmailAddressTakenJSON = newJSONError(ErrEmailAddressTaken, http.StatusBadRequest)
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -31,6 +38,7 @@ type User struct {
|
|||
Password string `db:"password" json:"password,omitempty"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Role string `db:"role" json:"role"`
|
||||
Verified bool `db:"verified" json:"-"`
|
||||
CreatedAt NullTime `db:"created_at" json:"createdAt"`
|
||||
UpdatedAt NullTime `db:"updated_at" json:"updatedAt"`
|
||||
DeletedAt NullTime `db:"deleted_at" json:"deletedAt"`
|
||||
|
@ -126,7 +134,10 @@ func (u UserService) list(val *url.Values) (entity, *appError) {
|
|||
|
||||
users := make(Users, 0)
|
||||
sql := `SELECT id, email, 'password' AS password, name, role,
|
||||
created_at, updated_at, deleted_at FROM users;`
|
||||
created_at, updated_at, deleted_at
|
||||
FROM users
|
||||
WHERE verified IS NOT FALSE
|
||||
AND deleted_at IS NULL;`
|
||||
if err := DBH.Select(&users, sql); err != nil {
|
||||
return nil, newJSONError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -136,7 +147,11 @@ func (u UserService) list(val *url.Values) (entity, *appError) {
|
|||
func (u UserService) get(id int64, genus string) (entity, *appError) {
|
||||
var user User
|
||||
q := `SELECT id, email, 'password' AS password, name, role,
|
||||
created_at, updated_at, deleted_at FROM users WHERE id=$1;`
|
||||
created_at, updated_at, deleted_at
|
||||
FROM users
|
||||
WHERE id=$1
|
||||
AND verified IS NOT FALSE
|
||||
AND deleted_at IS NULL;`
|
||||
if err := DBH.SelectOne(&user, q, id); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrUserNotFoundJSON
|
||||
|
@ -175,10 +190,29 @@ func (u UserService) create(e *entity, claims Claims) *appError {
|
|||
}
|
||||
user.Password = string(hash)
|
||||
user.Role = "R"
|
||||
user.Verified = false
|
||||
|
||||
if err := DBH.Insert(user); err != nil {
|
||||
if err, ok := err.(*pq.Error); ok {
|
||||
if err.Code == "23505" {
|
||||
return ErrEmailAddressTakenJSON
|
||||
}
|
||||
}
|
||||
return newJSONError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
user.Password = "password" // don't want to send the hashed PW back to the client
|
||||
|
||||
q := `INSERT INTO verification (user_id, nonce, created_at) VALUES ($1, $2, $3);`
|
||||
nonce, err := generateNonce()
|
||||
if err != nil {
|
||||
return newJSONError(err, http.StatusInternalServerError)
|
||||
}
|
||||
_, err = DBH.Exec(q, user.Id, nonce, ct)
|
||||
if err != nil {
|
||||
return newJSONError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -207,3 +241,46 @@ func dbGetUserByEmail(email string) (*User, error) {
|
|||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func handleUserVerify(w http.ResponseWriter, r *http.Request) {
|
||||
nonce := mux.Vars(r)["Nonce"]
|
||||
q := `SELECT user_id FROM verification WHERE nonce=$1;`
|
||||
|
||||
var user_id int64
|
||||
if err := DBH.SelectOne(&user_id, q, nonce); err != nil {
|
||||
log.Printf("%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if user_id == 0 {
|
||||
fmt.Fprintln(w, "NOT FOUND/EXPIRED")
|
||||
return
|
||||
}
|
||||
|
||||
var user User
|
||||
if err := DBH.Get(&user, user_id); err != nil {
|
||||
fmt.Printf("%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
user.UpdatedAt = currentTime()
|
||||
user.Verified = true
|
||||
|
||||
count, err := DBH.Update(&user)
|
||||
if err != nil {
|
||||
fmt.Printf("%+v", err)
|
||||
return
|
||||
}
|
||||
if count != 1 {
|
||||
fmt.Printf("%+v", "hmm")
|
||||
return
|
||||
}
|
||||
|
||||
q = `DELETE FROM verification WHERE user_id=$1;`
|
||||
_, err = DBH.Exec(q, user_id)
|
||||
if err != nil {
|
||||
log.Printf("%+v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, user_id)
|
||||
}
|
||||
|
|
Reference in a new issue