src/auth/auth.go (view raw)
1package auth
2
3import (
4 "crypto/rand"
5 "encoding/hex"
6 "errors"
7 "net/http"
8 "time"
9 "unicode"
10
11 "golang.org/x/crypto/bcrypt"
12)
13
14type Auth struct {
15 Pepper string
16 spicesLength int
17}
18
19const (
20 minPasswordLength = 6
21 maxHashLength = 72
22 DefaultMaxPasswordLength = 56 // leaves 16 bytes for salt and pepper
23)
24
25func NewAuth(pepper string, maxPasswordLength uint) *Auth {
26 if maxPasswordLength > 70 {
27 return nil
28 }
29
30 hash, err := bcrypt.GenerateFromPassword([]byte(pepper), bcrypt.DefaultCost)
31 if err != nil {
32 return nil
33 }
34
35 spicesLength := int(maxHashLength-maxPasswordLength) / 2
36 return &Auth{
37 Pepper: hex.EncodeToString(hash)[:spicesLength],
38 spicesLength: spicesLength,
39 }
40}
41
42func (g Auth) HashPassword(password string) (hashedPassword, salt string, err error) {
43 if !isASCII(password) || len(password) < minPasswordLength {
44 err = errors.New("invalid password")
45 return
46 }
47
48 salt, err = g.GenerateRandomToken(g.spicesLength)
49 if err != nil {
50 return
51 }
52 salt = salt[:g.spicesLength]
53
54 bytesPassword, err := bcrypt.GenerateFromPassword([]byte(password+salt+g.Pepper), bcrypt.DefaultCost)
55 if err != nil {
56 return
57 }
58
59 hashedPassword = string(bytesPassword)
60 return
61}
62
63func (g Auth) CheckPassword(password, salt, hash string) bool {
64 err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password+salt+g.Pepper))
65 return err == nil
66}
67
68func (g Auth) GenerateRandomToken(n int) (string, error) {
69 token := make([]byte, n)
70 _, err := rand.Read(token)
71 if err != nil {
72 return "", err
73 }
74 return hex.EncodeToString(token), nil
75}
76
77func (g Auth) GenerateCookie(duration time.Duration) (*http.Cookie, error) {
78 sessionToken, err := g.GenerateRandomToken(32)
79 if err != nil {
80 return nil, err
81 }
82
83 return &http.Cookie{
84 Name: "session_token",
85 Value: sessionToken,
86 Expires: time.Now().Add(duration),
87 Path: "/",
88 HttpOnly: true,
89 Secure: true,
90 }, nil
91}
92
93func (g Auth) GenerateEmptyCookie() *http.Cookie {
94 return &http.Cookie{
95 Name: "session_token",
96 Value: "",
97 Expires: time.Now().Add(-1 * time.Hour),
98 Path: "/",
99 }
100}
101
102func isASCII(s string) bool {
103 for i := 0; i < len(s); i++ {
104 if s[i] > unicode.MaxASCII {
105 return false
106 }
107 }
108 return true
109}