Skip to content

Commit 040c7f2

Browse files
author
user
committed
Allow users to login and logout
1 parent 242a2fa commit 040c7f2

File tree

7 files changed

+204
-2
lines changed

7 files changed

+204
-2
lines changed

handlers.user.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,51 @@ import (
1010
"github.com/gin-gonic/gin"
1111
)
1212

13+
func showLoginPage(c *gin.Context) {
14+
// Call the render function with the name of the template to render
15+
render(c, gin.H{
16+
"title": "Login",
17+
}, "login.html")
18+
}
19+
20+
func performLogin(c *gin.Context) {
21+
// Obtain the POSTed username and password values
22+
username := c.PostForm("username")
23+
password := c.PostForm("password")
24+
25+
// Check if the username/password combination is valid
26+
if isUserValid(username, password) {
27+
// If the username/password is valid set the token in a cookie
28+
token := generateSessionToken()
29+
c.SetCookie("token", token, 3600, "", "", false, true)
30+
31+
render(c, gin.H{
32+
"title": "Successful Login"}, "login-successful.html")
33+
34+
} else {
35+
// If the username/password combination is invalid,
36+
// show the error message on the login page
37+
c.HTML(http.StatusBadRequest, "login.html", gin.H{
38+
"ErrorTitle": "Login Failed",
39+
"ErrorMessage": "Invalid credentials provided"})
40+
}
41+
}
42+
1343
func generateSessionToken() string {
1444
// We're using a random 16 character string as the session token
1545
// This is NOT a secure way of generating session tokens
1646
// DO NOT USE THIS IN PRODUCTION
1747
return strconv.FormatInt(rand.Int63(), 16)
1848
}
1949

50+
func logout(c *gin.Context) {
51+
// Clear the cookie
52+
c.SetCookie("token", "", -1, "", "", false, true)
53+
54+
// Redirect to the home page
55+
c.Redirect(http.StatusTemporaryRedirect, "/")
56+
}
57+
2058
func showRegistrationPage(c *gin.Context) {
2159
// Call the render function with the name of the template to render
2260
render(c, gin.H{

handlers.user_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,91 @@ import (
1212
"testing"
1313
)
1414

15+
// Test that a GET request to the login page returns the login page with
16+
// the HTTP code 200 for an unauthenticated user
17+
func TestShowLoginPageUnauthenticated(t *testing.T) {
18+
r := getRouter(true)
19+
20+
// Define the route similar to its definition in the routes file
21+
r.GET("/u/login", showLoginPage)
22+
23+
// Create a request to send to the above route
24+
req, _ := http.NewRequest("GET", "/u/login", nil)
25+
26+
testHTTPResponse(t, r, req, func(w *httptest.ResponseRecorder) bool {
27+
// Test that the http status code is 200
28+
statusOK := w.Code == http.StatusOK
29+
30+
// Test that the page title is "Login"
31+
p, err := ioutil.ReadAll(w.Body)
32+
pageOK := err == nil && strings.Index(string(p), "<title>Login</title>") > 0
33+
34+
return statusOK && pageOK
35+
})
36+
}
37+
38+
// Test that a POST request to login returns a success message for
39+
// an unauthenticated user
40+
func TestLoginUnauthenticated(t *testing.T) {
41+
// Create a response recorder
42+
w := httptest.NewRecorder()
43+
44+
// Get a new router
45+
r := getRouter(true)
46+
47+
// Define the route similar to its definition in the routes file
48+
r.POST("/u/login", performLogin)
49+
50+
// Create a request to send to the above route
51+
loginPayload := getLoginPOSTPayload()
52+
req, _ := http.NewRequest("POST", "/u/login", strings.NewReader(loginPayload))
53+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
54+
req.Header.Add("Content-Length", strconv.Itoa(len(loginPayload)))
55+
56+
// Create the service and process the above request.
57+
r.ServeHTTP(w, req)
58+
59+
// Test that the http status code is 200
60+
if w.Code != http.StatusOK {
61+
t.Fail()
62+
}
63+
64+
// Test that the page title is "Successful Login"
65+
// You can carry out a lot more detailed tests using libraries that can
66+
// parse and process HTML pages
67+
p, err := ioutil.ReadAll(w.Body)
68+
if err != nil || strings.Index(string(p), "<title>Successful Login</title>") < 0 {
69+
t.Fail()
70+
}
71+
}
72+
73+
// Test that a POST request to login returns an error when using
74+
// incorrect credentials
75+
func TestLoginUnauthenticatedIncorrectCredentials(t *testing.T) {
76+
// Create a response recorder
77+
w := httptest.NewRecorder()
78+
79+
// Get a new router
80+
r := getRouter(true)
81+
82+
// Define the route similar to its definition in the routes file
83+
r.POST("/u/login", performLogin)
84+
85+
// Create a request to send to the above route
86+
loginPayload := getRegistrationPOSTPayload()
87+
req, _ := http.NewRequest("POST", "/u/login", strings.NewReader(loginPayload))
88+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
89+
req.Header.Add("Content-Length", strconv.Itoa(len(loginPayload)))
90+
91+
// Create the service and process the above request.
92+
r.ServeHTTP(w, req)
93+
94+
// Test that the http status code is 200
95+
if w.Code != http.StatusBadRequest {
96+
t.Fail()
97+
}
98+
}
99+
15100
// Test that a GET request to the registration page returns the registration
16101
// page with the HTTP code 200 for an unauthenticated user
17102
func TestShowRegistrationPageUnauthenticated(t *testing.T) {

models.user.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ var userList = []user{
2424
user{Username: "user3", Password: "pass3"},
2525
}
2626

27+
// Check if the username and password combination is valid
28+
func isUserValid(username, password string) bool {
29+
for _, u := range userList {
30+
if u.Username == username && u.Password == password {
31+
return true
32+
}
33+
}
34+
return false
35+
}
36+
2737
// Register a new user with the given username and password
2838
// NOTE: For this demo, we
2939
func registerNewUser(username, password string) (*user, error) {

models.user_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ package main
44

55
import "testing"
66

7+
// Test the validity of different combinations of username/password
8+
func TestUserValidity(t *testing.T) {
9+
if !isUserValid("user1", "pass1") {
10+
t.Fail()
11+
}
12+
13+
if isUserValid("user2", "pass1") {
14+
t.Fail()
15+
}
16+
17+
if isUserValid("user1", "") {
18+
t.Fail()
19+
}
20+
21+
if isUserValid("", "pass1") {
22+
t.Fail()
23+
}
24+
25+
if isUserValid("User1", "pass1") {
26+
t.Fail()
27+
}
28+
}
729

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

routes.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ func initializeRoutes() {
1010
// Group user related routes together
1111
userRoutes := router.Group("/u")
1212
{
13+
// Handle the GET requests at /u/login
14+
// Show the login page
15+
// Ensure that the user is not logged in by using the middleware
16+
userRoutes.GET("/login", showLoginPage)
17+
18+
// Handle POST requests at /u/login
19+
// Ensure that the user is not logged in by using the middleware
20+
userRoutes.POST("/login", performLogin)
21+
22+
// Handle GET requests at /u/logout
23+
// Ensure that the user is logged in by using the middleware
24+
userRoutes.GET("/logout", logout)
25+
1326
// Handle the GET requests at /u/register
1427
// Show the registration page
15-
// Ensure that the user is not logged in by using the middleware
1628
userRoutes.GET("/register", showRegistrationPage)
1729

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

templates/login.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!--login.html-->
2+
3+
<!--Embed the header.html template at this location-->
4+
{{ template "header.html" .}}
5+
6+
<h1>Login</h1>
7+
8+
9+
<div class="panel panel-default col-sm-6">
10+
<div class="panel-body">
11+
<!--If there's an error, display the error-->
12+
{{ if .ErrorTitle}}
13+
<p class="bg-danger">
14+
{{.ErrorTitle}}: {{.ErrorMessage}}
15+
</p>
16+
{{end}}
17+
<!--Create a form that POSTs to the `/u/login` route-->
18+
<form class="form" action="/u/login" method="POST">
19+
<div class="form-group">
20+
<label for="username">Username</label>
21+
<input type="text" class="form-control" id="username" name="username" placeholder="Username">
22+
</div>
23+
<div class="form-group">
24+
<label for="password">Password</label>
25+
<input type="password" class="form-control" id="password" name="password" placeholder="Password">
26+
</div>
27+
<button type="submit" class="btn btn-primary">Login</button>
28+
</form>
29+
</div>
30+
</div>
31+
32+
33+
<!--Embed the footer.html template at this location-->
34+
{{ template "footer.html" .}}

templates/menu.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
</div>
1010
<ul class="nav navbar-nav">
1111
<li><a href="/u/register">Register</a></li>
12+
<li><a href="/u/login">Login</a></li>
13+
<li><a href="/u/logout">Logout</a></li>
1214
</ul>
1315
</div>
1416
</nav>

0 commit comments

Comments
 (0)