Roughed in verify user. Will resist after restructuring users routes

This commit is contained in:
Matthew Dillon 2015-06-25 22:09:58 -08:00
parent e6733b2c2e
commit 05ded17aec
7 changed files with 126 additions and 7 deletions

View file

@ -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")

View file

@ -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
}

View file

@ -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;

View file

@ -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,

View file

@ -0,0 +1,5 @@
-- bactdb
-- Matthew R Dillon
DROP TABLE verification;

View 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)
);

View file

@ -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)
}