-
-
Notifications
You must be signed in to change notification settings - Fork 457
/
main.go
347 lines (297 loc) Β· 9.64 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"os"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/csrf"
"github.com/gofiber/fiber/v2/middleware/session"
"github.com/gofiber/template/html/v2"
"golang.org/x/crypto/bcrypt"
)
// User represents a user in the dummy authentication system
type User struct {
Username string
Password string
}
// Dummy user database
var users map[string]User
func main() {
// In production, run the app on port 443 with TLS enabled
// or run the app behind a reverse proxy that handles TLS.
//
// It is also recommended that the csrf cookie is set to be
// Secure and HttpOnly and have the SameSite attribute set
// to Lax or Strict.
//
// In this example, we use the "__Host-" prefix for cookie names.
// This is suggested when your app uses secure connections (TLS).
// A cookie with this prefix is only accepted if it's secure,
// comes from a secure source, doesn't have a Domain attribute,
// and its Path attribute is "/".
// This makes these cookies "locked" to the domain.
//
// See the following for more details:
// https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
//
// It's recommended to use the "github.com/gofiber/fiber/v2/middleware/helmet"
// middleware to set headers to help prevent attacks such as XSS, man-in-the-middle,
// protocol downgrade, cookie hijacking, SSL stripping, clickjacking, etc.
// Never hardcode passwords in production code
hashedPasswords := make(map[string]string)
for username, password := range map[string]string{
"user1": "password1",
"user2": "password2",
} {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
panic(err)
}
hashedPasswords[username] = string(hashedPassword)
}
// Used to help prevent timing attacks
emptyHash, err := bcrypt.GenerateFromPassword([]byte(""), 10)
if err != nil {
panic(err)
}
emptyHashString := string(emptyHash)
users := make(map[string]User)
for username, hashedPassword := range hashedPasswords {
users[username] = User{Username: username, Password: hashedPassword}
}
// HTML templates
engine := html.New("./views", ".html")
// Create a Fiber app
app := fiber.New(fiber.Config{
Views: engine,
ViewsLayout: "layouts/main",
})
// Initialize a session store
sessConfig := session.Config{
Expiration: 30 * time.Minute, // Expire sessions after 30 minutes of inactivity
KeyLookup: "cookie:__Host-session", // Recommended to use the __Host- prefix when serving the app over TLS
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: "Lax",
}
store := session.New(sessConfig)
// CSRF Error handler
csrfErrorHandler := func(c *fiber.Ctx, err error) error {
// Log the error so we can track who is trying to perform CSRF attacks
// customize this to your needs
fmt.Printf("CSRF Error: %v Request: %v From: %v\n", err, c.OriginalURL(), c.IP())
// check accepted content types
switch c.Accepts("html", "json") {
case "json":
// Return a 403 Forbidden response for JSON requests
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "403 Forbidden",
})
case "html":
// Return a 403 Forbidden response for HTML requests
return c.Status(fiber.StatusForbidden).Render("error", fiber.Map{
"Title": "Error",
"Error": "403 Forbidden",
"ErrorCode": "403",
})
default:
// Return a 403 Forbidden response for all other requests
return c.Status(fiber.StatusForbidden).SendString("403 Forbidden")
}
}
// Configure the CSRF middleware
csrfConfig := csrf.Config{
Session: store,
KeyLookup: "form:csrf", // In this example, we will be using a hidden input field to store the CSRF token
CookieName: "__Host-csrf", // Recommended to use the __Host- prefix when serving the app over TLS
CookieSameSite: "Lax", // Recommended to set this to Lax or Strict
CookieSecure: true, // Recommended to set to true when serving the app over TLS
CookieHTTPOnly: true, // Recommended, otherwise if using JS framework recomend: false and KeyLookup: "header:X-CSRF-Token"
ContextKey: "csrf",
ErrorHandler: csrfErrorHandler,
Expiration: 30 * time.Minute,
}
csrfMiddleware := csrf.New(csrfConfig)
// Route for the root path
app.Get("/", func(c *fiber.Ctx) error {
// render the root page as HTML
return c.Render("index", fiber.Map{
"Title": "Index",
})
})
// Route for the login page
app.Get("/login", csrfMiddleware, func(c *fiber.Ctx) error {
csrfToken, ok := c.Locals("csrf").(string)
if !ok {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.Render("login", fiber.Map{
"Title": "Login",
"csrf": csrfToken,
})
})
// Route for processing the login
app.Post("/login", csrfMiddleware, func(c *fiber.Ctx) error {
// Retrieve the submitted form data
username := c.FormValue("username")
password := c.FormValue("password")
// Check if the credentials are valid
user, exists := users[username]
var checkPassword string
if exists {
checkPassword = user.Password
} else {
checkPassword = emptyHashString
}
if bcrypt.CompareHashAndPassword([]byte(checkPassword), []byte(password)) != nil {
// Authentication failed
csrfToken, ok := c.Locals("csrf").(string)
if !ok {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.Render("login", fiber.Map{
"Title": "Login",
"csrf": csrfToken,
"error": "Invalid credentials",
})
}
// Set a session variable to mark the user as logged in
session, err := store.Get(c)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
if err := session.Reset(); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
session.Set("loggedIn", true)
if err := session.Save(); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// Redirect to the protected route
return c.Redirect("/protected")
})
// Route for logging out
app.Get("/logout", func(c *fiber.Ctx) error {
// Retrieve the session
session, err := store.Get(c)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// Revoke users authentication
if err := session.Destroy(); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// Redirect to the login page
return c.Redirect("/login")
})
// Route for the protected content
app.Get("/protected", csrfMiddleware, func(c *fiber.Ctx) error {
// Check if the user is logged in
session, err := store.Get(c)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
loggedIn, _ := session.Get("loggedIn").(bool)
if !loggedIn {
// User is not authenticated, redirect to the login page
return c.Redirect("/login")
}
csrfToken, ok := c.Locals("csrf").(string)
if !ok {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.Render("protected", fiber.Map{
"Title": "Protected",
"csrf": csrfToken,
})
})
// Route for processing the protected form
app.Post("/protected", csrfMiddleware, func(c *fiber.Ctx) error {
// Check if the user is logged in
session, err := store.Get(c)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
loggedIn, _ := session.Get("loggedIn").(bool)
if !loggedIn {
// User is not authenticated, redirect to the login page
return c.Redirect("/login")
}
csrfToken, ok := c.Locals("csrf").(string)
if !ok {
return c.SendStatus(fiber.StatusInternalServerError)
}
// Retrieve the submitted form data
message := c.FormValue("message")
return c.Render("protected", fiber.Map{
"Title": "Protected",
"csrf": csrfToken,
"message": message,
})
})
certFile := "cert.pem"
keyFile := "key.pem"
if _, err := os.Stat(certFile); os.IsNotExist(err) {
fmt.Println("Self-signed certificate not found, generating...")
if err := generateSelfSignedCert(certFile, keyFile); err != nil {
panic(err)
}
fmt.Println("Self-signed certificate generated successfully")
fmt.Println("You will need to accept the self-signed certificate in your browser")
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
panic(err)
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
ln, err := tls.Listen("tcp", "127.0.0.1:8443", config)
if err != nil {
panic(err)
}
app.Listener(ln)
}
// generateSelfSignedCert generates a self-signed certificate and key
// and saves them to the specified files
//
// This is only for testing purposes and should not be used in production
func generateSelfSignedCert(certFile string, keyFile string) error {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 180),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return err
}
certOut, err := os.Create(certFile)
if err != nil {
return err
}
defer certOut.Close()
_ = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
keyOut, err := os.Create(keyFile)
if err != nil {
return err
}
defer keyOut.Close()
_ = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
return nil
}