Implemented Registration

Created auth.GenerateSecureKey to handle generating API Keys
pull/24/head
Jordan 2014-02-04 18:39:01 -06:00
parent 359fa01c1c
commit 50292da53f
5 changed files with 144 additions and 27 deletions

View File

@ -3,8 +3,12 @@ package auth
import ( import (
"database/sql" "database/sql"
"encoding/gob" "encoding/gob"
"errors"
"fmt"
"io"
"net/http" "net/http"
"crypto/rand"
"code.google.com/p/go.crypto/bcrypt" "code.google.com/p/go.crypto/bcrypt"
ctx "github.com/gorilla/context" ctx "github.com/gorilla/context"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
@ -23,9 +27,9 @@ var Store = sessions.NewCookieStore(
[]byte(securecookie.GenerateRandomKey(64)), //Signing key []byte(securecookie.GenerateRandomKey(64)), //Signing key
[]byte(securecookie.GenerateRandomKey(32))) []byte(securecookie.GenerateRandomKey(32)))
// CheckLogin attempts to request a SQL record with the given username. var ErrUsernameTaken = errors.New("Username already taken")
// If successful, it then compares the received bcrypt hash.
// If all checks pass, this function sets the session id for later use. // Login attempts to login the user given a request.
func Login(r *http.Request) (bool, error) { func Login(r *http.Request) (bool, error) {
username, password := r.FormValue("username"), r.FormValue("password") username, password := r.FormValue("username"), r.FormValue("password")
session, _ := Store.Get(r, "gophish") session, _ := Store.Get(r, "gophish")
@ -50,6 +54,30 @@ func Login(r *http.Request) (bool, error) {
return true, nil return true, nil
} }
// Register attempts to register the user given a request.
func Register(r *http.Request) (bool, error) {
username, password := r.FormValue("username"), r.FormValue("password")
u := models.User{}
err := db.Conn.SelectOne(&u, "SELECT * FROM Users WHERE username=?", username)
if err != sql.ErrNoRows {
return false, ErrUsernameTaken
}
//If we've made it here, we should have a valid username given
//Let's create the password hash
h, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
u.Username = username
u.Hash = string(h)
u.APIKey = GenerateSecureKey()
if err != nil {
return false, err
}
err = db.Conn.Insert(&u)
if err != nil {
return false, err
}
return true, nil
}
// GetUserById returns the user that the given id corresponds to. If no user is found, an // GetUserById returns the user that the given id corresponds to. If no user is found, an
// error is thrown. // error is thrown.
func GetUserById(id int64) (models.User, error) { func GetUserById(id int64) (models.User, error) {
@ -71,3 +99,10 @@ func GetUserByAPIKey(key []byte) (models.User, error) {
} }
return u, nil return u, nil
} }
func GenerateSecureKey() string {
// Inspired from gorilla/securecookie
k := make([]byte, 32)
io.ReadFull(rand.Reader, k)
return fmt.Sprintf("%x", k)
}

View File

@ -1,10 +1,8 @@
package controllers package controllers
import ( import (
"crypto/rand"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
@ -12,6 +10,7 @@ import (
ctx "github.com/gorilla/context" ctx "github.com/gorilla/context"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/jordan-wright/gophish/auth"
"github.com/jordan-wright/gophish/db" "github.com/jordan-wright/gophish/db"
"github.com/jordan-wright/gophish/models" "github.com/jordan-wright/gophish/models"
) )
@ -36,11 +35,7 @@ func API_Reset(w http.ResponseWriter, r *http.Request) {
switch { switch {
case r.Method == "POST": case r.Method == "POST":
u := ctx.Get(r, "user").(models.User) u := ctx.Get(r, "user").(models.User)
// Inspired from gorilla/securecookie u.APIKey = auth.GenerateSecureKey()
k := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, k)
checkError(err, w, "Error setting new API key")
u.APIKey = fmt.Sprintf("%x", k)
db.Conn.Exec("UPDATE users SET api_key=? WHERE id=?", u.APIKey, u.Id) db.Conn.Exec("UPDATE users SET api_key=? WHERE id=?", u.APIKey, u.Id)
session := ctx.Get(r, "session").(*sessions.Session) session := ctx.Get(r, "session").(*sessions.Session)
session.AddFlash(models.Flash{ session.AddFlash(models.Flash{
@ -75,19 +70,14 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) {
c := models.Campaign{} c := models.Campaign{}
// Put the request into a campaign // Put the request into a campaign
err := json.NewDecoder(r.Body).Decode(&c) err := json.NewDecoder(r.Body).Decode(&c)
checkError(err, w, "Invalid Request") if checkError(err, w, "Invalid Request") {
return
}
// Fill in the details // Fill in the details
c.CreatedDate = time.Now() c.CreatedDate = time.Now()
c.CompletedDate = time.Time{} c.CompletedDate = time.Time{}
c.Status = IN_PROGRESS c.Status = IN_PROGRESS
c.Uid, err = db.Conn.SelectInt("SELECT id FROM users WHERE api_key=?", ctx.Get(r, "api_key")) c.Uid = ctx.Get(r, "user_id").(int64)
if c.Uid == 0 {
http.Error(w, "Error: Invalid API Key", http.StatusInternalServerError)
return
}
if checkError(err, w, "Invalid API Key") {
return
}
// Insert into the DB // Insert into the DB
err = db.Conn.Insert(&c) err = db.Conn.Insert(&c)
if checkError(err, w, "Cannot insert campaign into database") { if checkError(err, w, "Cannot insert campaign into database") {
@ -133,7 +123,49 @@ func API_Campaigns_Id_Launch(w http.ResponseWriter, r *http.Request) {
// API_Groups returns details about the requested group. If the campaign is not // API_Groups returns details about the requested group. If the campaign is not
// valid, API_Groups returns null. // valid, API_Groups returns null.
func API_Groups(w http.ResponseWriter, r *http.Request) { func API_Groups(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", 302) switch {
case r.Method == "GET":
gs := []models.Group{}
_, err := db.Conn.Select(&gs, "SELECT g.id, g.name, g.modified_date FROM groups g, users u WHERE g.uid=u.id AND u.api_key=?", ctx.Get(r, "api_key"))
if err != nil {
fmt.Println(err)
}
for _, g := range gs {
_, err := db.Conn.Select(&g.Targets, "SELECT t.id t.email FROM targets t, groups g, group_targets gt WHERE gt.gid=? AND gt.tid=t.id", g.Id)
if checkError(err, w, "Error looking up groups") {
return
}
}
gj, err := json.MarshalIndent(gs, "", " ")
if checkError(err, w, "Error looking up groups") {
return
}
writeJSON(w, gj)
//POST: Create a new group and return it as JSON
case r.Method == "POST":
g := models.Group{}
// Put the request into a group
err := json.NewDecoder(r.Body).Decode(&g)
if checkError(err, w, "Invalid Request") {
return
}
// Check to make sure targets were specified
if len(g.Targets) == 0 {
http.Error(w, "Error: No targets specified", http.StatusInternalServerError)
return
}
g.ModifiedDate = time.Now()
// Insert into the DB
err = db.Conn.Insert(&g)
if checkError(err, w, "Cannot insert group into database") {
return
}
gj, err := json.MarshalIndent(g, "", " ")
if checkError(err, w, "Error creating JSON response") {
return
}
writeJSON(w, gj)
}
} }
// API_Campaigns_Id returns details about the requested campaign. If the campaign is not // API_Campaigns_Id returns details about the requested campaign. If the campaign is not

View File

@ -61,7 +61,48 @@ func Use(handler http.HandlerFunc, mid ...func(http.Handler) http.HandlerFunc) h
func Register(w http.ResponseWriter, r *http.Request) { func Register(w http.ResponseWriter, r *http.Request) {
// If it is a post request, attempt to register the account // If it is a post request, attempt to register the account
// Now that we are all registered, we can log the user in // Now that we are all registered, we can log the user in
Login(w, r) params := struct {
Title string
Flashes []interface{}
User models.User
Token string
}{Title: "Register", Token: nosurf.Token(r)}
session := ctx.Get(r, "session").(*sessions.Session)
switch {
case r.Method == "GET":
params.Flashes = session.Flashes()
session.Save(r, w)
getTemplate(w, "register").ExecuteTemplate(w, "base", params)
case r.Method == "POST":
//Attempt to register
succ, err := auth.Register(r)
//If we've registered, redirect to the login page
if succ {
session.AddFlash(models.Flash{
Type: "success",
Message: "Registration successful!.",
})
session.Save(r, w)
http.Redirect(w, r, "/login", 302)
} else {
// Check the error
m := ""
if err == auth.ErrUsernameTaken {
m = "Username already taken"
} else {
m = "Unknown error - please try again"
fmt.Println(err)
}
fmt.Println(m)
session.AddFlash(models.Flash{
Type: "danger",
Message: m,
})
session.Save(r, w)
http.Redirect(w, r, "/register", 302)
}
}
} }
func Logout(w http.ResponseWriter, r *http.Request) { func Logout(w http.ResponseWriter, r *http.Request) {
@ -134,10 +175,6 @@ func Login(w http.ResponseWriter, r *http.Request) {
getTemplate(w, "login").ExecuteTemplate(w, "base", params) getTemplate(w, "login").ExecuteTemplate(w, "base", params)
case r.Method == "POST": case r.Method == "POST":
//Attempt to login //Attempt to login
err := r.ParseForm()
if checkError(err, w, "Error parsing request") {
return
}
succ, err := auth.Login(r) succ, err := auth.Login(r)
if checkError(err, w, "Error logging in") { if checkError(err, w, "Error logging in") {
return return

View File

@ -30,9 +30,9 @@ func Setup() error {
fmt.Println("Database not found, recreating...") fmt.Println("Database not found, recreating...")
createTablesSQL := []string{ createTablesSQL := []string{
//Create tables //Create tables
`CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, hash VARCHAR(60) NOT NULL, api_key VARCHAR(32));`, `CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, hash VARCHAR(60) NOT NULL, api_key VARCHAR(32), UNIQUE(username), UNIQUE(api_key));`,
`CREATE TABLE campaigns (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_date TIMESTAMP NOT NULL, completed_date TIMESTAMP, template TEXT, status TEXT NOT NULL, uid INTEGER, FOREIGN KEY (uid) REFERENCES users(id));`, `CREATE TABLE campaigns (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_date TIMESTAMP NOT NULL, completed_date TIMESTAMP, template TEXT, status TEXT NOT NULL, uid INTEGER, FOREIGN KEY (uid) REFERENCES users(id));`,
`CREATE TABLE targets (id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL);`, `CREATE TABLE targets (id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL, UNIQUE(address));`,
`CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, modified_date TIMESTAMP NOT NULL);`, `CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, modified_date TIMESTAMP NOT NULL);`,
`CREATE TABLE user_groups (uid INTEGER NOT NULL, gid INTEGER NOT NULL, FOREIGN KEY (uid) REFERENCES users(id), FOREIGN KEY (gid) REFERENCES groups(id), UNIQUE(uid, gid))`, `CREATE TABLE user_groups (uid INTEGER NOT NULL, gid INTEGER NOT NULL, FOREIGN KEY (uid) REFERENCES users(id), FOREIGN KEY (gid) REFERENCES groups(id), UNIQUE(uid, gid))`,
`CREATE TABLE group_targets (gid INTEGER NOT NULL, tid INTEGER NOT NULL, FOREIGN KEY (gid) REFERENCES groups(id), FOREIGN KEY (tid) REFERENCES targets(id), UNIQUE(gid, tid));`, `CREATE TABLE group_targets (gid INTEGER NOT NULL, tid INTEGER NOT NULL, FOREIGN KEY (gid) REFERENCES groups(id), FOREIGN KEY (tid) REFERENCES targets(id), UNIQUE(gid, tid));`,

13
templates/register.html Normal file
View File

@ -0,0 +1,13 @@
{{%define "content"%}}
<div class="container">
<form class="form-signin" action="/register" method="POST">
<img id="logo" src="/images/logo.png" />
<h2 class="form-signin-heading">Please register below</h2>
{{%template "flashes" .Flashes%}}
<input type="text" name="username" class="form-control" placeholder="Username" required autofocus/>
<input type="password" name="password" class="form-control" placeholder="Password" required/>
<input type="hidden" name="csrf_token" value={{%.Token%}}/>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
{{%end%}}