/
verify.go
172 lines (146 loc) · 4.76 KB
/
verify.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
package users
import (
"context"
"errors"
"fmt"
"regexp"
"github.com/hakierspejs/long-season/pkg/services/happier"
"github.com/hakierspejs/long-season/pkg/storage"
"golang.org/x/crypto/bcrypt"
)
const (
invalidNicknameMsg = "username should contains from 4 to 32 numerical and alphabetical characters"
invalidPasswordMsg = "password should contains from 6 to 50 any characters, excluding whitespace characters"
)
var (
// ErrInvalidNickname error is used for various verifies to
// signal that user verification failed because of
// invalid username. Raw message of error is safe to output
// to client.
ErrInvalidNickname = errors.New(invalidNicknameMsg)
// ErrInvaliPassword error is used for various verifies to
// signal that user verification failed because of
// invalid password. Raw message of error is safe to output
// to client.
ErrInvaliPassword = errors.New(invalidPasswordMsg)
)
var (
nicknameRegex = regexp.MustCompile(`^[a-zA-Z0-9]{4,32}$`)
passwordRegex = regexp.MustCompile(`^[^[:space:]]{6,50}$`)
)
// VerifyNickname verifies if given nickname string
// is proper nickname for long-season application.
func VerifyNickname(n string) bool {
return nicknameRegex.MatchString(n)
}
// VerifyPassword verifies if given password string
// is proper password for long-season application.
func VerifyPassword(p string) bool {
return passwordRegex.MatchString(p)
}
// VerifyRegisterData verifies if given data required for
// user registration is valid. Returned error messages are
// safe to output to client.
func VerifyRegisterData(nickname, password string) error {
if ok := VerifyNickname(nickname); !ok {
return ErrInvalidNickname
}
if ok := VerifyPassword(password); !ok {
return ErrInvaliPassword
}
return nil
}
// AuthenticationRequest holds input data for AuthenticateWithPassword
// function.
type AuthenticationRequest struct {
// UserID is used to find user.
UserID string
// Nickname can be also used to find user as
// alternative to UserID.
Nickname string
// Password is used to verify user with
// requested nickname.
Password []byte
}
// AuthenticationDependencies are dependencies for
// authenticating user with password.
type AuthenticationDependencies struct {
// Request holds input data.
Request AuthenticationRequest
// Storage operates on user data in database.
Storage storage.Users
// ErrorFactory is optional. If you want
// to have debug errors, you can pass
// error Factory created from http request.
ErrorFactory *happier.Factory
}
// AuthenticationResponse holds data of authenticated user.
type AuthenticationResponse struct {
// UserID is used to find user.
UserID string
// Nickname can be also used to find user as
// alternative to UserID.
Nickname string
}
// AuthenticateWithPassword takes aut dependencies with authenticate request
// and process user data to verify whether given passwords matches or not.
// It returns user data for convince if authentication passes and nil pointer with error
// if it doesn't.
func AuthenticateWithPassword(ctx context.Context, deps AuthenticationDependencies) (*AuthenticationResponse, error) {
if deps.ErrorFactory == nil {
// ErrorFactory is optional parameter, so if it is nil
// we replace it with default happier error factory.
deps.ErrorFactory = happier.Default()
}
var match *storage.UserEntry
var err error
if deps.Request.UserID != "" {
// UserID is not empty, try to find matching user.
match, err = deps.Storage.Read(ctx, deps.Request.UserID)
if err != nil {
return nil, deps.ErrorFactory.NotFound(
fmt.Errorf("deps.Storage.Read: %w", err),
"There is no user with given user ID",
)
}
} else {
// UserID is empty, so try to find matching user
// by nickname.
users, err := deps.Storage.All(ctx)
if err != nil {
return nil, deps.ErrorFactory.InternalServerError(
fmt.Errorf("deps.Storage.All: %w", err),
"Internal Server Error please try again later.",
)
}
// Search for user with exactly same nickname.
for _, user := range users {
if user.Nickname == deps.Request.Nickname {
match = &user
break
}
}
}
// Check if there is the user with given nickname
// or ID in the database.
if match == nil {
return nil, deps.ErrorFactory.NotFound(
fmt.Errorf("match == nil, user given nickname: %s, not found", deps.Request.Nickname),
fmt.Sprintf("there is no user with given nickname: \"%s\"", deps.Request.Nickname),
)
}
// Check if passwords do match.
if err := bcrypt.CompareHashAndPassword(
match.HashedPassword,
deps.Request.Password,
); err != nil {
return nil, deps.ErrorFactory.Unauthorized(
fmt.Errorf("bcrypt.CompareHashAndPassword: %w", err),
fmt.Sprintf("given password does not match"),
)
}
return &AuthenticationResponse{
UserID: match.ID,
Nickname: match.Nickname,
}, nil
}