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
|
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")
|
||||||
|
|
11
helpers.go
11
helpers.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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"
|
"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)
|
||||||
|
}
|
||||||
|
|
Reference in a new issue