Browse Source

Make user login based on database entries

Toni Fadjukoff 7 years ago
parent
commit
0816f45820
7 changed files with 158 additions and 5 deletions
  1. 6 1
      .gitignore
  2. 79 3
      auth.go
  3. 32 0
      auth_test.go
  4. 15 0
      sql/create-database.sql
  5. 19 0
      sql/database.sql
  6. 5 0
      sql/readme.txt
  7. 2 1
      web.go

+ 6 - 1
.gitignore View File

@@ -1 +1,6 @@
1
-credentials.json
1
+credentials.json
2
+web/browserconfig.xml
3
+web/fonts
4
+web/css/bootstrap.css
5
+web/js/vender/bootstrap.js
6
+sql/database.custom.sql

+ 79 - 3
auth.go View File

@@ -1,12 +1,18 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"container/heap"
5 6
 	"crypto/rand"
7
+	"crypto/sha1"
8
+	"database/sql"
6 9
 	"encoding/base64"
7 10
 	"errors"
11
+	_ "github.com/lib/pq"
12
+	"golang.org/x/crypto/pbkdf2"
8 13
 	"log"
9 14
 	"net/http"
15
+	"strings"
10 16
 	"time"
11 17
 )
12 18
 
@@ -52,6 +58,7 @@ func (pq *SessionQueue) Pop() interface{} {
52 58
 var (
53 59
 	sessions     = make(map[string]*SessionData)
54 60
 	sessionQueue = make(SessionQueue, 0)
61
+	db           *sql.DB
55 62
 )
56 63
 
57 64
 func sessionExpirer() {
@@ -66,6 +73,17 @@ func sessionExpirer() {
66 73
 
67 74
 func init() {
68 75
 	go sessionExpirer()
76
+
77
+	connStr := "user=levyraati password=levyraati dbname=levyraati sslmode=disable"
78
+	var err error
79
+	db, err = sql.Open("postgres", connStr)
80
+	if err != nil {
81
+		log.Fatal(err)
82
+	}
83
+	_, err = db.Query("SELECT 1")
84
+	if err != nil {
85
+		log.Fatal(err)
86
+	}
69 87
 }
70 88
 
71 89
 func addSession(data *SessionData) {
@@ -73,12 +91,70 @@ func addSession(data *SessionData) {
73 91
 	heap.Push(&sessionQueue, data)
74 92
 }
75 93
 
76
-func userOk(username, password string) bool {
77
-	return (username == "Lamperi" && password == "paskaa")
94
+func generateHash(password string) (string, error) {
95
+	salt := make([]byte, 11)
96
+
97
+	if _, err := rand.Read(salt); err != nil {
98
+		log.Println(err)
99
+		return "", errors.New("Couldn't generate random string")
100
+	}
101
+
102
+	passwordBytes := []byte(password)
103
+	key := hashPasswordSalt(passwordBytes, salt)
104
+
105
+	saltStr := base64.StdEncoding.EncodeToString(salt)
106
+	keyStr := base64.StdEncoding.EncodeToString(key)
107
+	return saltStr + "$" + keyStr, nil
108
+}
109
+
110
+func hashOk(password, hashed string) (bool, error) {
111
+	parts := strings.Split(hashed, "$")
112
+	if len(parts) != 2 {
113
+		return false, errors.New("Invalid data stored in database for password")
114
+	}
115
+	salt, err := base64.StdEncoding.DecodeString(parts[0])
116
+	if err != nil {
117
+		return false, err
118
+	}
119
+	passwordHash, err := base64.StdEncoding.DecodeString(parts[1])
120
+	if err != nil {
121
+		return false, err
122
+	}
123
+	if len(passwordHash) != 32 {
124
+		return false, errors.New("Password hash was not 32 bytes long")
125
+	}
126
+
127
+	key := hashPasswordSalt([]byte(password), salt)
128
+	return bytes.Equal(key, passwordHash), nil
129
+}
130
+
131
+func hashPasswordSalt(password, salt []byte) []byte {
132
+	return pbkdf2.Key(password, salt, 4096, 32, sha1.New)
133
+}
134
+
135
+func userOk(username, password string) (bool, error) {
136
+	var hash string
137
+	err := db.QueryRow("SELECT u.password FROM public.user u WHERE lower(u.username) = lower($1)", username).Scan(&hash)
138
+	if err != nil {
139
+		if err.Error() == "sql: no rows in result set" {
140
+			return false, nil
141
+		} else {
142
+			return false, err
143
+		}
144
+	}
145
+
146
+	ok, err := hashOk(password, hash)
147
+	if err != nil {
148
+		return false, err
149
+	}
150
+	return ok, nil
78 151
 }
79 152
 
80 153
 func tryLogin(username, password string, longerTime bool) (http.Cookie, error) {
81
-	if exists := userOk(username, password); !exists {
154
+	if exists, err := userOk(username, password); !exists {
155
+		if err != nil {
156
+			return http.Cookie{}, err
157
+		}
82 158
 		return http.Cookie{},
83 159
 			errors.New("The username or password you entered isn't correct.")
84 160
 	}

+ 32 - 0
auth_test.go View File

@@ -0,0 +1,32 @@
1
+package main
2
+
3
+import (
4
+	"testing"
5
+)
6
+
7
+func TestHash(t *testing.T) {
8
+	password := "kakka"
9
+	hash := "gLXSSyQqG9PVMGM=$HAl9O6m3eRuhWZIo8TgqNJO92mdts8he32N7OnX3Q9A="
10
+	ok, err := hashOk(password, hash)
11
+	if err != nil {
12
+		t.Fatal(err)
13
+	}
14
+	if !ok {
15
+		t.Fatal("Could not hash password")
16
+	}
17
+}
18
+
19
+func TestGeneratePasswordHash(t *testing.T) {
20
+	password := "toni"
21
+	hash, err := generateHash(password)
22
+	if err != nil {
23
+		t.Fatal("Error generating hash", err)
24
+	}
25
+	ok, err := hashOk(password, hash)
26
+	if err != nil {
27
+		t.Fatal("Error checking hash", err)
28
+	}
29
+	if !ok {
30
+		t.Fatal("Did not match!")
31
+	}
32
+}

+ 15 - 0
sql/create-database.sql View File

@@ -0,0 +1,15 @@
1
+
2
+-- User: levyraati
3
+
4
+-- DROP USER levyraati;
5
+
6
+CREATE USER levyraati;
7
+
8
+-- Database: levyraati
9
+
10
+-- DROP DATABASE levyraati;
11
+
12
+CREATE DATABASE levyraati
13
+    WITH 
14
+    OWNER = levyraati
15
+    ENCODING = 'UTF8';

+ 19 - 0
sql/database.sql View File

@@ -0,0 +1,19 @@
1
+-- Table: public."user"
2
+
3
+-- DROP TABLE public."user";
4
+
5
+CREATE TABLE public."user"
6
+(
7
+    id serial,
8
+    username text NOT NULL,
9
+    password character varying,
10
+    CONSTRAINT user_pkey PRIMARY KEY (id)
11
+);
12
+
13
+-- Index: user_lower_idx
14
+
15
+-- DROP INDEX public.user_lower_idx;
16
+
17
+CREATE UNIQUE INDEX user_lower_idx
18
+    ON public."user"
19
+    (lower(username));

+ 5 - 0
sql/readme.txt View File

@@ -0,0 +1,5 @@
1
+Create databases:
2
+
3
+$ sudo -u postgres psql < create-database.sql
4
+$ sudo adduser levyraati
5
+$ sudo -u levyraati psql < database.sql

+ 2 - 1
web.go View File

@@ -175,7 +175,8 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
175 175
 	cookie, err := tryLogin(username, password, longer)
176 176
 
177 177
 	if err != nil {
178
-		http.Error(w, err.Error(), http.StatusForbidden)
178
+		log.Println("Error while trying to login", err.Error())
179
+		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
179 180
 		return
180 181
 	}
181 182