Skip to content

Commit

Permalink
Merge pull request demo-apps#5 from demo-apps/5-registration
Browse files Browse the repository at this point in the history
Allow user registration
  • Loading branch information
kulshekhar authored Jun 14, 2016
2 parents fe0dd8b + 3744354 commit 242a2fa
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 0 deletions.
3 changes: 3 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin"
)

var tmpUserList []user
var tmpArticleList []article

// This function is used to do setup before executing the test functions
Expand Down Expand Up @@ -46,10 +47,12 @@ func testHTTPResponse(t *testing.T, r *gin.Engine, req *http.Request, f func(w *
// This function is used to store the main lists into the temporary one
// for testing
func saveLists() {
tmpUserList = userList
tmpArticleList = articleList
}

// This function is used to restore the main lists from the temporary one
func restoreLists() {
userList = tmpUserList
articleList = tmpArticleList
}
48 changes: 48 additions & 0 deletions handlers.user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// handlers.user.go

package main

import (
"math/rand"
"net/http"
"strconv"

"github.com/gin-gonic/gin"
)

func generateSessionToken() string {
// We're using a random 16 character string as the session token
// This is NOT a secure way of generating session tokens
// DO NOT USE THIS IN PRODUCTION
return strconv.FormatInt(rand.Int63(), 16)
}

func showRegistrationPage(c *gin.Context) {
// Call the render function with the name of the template to render
render(c, gin.H{
"title": "Register"}, "register.html")
}

func register(c *gin.Context) {
// Obtain the POSTed username and password values
username := c.PostForm("username")
password := c.PostForm("password")

if _, err := registerNewUser(username, password); err == nil {
// If the user is created, set the token in a cookie and log the user in
token := generateSessionToken()
c.SetCookie("token", token, 3600, "", "", false, true)
c.Set("is_logged_in", true)

render(c, gin.H{
"title": "Successful registration & Login"}, "login-successful.html")

} else {
// If the username/password combination is invalid,
// show the error message on the login page
c.HTML(http.StatusBadRequest, "register.html", gin.H{
"ErrorTitle": "Registration Failed",
"ErrorMessage": err.Error()})

}
}
114 changes: 114 additions & 0 deletions handlers.user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// handlers.user_test.go

package main

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
)

// Test that a GET request to the registration page returns the registration
// page with the HTTP code 200 for an unauthenticated user
func TestShowRegistrationPageUnauthenticated(t *testing.T) {
r := getRouter(true)

// Define the route similar to its definition in the routes file
r.GET("/u/register", showRegistrationPage)

// Create a request to send to the above route
req, _ := http.NewRequest("GET", "/u/register", nil)

testHTTPResponse(t, r, req, func(w *httptest.ResponseRecorder) bool {
// Test that the http status code is 200
statusOK := w.Code == http.StatusOK

// Test that the page title is "Login"
p, err := ioutil.ReadAll(w.Body)
pageOK := err == nil && strings.Index(string(p), "<title>Register</title>") > 0

return statusOK && pageOK
})
}

// Test that a POST request to register returns a success message for
// an unauthenticated user
func TestRegisterUnauthenticated(t *testing.T) {
// Create a response recorder
w := httptest.NewRecorder()

// Get a new router
r := getRouter(true)

// Define the route similar to its definition in the routes file
r.POST("/u/register", register)

// Create a request to send to the above route
registrationPayload := getRegistrationPOSTPayload()
req, _ := http.NewRequest("POST", "/u/register", strings.NewReader(registrationPayload))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(registrationPayload)))

// Create the service and process the above request.
r.ServeHTTP(w, req)

// Test that the http status code is 200
if w.Code != http.StatusOK {
t.Fail()
}

// Test that the page title is "Successful registration &amp; Login"
// You can carry out a lot more detailed tests using libraries that can
// parse and process HTML pages
p, err := ioutil.ReadAll(w.Body)
if err != nil || strings.Index(string(p), "<title>Successful registration &amp; Login</title>") < 0 {
t.Fail()
}
}

// Test that a POST request to register returns a an error when
// the username is already in use
func TestRegisterUnauthenticatedUnavailableUsername(t *testing.T) {
// Create a response recorder
w := httptest.NewRecorder()

// Get a new router
r := getRouter(true)

// Define the route similar to its definition in the routes file
r.POST("/u/register", register)

// Create a request to send to the above route
registrationPayload := getLoginPOSTPayload()
req, _ := http.NewRequest("POST", "/u/register", strings.NewReader(registrationPayload))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(registrationPayload)))

// Create the service and process the above request.
r.ServeHTTP(w, req)

// Test that the http status code is 400
if w.Code != http.StatusBadRequest {
t.Fail()
}
}

func getLoginPOSTPayload() string {
params := url.Values{}
params.Add("username", "user1")
params.Add("password", "pass1")

return params.Encode()
}

func getRegistrationPOSTPayload() string {
params := url.Values{}
params.Add("username", "u1")
params.Add("password", "p1")

return params.Encode()
}
51 changes: 51 additions & 0 deletions models.user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// models.user.go

package main

import (
"errors"
"strings"
)

type user struct {
Username string `json:"username"`
Password string `json:"-"`
}

// For this demo, we're storing the user list in memory
// We also have some users predefined.
// In a real application, this list will most likely be fetched
// from a database. Moreover, in production settings, you should
// store passwords securely by salting and hashing them instead
// of using them as we're doing in this demo
var userList = []user{
user{Username: "user1", Password: "pass1"},
user{Username: "user2", Password: "pass2"},
user{Username: "user3", Password: "pass3"},
}

// Register a new user with the given username and password
// NOTE: For this demo, we
func registerNewUser(username, password string) (*user, error) {
if strings.TrimSpace(password) == "" {
return nil, errors.New("The password can't be empty")
} else if !isUsernameAvailable(username) {
return nil, errors.New("The username isn't available")
}

u := user{Username: username, Password: password}

userList = append(userList, u)

return &u, nil
}

// Check if the supplied username is available
func isUsernameAvailable(username string) bool {
for _, u := range userList {
if u.Username == username {
return false
}
}
return true
}
65 changes: 65 additions & 0 deletions models.user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// models.user_test.go

package main

import "testing"


// Test if a new user can be registered with valid username/password
func TestValidUserRegistration(t *testing.T) {
saveLists()

u, err := registerNewUser("newuser", "newpass")

if err != nil || u.Username == "" {
t.Fail()
}

restoreLists()
}

// Test that a new user cannot be registered with invalid username/password
func TestInvalidUserRegistration(t *testing.T) {
saveLists()

// Try to register a user with a used username
u, err := registerNewUser("user1", "pass1")

if err == nil || u != nil {
t.Fail()
}

// Try to register with a blank password
u, err = registerNewUser("newuser", "")

if err == nil || u != nil {
t.Fail()
}

restoreLists()
}

// Test the function that checks for username availability
func TestUsernameAvailability(t *testing.T) {
saveLists()

// This username should be available
if !isUsernameAvailable("newuser") {
t.Fail()
}

// This username should not be available
if isUsernameAvailable("user1") {
t.Fail()
}

// Register a new user
registerNewUser("newuser", "newpass")

// This newly registered username should not be available
if isUsernameAvailable("newuser") {
t.Fail()
}

restoreLists()
}
13 changes: 13 additions & 0 deletions routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ func initializeRoutes() {
// Handle the index route
router.GET("/", showIndexPage)

// Group user related routes together
userRoutes := router.Group("/u")
{
// Handle the GET requests at /u/register
// Show the registration page
// Ensure that the user is not logged in by using the middleware
userRoutes.GET("/register", showRegistrationPage)

// Handle POST requests at /u/register
// Ensure that the user is not logged in by using the middleware
userRoutes.POST("/register", register)
}

// Handle GET requests at /article/view/some_article_id
router.GET("/article/view/:article_id", getArticle)

Expand Down
11 changes: 11 additions & 0 deletions templates/login-successful.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!--login-successful.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

<div>
You have successfully logged in.
</div>

<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}
3 changes: 3 additions & 0 deletions templates/menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
Home
</a>
</div>
<ul class="nav navbar-nav">
<li><a href="/u/register">Register</a></li>
</ul>
</div>
</nav>
33 changes: 33 additions & 0 deletions templates/register.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!--register.html-->

<!--Embed the header.html template at this location-->
{{ template "header.html" .}}

<h1>Register</h1>

<div class="panel panel-default col-sm-6">
<div class="panel-body">
<!--If there's an error, display the error-->
{{ if .ErrorTitle}}
<p class="bg-danger">
{{.ErrorTitle}}: {{.ErrorMessage}}
</p>
{{end}}
<!--Create a form that POSTs to the `/u/register` route-->
<form class="form" action="/u/register" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password" class="form-control" id="password" placeholder="Password">
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>


<!--Embed the footer.html template at this location-->
{{ template "footer.html" .}}

0 comments on commit 242a2fa

Please sign in to comment.