forked from demo-apps/go-gin-app
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request demo-apps#5 from demo-apps/5-registration
Allow user registration
- Loading branch information
Showing
9 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()}) | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 & 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 & 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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" .}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,5 +7,8 @@ | |
Home | ||
</a> | ||
</div> | ||
<ul class="nav navbar-nav"> | ||
<li><a href="/u/register">Register</a></li> | ||
</ul> | ||
</div> | ||
</nav> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" .}} |