-
Notifications
You must be signed in to change notification settings - Fork 110
/
identity_reconciler.go
109 lines (96 loc) · 3.4 KB
/
identity_reconciler.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
package services
import (
"encoding/hex"
"github.com/keratin/authn-server/app"
"github.com/keratin/authn-server/app/data"
"github.com/keratin/authn-server/app/models"
"github.com/keratin/authn-server/lib"
"github.com/keratin/authn-server/lib/oauth"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
// IdentityReconciler will reconcile an OAuth identity with an AuthN account. This may mean:
//
// * finding the linked account
// * linking to an existing account
// * creating a new account
//
// Some expected errors include:
//
// * account is locked
// * linkable account is already linked
// * identity's email is already registered
func IdentityReconciler(accountStore data.AccountStore, cfg *app.Config, providerName string, providerUser *oauth.UserInfo, providerToken *oauth2.Token, linkableAccountID int) (*models.Account, error) {
// 1. check for linked account
linkedAccount, err := accountStore.FindByOauthAccount(providerName, providerUser.ID)
if err != nil {
return nil, errors.Wrap(err, "FindByOauthAccount")
}
if linkedAccount != nil {
if linkedAccount.ID != linkableAccountID && linkableAccountID != 0 {
return nil, errors.New("account already linked")
}
if linkedAccount.Locked {
return nil, errors.New("account locked")
}
err = updateUserInfo(accountStore, linkedAccount.ID, providerName, providerUser)
if err != nil {
return nil, errors.Wrap(err, "updateUserInfo")
}
return linkedAccount, nil
}
// 2. attempt linking to existing account
if linkableAccountID != 0 {
err = accountStore.AddOauthAccount(linkableAccountID, providerName, providerUser.ID, providerUser.Email, providerToken.AccessToken)
if err != nil {
if data.IsUniquenessError(err) {
return nil, errors.New("session conflict")
}
return nil, errors.Wrap(err, "AddOauthAccount")
}
sessionAccount, err := accountStore.Find(linkableAccountID)
if err != nil {
return nil, errors.Wrap(err, "Find")
}
return sessionAccount, nil
}
// 3. attempt creating new account
rand, err := lib.GenerateToken()
if err != nil {
return nil, errors.Wrap(err, "GenerateToken")
}
// TODO: transactional account + identity
// Note we hex encode token because zxcvbn does not seem to like non-printable characters
newAccount, err := AccountCreator(accountStore, cfg, providerUser.Email, hex.EncodeToString(rand))
if err != nil {
return nil, errors.Wrap(err, "AccountCreator")
}
err = accountStore.AddOauthAccount(newAccount.ID, providerName, providerUser.ID, providerUser.Email, providerToken.AccessToken)
if err != nil {
// this should not happen since oauth details used to lookup account above
// not sure how best to test but feels appropriate to return error if encountered
return nil, errors.Wrap(err, "AddOauthAccount")
}
return newAccount, nil
}
func updateUserInfo(accountStore data.AccountStore, accountID int, providerName string, providerUser *oauth.UserInfo) error {
oAccounts, err := accountStore.GetOauthAccounts(accountID)
if err != nil {
return errors.Wrap(err, "GetOauthAccounts")
}
if len(oAccounts) == 0 {
return nil
}
for _, oAccount := range oAccounts {
if providerName != oAccount.Provider && providerUser.ID != oAccount.ProviderID {
continue
}
if oAccount.GetEmail() != providerUser.Email {
_, err = accountStore.UpdateOauthAccount(accountID, oAccount.Provider, providerUser.Email)
if err != nil {
return errors.Wrap(err, "UpdateOauthAccount")
}
}
}
return nil
}