/
auth.go
138 lines (106 loc) · 3.5 KB
/
auth.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
package auth
import (
"context"
"time"
"golang.org/x/crypto/bcrypt"
"github.com/google/uuid"
opentracing "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/Evertras/events-demo/auth/lib/authdb"
"github.com/Evertras/events-demo/auth/lib/events"
"github.com/Evertras/events-demo/auth/lib/events/authevents"
)
var ErrUserAlreadyExists = errors.New("user already exists")
// Auth performs auth operations and updates an underlying data store and event stream
type Auth interface {
// Register creates a UserRegistered event and waits for the user to be added.
// Returns the generated user ID for the user on success.
Register(ctx context.Context, email string, password string) (string, error)
// Validate checks if the id and password are correct.
//
// Returns true if they match
// Returns false if they do not match, but the check itself was made
// Returns an error if the check could not be made
ValidateByID(ctx context.Context, id string, password string) (bool, error)
// GetIDFromEmail gets the canonical user ID from the given email, if it exists
//
// Returns the ID on match
// Returns an empty string if not found
// Returns an error if something unexpected occurred
GetIDFromEmail(ctx context.Context, email string) (string, error)
}
type auth struct {
db authdb.Db
eventWriter events.Writer
}
func New(db authdb.Db, eventWriter events.Writer) Auth {
a := &auth{
db: db,
eventWriter: eventWriter,
}
return a
}
func (a *auth) Register(ctx context.Context, email string, password string) (string, error) {
fullSpan, ctx := opentracing.StartSpanFromContext(ctx, "Register")
fullSpan.SetTag("component", "logic")
defer fullSpan.Finish()
// Note that this is a best-effort sanity check; if two register commands are
// sent quickly back to back, this will NOT stop multiple events from being
// created, and that's okay. We need to cleanly handle multiple register events
// later on in the process. It's still good to filter what we can at this point.
existingID, err := a.db.GetIDByEmail(ctx, email)
if err != nil {
return "", errors.Wrap(err, "failed to check for existing user")
}
if existingID != "" {
return "", ErrUserAlreadyExists
}
hashSpan := opentracing.StartSpan("Hash password", opentracing.ChildOf(fullSpan.Context()))
// bcrypt package takes care of salting for us
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
hashSpan.Finish()
if err != nil {
return "", errors.Wrap(err, "unable to hash password")
}
id := uuid.New().String()
done := make(chan bool)
errs := make(chan error)
go func() {
err := a.db.WaitForCreateUser(ctx, id)
if err != nil {
errs <- err
} else {
done <- true
}
}()
ev := authevents.NewUserRegistered()
ev.ID = id
ev.Email = email
ev.PasswordHash = string(hash)
ev.TimeUnixMs = time.Now().Unix()
err = a.eventWriter.PostRegisteredEvent(ctx, ev)
if err != nil {
return "", err
}
select {
case <-done:
break
case e := <-errs:
fullSpan.SetTag("error", true)
fullSpan.SetTag("error.object", e)
return "", errors.Wrap(err, "failed to find registered event")
case <-ctx.Done():
err := errors.New("context ended")
fullSpan.SetTag("error", true)
fullSpan.SetTag("error.object", err)
return "", err
}
return id, nil
}
func (a *auth) GetIDFromEmail(ctx context.Context, email string) (string, error) {
id, err := a.db.GetIDByEmail(ctx, email)
if err != nil {
return "", errors.Wrap(err, "failed to find ID")
}
return id, nil
}