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 Role string
Iat int64 Iat int64
Exp int64 Exp int64
Ref string
} }
func Handler() http.Handler { func Handler() http.Handler {
@ -39,6 +40,7 @@ func Handler() http.Handler {
"role": user.Role, "role": user.Role,
"iat": currentTime.Unix(), "iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(), "exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
"ref": "",
}, nil }, nil
} }
@ -78,6 +80,7 @@ func Handler() http.Handler {
// Non-auth routes // Non-auth routes
m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST") m.Handle("/authenticate", tokenHandler(j.GenerateToken())).Methods("POST")
m.Handle("/users", errorHandler(handleCreater(userService))).Methods("POST") m.Handle("/users", errorHandler(handleCreater(userService))).Methods("POST")
m.Handle("/users/verify/{Nonce}", http.HandlerFunc(handleUserVerify)).Methods("GET")
// Auth routes // Auth routes
m.Handle("/users", j.Secure(errorHandler(handleLister(userService)), verifyClaims)).Methods("GET") m.Handle("/users", j.Secure(errorHandler(handleLister(userService)), verifyClaims)).Methods("GET")

View file

@ -1,6 +1,8 @@
package main package main
import ( import (
"crypto/rand"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -89,3 +91,12 @@ func verifyPassword(s string) (sevenOrMore, number, upper bool) {
sevenOrMore = letters >= 7 sevenOrMore = letters >= 7
return 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 -- bactdb
-- Matthew R Dillon -- Matthew R Dillon
-- Need to include something to keep gomigrate happy.
-- SELECT 1;
DROP TABLE users; DROP TABLE users;
DROP TYPE e_roles; DROP TYPE e_roles;

View file

@ -1,17 +1,23 @@
-- bactdb -- bactdb
-- Matthew R Dillon -- Matthew R Dillon
CREATE TYPE e_roles AS ENUM('R', 'W', 'A'); DO $$
-- 'R': read-only, default BEGIN
-- 'W': read-write IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'e_roles') THEN
-- 'A': administrator 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, id BIGSERIAL NOT NULL,
email CHARACTER VARYING(254) NOT NULL UNIQUE, email CHARACTER VARYING(254) NOT NULL UNIQUE,
password CHARACTER(60) NOT NULL, password CHARACTER(60) NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
role e_roles DEFAULT 'R' NOT NULL, role e_roles DEFAULT 'R' NOT NULL,
verified BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_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" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"log"
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
"github.com/gorilla/mux"
"github.com/lib/pq"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -17,6 +22,8 @@ var (
ErrUserNotUpdated = errors.New("User not updated") ErrUserNotUpdated = errors.New("User not updated")
ErrUserNotUpdatedJSON = newJSONError(ErrUserNotUpdated, http.StatusBadRequest) ErrUserNotUpdatedJSON = newJSONError(ErrUserNotUpdated, http.StatusBadRequest)
ErrInvalidEmailOrPassword = errors.New("Invalid email or password") ErrInvalidEmailOrPassword = errors.New("Invalid email or password")
ErrEmailAddressTaken = errors.New("Email address already registered")
ErrEmailAddressTakenJSON = newJSONError(ErrEmailAddressTaken, http.StatusBadRequest)
) )
func init() { func init() {
@ -31,6 +38,7 @@ type User struct {
Password string `db:"password" json:"password,omitempty"` Password string `db:"password" json:"password,omitempty"`
Name string `db:"name" json:"name"` Name string `db:"name" json:"name"`
Role string `db:"role" json:"role"` Role string `db:"role" json:"role"`
Verified bool `db:"verified" json:"-"`
CreatedAt NullTime `db:"created_at" json:"createdAt"` CreatedAt NullTime `db:"created_at" json:"createdAt"`
UpdatedAt NullTime `db:"updated_at" json:"updatedAt"` UpdatedAt NullTime `db:"updated_at" json:"updatedAt"`
DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` DeletedAt NullTime `db:"deleted_at" json:"deletedAt"`
@ -126,7 +134,10 @@ func (u UserService) list(val *url.Values) (entity, *appError) {
users := make(Users, 0) users := make(Users, 0)
sql := `SELECT id, email, 'password' AS password, name, role, 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 { if err := DBH.Select(&users, sql); err != nil {
return nil, newJSONError(err, http.StatusInternalServerError) 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) { func (u UserService) get(id int64, genus string) (entity, *appError) {
var user User var user User
q := `SELECT id, email, 'password' AS password, name, role, 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 := DBH.SelectOne(&user, q, id); err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, ErrUserNotFoundJSON return nil, ErrUserNotFoundJSON
@ -175,10 +190,29 @@ func (u UserService) create(e *entity, claims Claims) *appError {
} }
user.Password = string(hash) user.Password = string(hash)
user.Role = "R" user.Role = "R"
user.Verified = false
if err := DBH.Insert(user); err != nil { 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) 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 return nil
} }
@ -207,3 +241,46 @@ func dbGetUserByEmail(email string) (*User, error) {
} }
return &user, nil 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)
}