|
@@ -4,7 +4,6 @@
|
4
|
4
|
package irc
|
5
|
5
|
|
6
|
6
|
import (
|
7
|
|
- "bytes"
|
8
|
7
|
"crypto/rand"
|
9
|
8
|
"crypto/x509"
|
10
|
9
|
"encoding/json"
|
|
@@ -32,7 +31,6 @@ const (
|
32
|
31
|
keyAccountExists = "account.exists %s"
|
33
|
32
|
keyAccountVerified = "account.verified %s"
|
34
|
33
|
keyAccountUnregistered = "account.unregistered %s"
|
35
|
|
- keyAccountCallback = "account.callback %s"
|
36
|
34
|
keyAccountVerificationCode = "account.verificationcode %s"
|
37
|
35
|
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
|
38
|
36
|
keyAccountRegTime = "account.registered.time %s"
|
|
@@ -46,6 +44,8 @@ const (
|
46
|
44
|
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
47
|
45
|
keyAccountRealname = "account.realname %s" // client realname stored as string
|
48
|
46
|
keyAccountSuspended = "account.suspended %s" // client realname stored as string
|
|
47
|
+ keyAccountPwReset = "account.pwreset %s"
|
|
48
|
+ keyAccountEmailChange = "account.emailchange %s"
|
49
|
49
|
// for an always-on client, a map of channel names they're in to their current modes
|
50
|
50
|
// (not to be confused with their amodes, which a non-always-on client can have):
|
51
|
51
|
keyAccountChannelToModes = "account.channeltomodes %s"
|
|
@@ -391,10 +391,10 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
391
|
391
|
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
392
|
392
|
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
|
393
|
393
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
394
|
|
- callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
395
|
394
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
396
|
395
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
397
|
396
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
|
397
|
+ settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
398
|
398
|
certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
|
399
|
399
|
|
400
|
400
|
var creds AccountCredentials
|
|
@@ -409,8 +409,16 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
409
|
409
|
return err
|
410
|
410
|
}
|
411
|
411
|
|
|
412
|
+ var settingsStr string
|
|
413
|
+ if callbackNamespace == "mailto" {
|
|
414
|
+ settings := AccountSettings{Email: callbackValue}
|
|
415
|
+ j, err := json.Marshal(settings)
|
|
416
|
+ if err == nil {
|
|
417
|
+ settingsStr = string(j)
|
|
418
|
+ }
|
|
419
|
+ }
|
|
420
|
+
|
412
|
421
|
registeredTimeStr := strconv.FormatInt(time.Now().UnixNano(), 10)
|
413
|
|
- callbackSpec := fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)
|
414
|
422
|
|
415
|
423
|
var setOptions *buntdb.SetOptions
|
416
|
424
|
ttl := time.Duration(config.Accounts.Registration.VerifyTimeout)
|
|
@@ -449,7 +457,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
|
449
|
457
|
tx.Set(accountNameKey, account, setOptions)
|
450
|
458
|
tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
|
451
|
459
|
tx.Set(credentialsKey, credStr, setOptions)
|
452
|
|
- tx.Set(callbackKey, callbackSpec, setOptions)
|
|
460
|
+ tx.Set(settingsKey, settingsStr, setOptions)
|
453
|
461
|
if certfp != "" {
|
454
|
462
|
tx.Set(certFPKey, casefoldedAccount, setOptions)
|
455
|
463
|
}
|
|
@@ -782,15 +790,7 @@ func (am *AccountManager) dispatchMailtoCallback(client *Client, account string,
|
782
|
790
|
subject = fmt.Sprintf(client.t("Verify your account on %s"), am.server.name)
|
783
|
791
|
}
|
784
|
792
|
|
785
|
|
- var message bytes.Buffer
|
786
|
|
- fmt.Fprintf(&message, "From: %s\r\n", config.Sender)
|
787
|
|
- fmt.Fprintf(&message, "To: %s\r\n", callbackValue)
|
788
|
|
- if config.DKIM.Domain != "" {
|
789
|
|
- fmt.Fprintf(&message, "Message-ID: <%s@%s>\r\n", utils.GenerateSecretKey(), config.DKIM.Domain)
|
790
|
|
- }
|
791
|
|
- fmt.Fprintf(&message, "Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))
|
792
|
|
- fmt.Fprintf(&message, "Subject: %s\r\n", subject)
|
793
|
|
- message.WriteString("\r\n") // blank line: end headers, begin message body
|
|
793
|
+ message := email.ComposeMail(config, callbackValue, subject)
|
794
|
794
|
fmt.Fprintf(&message, client.t("Account: %s"), account)
|
795
|
795
|
message.WriteString("\r\n")
|
796
|
796
|
fmt.Fprintf(&message, client.t("Verification code: %s"), code)
|
|
@@ -823,8 +823,8 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
|
823
|
823
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
824
|
824
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
825
|
825
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
826
|
|
- callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
827
|
826
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
|
827
|
+ settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
828
|
828
|
|
829
|
829
|
var raw rawClientAccount
|
830
|
830
|
|
|
@@ -892,8 +892,8 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
|
892
|
892
|
tx.Set(accountKey, "1", nil)
|
893
|
893
|
tx.Set(accountNameKey, raw.Name, nil)
|
894
|
894
|
tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
|
895
|
|
- tx.Set(callbackKey, raw.Callback, nil)
|
896
|
895
|
tx.Set(credentialsKey, raw.Credentials, nil)
|
|
896
|
+ tx.Set(settingsKey, raw.Settings, nil)
|
897
|
897
|
|
898
|
898
|
var creds AccountCredentials
|
899
|
899
|
// XXX we shouldn't do (de)serialization inside the txn,
|
|
@@ -955,6 +955,214 @@ func (am *AccountManager) SARegister(account, passphrase string) (err error) {
|
955
|
955
|
return
|
956
|
956
|
}
|
957
|
957
|
|
|
958
|
+type EmailChangeRecord struct {
|
|
959
|
+ TimeCreated time.Time
|
|
960
|
+ Code string
|
|
961
|
+ Email string
|
|
962
|
+}
|
|
963
|
+
|
|
964
|
+func (am *AccountManager) NsSetEmail(client *Client, emailAddr string) (err error) {
|
|
965
|
+ casefoldedAccount := client.Account()
|
|
966
|
+ if casefoldedAccount == "" {
|
|
967
|
+ return errAccountNotLoggedIn
|
|
968
|
+ }
|
|
969
|
+
|
|
970
|
+ if am.touchRegisterThrottle() {
|
|
971
|
+ am.server.logger.Warning("accounts", "global registration throttle exceeded by client changing email", client.Nick())
|
|
972
|
+ return errLimitExceeded
|
|
973
|
+ }
|
|
974
|
+
|
|
975
|
+ config := am.server.Config()
|
|
976
|
+ if !config.Accounts.Registration.EmailVerification.Enabled {
|
|
977
|
+ return errFeatureDisabled // redundant check, just in case
|
|
978
|
+ }
|
|
979
|
+ record := EmailChangeRecord{
|
|
980
|
+ TimeCreated: time.Now().UTC(),
|
|
981
|
+ Code: utils.GenerateSecretToken(),
|
|
982
|
+ Email: emailAddr,
|
|
983
|
+ }
|
|
984
|
+ recordKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
|
|
985
|
+ recordBytes, _ := json.Marshal(record)
|
|
986
|
+ recordVal := string(recordBytes)
|
|
987
|
+ am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
988
|
+ tx.Set(recordKey, recordVal, nil)
|
|
989
|
+ return nil
|
|
990
|
+ })
|
|
991
|
+
|
|
992
|
+ if err != nil {
|
|
993
|
+ return err
|
|
994
|
+ }
|
|
995
|
+
|
|
996
|
+ message := email.ComposeMail(config.Accounts.Registration.EmailVerification,
|
|
997
|
+ emailAddr,
|
|
998
|
+ fmt.Sprintf(client.t("Verify your change of e-mail address on %s"), am.server.name))
|
|
999
|
+ message.WriteString(fmt.Sprintf(client.t("To confirm your change of e-mail address on %s, issue the following command:"), am.server.name))
|
|
1000
|
+ message.WriteString("\r\n")
|
|
1001
|
+ fmt.Fprintf(&message, "/MSG NickServ VERIFYEMAIL %s\r\n", record.Code)
|
|
1002
|
+
|
|
1003
|
+ err = email.SendMail(config.Accounts.Registration.EmailVerification, emailAddr, message.Bytes())
|
|
1004
|
+ if err == nil {
|
|
1005
|
+ am.server.logger.Info("services",
|
|
1006
|
+ fmt.Sprintf("email change verification sent for account %s", casefoldedAccount))
|
|
1007
|
+ return
|
|
1008
|
+ } else {
|
|
1009
|
+ am.server.logger.Error("internal", "Failed to dispatch e-mail change verification to", emailAddr, err.Error())
|
|
1010
|
+ return ®istrationCallbackError{err}
|
|
1011
|
+ }
|
|
1012
|
+}
|
|
1013
|
+
|
|
1014
|
+func (am *AccountManager) NsVerifyEmail(client *Client, code string) (err error) {
|
|
1015
|
+ casefoldedAccount := client.Account()
|
|
1016
|
+ if casefoldedAccount == "" {
|
|
1017
|
+ return errAccountNotLoggedIn
|
|
1018
|
+ }
|
|
1019
|
+
|
|
1020
|
+ var record EmailChangeRecord
|
|
1021
|
+ success := false
|
|
1022
|
+ key := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
|
|
1023
|
+ ttl := time.Duration(am.server.Config().Accounts.Registration.VerifyTimeout)
|
|
1024
|
+ am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
1025
|
+ rawStr, err := tx.Get(key)
|
|
1026
|
+ if err == nil && rawStr != "" {
|
|
1027
|
+ err := json.Unmarshal([]byte(rawStr), &record)
|
|
1028
|
+ if err == nil {
|
|
1029
|
+ if (ttl == 0 || time.Since(record.TimeCreated) < ttl) && utils.SecretTokensMatch(record.Code, code) {
|
|
1030
|
+ success = true
|
|
1031
|
+ tx.Delete(key)
|
|
1032
|
+ }
|
|
1033
|
+ }
|
|
1034
|
+ }
|
|
1035
|
+ return nil
|
|
1036
|
+ })
|
|
1037
|
+
|
|
1038
|
+ if !success {
|
|
1039
|
+ return errAccountVerificationInvalidCode
|
|
1040
|
+ }
|
|
1041
|
+
|
|
1042
|
+ munger := func(in AccountSettings) (out AccountSettings, err error) {
|
|
1043
|
+ out = in
|
|
1044
|
+ out.Email = record.Email
|
|
1045
|
+ return
|
|
1046
|
+ }
|
|
1047
|
+
|
|
1048
|
+ _, err = am.ModifyAccountSettings(casefoldedAccount, munger)
|
|
1049
|
+ return
|
|
1050
|
+}
|
|
1051
|
+
|
|
1052
|
+func (am *AccountManager) NsSendpass(client *Client, accountName string) (err error) {
|
|
1053
|
+ config := am.server.Config()
|
|
1054
|
+ if !(config.Accounts.Registration.EmailVerification.Enabled && config.Accounts.Registration.EmailVerification.PasswordReset.Enabled) {
|
|
1055
|
+ return errFeatureDisabled
|
|
1056
|
+ }
|
|
1057
|
+
|
|
1058
|
+ account, err := am.LoadAccount(accountName)
|
|
1059
|
+ if err != nil {
|
|
1060
|
+ return err
|
|
1061
|
+ }
|
|
1062
|
+ if !account.Verified {
|
|
1063
|
+ return errAccountUnverified
|
|
1064
|
+ }
|
|
1065
|
+ if account.Suspended != nil {
|
|
1066
|
+ return errAccountSuspended
|
|
1067
|
+ }
|
|
1068
|
+ if account.Settings.Email == "" {
|
|
1069
|
+ return errValidEmailRequired
|
|
1070
|
+ }
|
|
1071
|
+
|
|
1072
|
+ record := PasswordResetRecord{
|
|
1073
|
+ TimeCreated: time.Now().UTC(),
|
|
1074
|
+ Code: utils.GenerateSecretToken(),
|
|
1075
|
+ }
|
|
1076
|
+ recordKey := fmt.Sprintf(keyAccountPwReset, account.NameCasefolded)
|
|
1077
|
+ recordBytes, _ := json.Marshal(record)
|
|
1078
|
+ recordVal := string(recordBytes)
|
|
1079
|
+
|
|
1080
|
+ am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
1081
|
+ recStr, recErr := tx.Get(recordKey)
|
|
1082
|
+ if recErr == nil && recStr != "" {
|
|
1083
|
+ var existing PasswordResetRecord
|
|
1084
|
+ jErr := json.Unmarshal([]byte(recStr), &existing)
|
|
1085
|
+ cooldown := time.Duration(config.Accounts.Registration.EmailVerification.PasswordReset.Cooldown)
|
|
1086
|
+ if jErr == nil && time.Since(existing.TimeCreated) < cooldown {
|
|
1087
|
+ err = errLimitExceeded
|
|
1088
|
+ return nil
|
|
1089
|
+ }
|
|
1090
|
+ }
|
|
1091
|
+ tx.Set(recordKey, recordVal, &buntdb.SetOptions{
|
|
1092
|
+ Expires: true,
|
|
1093
|
+ TTL: time.Duration(config.Accounts.Registration.EmailVerification.PasswordReset.Timeout),
|
|
1094
|
+ })
|
|
1095
|
+ return nil
|
|
1096
|
+ })
|
|
1097
|
+
|
|
1098
|
+ if err != nil {
|
|
1099
|
+ return
|
|
1100
|
+ }
|
|
1101
|
+
|
|
1102
|
+ subject := fmt.Sprintf(client.t("Reset your password on %s"), am.server.name)
|
|
1103
|
+ message := email.ComposeMail(config.Accounts.Registration.EmailVerification, account.Settings.Email, subject)
|
|
1104
|
+ fmt.Fprintf(&message, client.t("We received a request to reset your password on %s for account: %s"), am.server.name, account.Name)
|
|
1105
|
+ message.WriteString("\r\n")
|
|
1106
|
+ fmt.Fprintf(&message, client.t("If you did not initiate this request, you can safely ignore this message."))
|
|
1107
|
+ message.WriteString("\r\n")
|
|
1108
|
+ message.WriteString("\r\n")
|
|
1109
|
+ message.WriteString(client.t("Otherwise, to reset your password, issue the following command (replace `new_password` with your desired password):"))
|
|
1110
|
+ message.WriteString("\r\n")
|
|
1111
|
+ fmt.Fprintf(&message, "/MSG NickServ RESETPASS %s %s new_password\r\n", account.Name, record.Code)
|
|
1112
|
+
|
|
1113
|
+ err = email.SendMail(config.Accounts.Registration.EmailVerification, account.Settings.Email, message.Bytes())
|
|
1114
|
+ if err == nil {
|
|
1115
|
+ am.server.logger.Info("services",
|
|
1116
|
+ fmt.Sprintf("client %s sent a password reset email for account %s", client.Nick(), account.Name))
|
|
1117
|
+ } else {
|
|
1118
|
+ am.server.logger.Error("internal", "Failed to dispatch e-mail to", account.Settings.Email, err.Error())
|
|
1119
|
+ }
|
|
1120
|
+ return
|
|
1121
|
+
|
|
1122
|
+}
|
|
1123
|
+
|
|
1124
|
+func (am *AccountManager) NsResetpass(client *Client, accountName, code, password string) (err error) {
|
|
1125
|
+ if validatePassphrase(password) != nil {
|
|
1126
|
+ return errAccountBadPassphrase
|
|
1127
|
+ }
|
|
1128
|
+ account, err := am.LoadAccount(accountName)
|
|
1129
|
+ if err != nil {
|
|
1130
|
+ return
|
|
1131
|
+ }
|
|
1132
|
+ if !account.Verified {
|
|
1133
|
+ return errAccountUnverified
|
|
1134
|
+ }
|
|
1135
|
+ if account.Suspended != nil {
|
|
1136
|
+ return errAccountSuspended
|
|
1137
|
+ }
|
|
1138
|
+
|
|
1139
|
+ success := false
|
|
1140
|
+ key := fmt.Sprintf(keyAccountPwReset, account.NameCasefolded)
|
|
1141
|
+ am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
1142
|
+ rawStr, err := tx.Get(key)
|
|
1143
|
+ if err == nil && rawStr != "" {
|
|
1144
|
+ var record PasswordResetRecord
|
|
1145
|
+ err := json.Unmarshal([]byte(rawStr), &record)
|
|
1146
|
+ if err == nil && utils.SecretTokensMatch(record.Code, code) {
|
|
1147
|
+ success = true
|
|
1148
|
+ tx.Delete(key)
|
|
1149
|
+ }
|
|
1150
|
+ }
|
|
1151
|
+ return nil
|
|
1152
|
+ })
|
|
1153
|
+
|
|
1154
|
+ if success {
|
|
1155
|
+ return am.setPassword(accountName, password, true)
|
|
1156
|
+ } else {
|
|
1157
|
+ return errAccountInvalidCredentials
|
|
1158
|
+ }
|
|
1159
|
+}
|
|
1160
|
+
|
|
1161
|
+type PasswordResetRecord struct {
|
|
1162
|
+ TimeCreated time.Time
|
|
1163
|
+ Code string
|
|
1164
|
+}
|
|
1165
|
+
|
958
|
1166
|
func marshalReservedNicks(nicks []string) string {
|
959
|
1167
|
return strings.Join(nicks, ",")
|
960
|
1168
|
}
|
|
@@ -1294,9 +1502,6 @@ func (am *AccountManager) deserializeRawAccount(raw rawClientAccount, cfName str
|
1294
|
1502
|
return
|
1295
|
1503
|
}
|
1296
|
1504
|
result.AdditionalNicks = unmarshalReservedNicks(raw.AdditionalNicks)
|
1297
|
|
- if strings.HasPrefix(raw.Callback, "mailto:") {
|
1298
|
|
- result.Email = strings.TrimPrefix(raw.Callback, "mailto:")
|
1299
|
|
- }
|
1300
|
1505
|
result.Verified = raw.Verified
|
1301
|
1506
|
if raw.VHost != "" {
|
1302
|
1507
|
e := json.Unmarshal([]byte(raw.VHost), &result.VHost)
|
|
@@ -1329,7 +1534,6 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
1329
|
1534
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
1330
|
1535
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
1331
|
1536
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
1332
|
|
- callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
1333
|
1537
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
1334
|
1538
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
1335
|
1539
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
|
@@ -1344,7 +1548,6 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
1344
|
1548
|
result.Name, _ = tx.Get(accountNameKey)
|
1345
|
1549
|
result.RegisteredAt, _ = tx.Get(registeredTimeKey)
|
1346
|
1550
|
result.Credentials, _ = tx.Get(credentialsKey)
|
1347
|
|
- result.Callback, _ = tx.Get(callbackKey)
|
1348
|
1551
|
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
1349
|
1552
|
result.VHost, _ = tx.Get(vhostKey)
|
1350
|
1553
|
result.Settings, _ = tx.Get(settingsKey)
|
|
@@ -1524,7 +1727,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1524
|
1727
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
1525
|
1728
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
1526
|
1729
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
1527
|
|
- callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
1528
|
1730
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
1529
|
1731
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
1530
|
1732
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
|
@@ -1537,6 +1739,8 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1537
|
1739
|
modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)
|
1538
|
1740
|
realnameKey := fmt.Sprintf(keyAccountRealname, casefoldedAccount)
|
1539
|
1741
|
suspendedKey := fmt.Sprintf(keyAccountSuspended, casefoldedAccount)
|
|
1742
|
+ pwResetKey := fmt.Sprintf(keyAccountPwReset, casefoldedAccount)
|
|
1743
|
+ emailChangeKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
|
1540
|
1744
|
|
1541
|
1745
|
var clients []*Client
|
1542
|
1746
|
defer func() {
|
|
@@ -1582,7 +1786,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1582
|
1786
|
tx.Delete(accountNameKey)
|
1583
|
1787
|
tx.Delete(verifiedKey)
|
1584
|
1788
|
tx.Delete(registeredTimeKey)
|
1585
|
|
- tx.Delete(callbackKey)
|
1586
|
1789
|
tx.Delete(verificationCodeKey)
|
1587
|
1790
|
tx.Delete(settingsKey)
|
1588
|
1791
|
rawNicks, _ = tx.Get(nicksKey)
|
|
@@ -1597,6 +1800,8 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1597
|
1800
|
tx.Delete(modesKey)
|
1598
|
1801
|
tx.Delete(realnameKey)
|
1599
|
1802
|
tx.Delete(suspendedKey)
|
|
1803
|
+ tx.Delete(pwResetKey)
|
|
1804
|
+ tx.Delete(emailChangeKey)
|
1600
|
1805
|
|
1601
|
1806
|
return nil
|
1602
|
1807
|
})
|
|
@@ -1940,17 +2145,19 @@ const (
|
1940
|
2145
|
CredentialsAnope = -2
|
1941
|
2146
|
)
|
1942
|
2147
|
|
|
2148
|
+type SCRAMCreds struct {
|
|
2149
|
+ Salt []byte
|
|
2150
|
+ Iters int
|
|
2151
|
+ StoredKey []byte
|
|
2152
|
+ ServerKey []byte
|
|
2153
|
+}
|
|
2154
|
+
|
1943
|
2155
|
// AccountCredentials stores the various methods for verifying accounts.
|
1944
|
2156
|
type AccountCredentials struct {
|
1945
|
2157
|
Version CredentialsVersion
|
1946
|
2158
|
PassphraseHash []byte
|
1947
|
2159
|
Certfps []string
|
1948
|
|
- SCRAMCreds struct {
|
1949
|
|
- Salt []byte
|
1950
|
|
- Iters int
|
1951
|
|
- StoredKey []byte
|
1952
|
|
- ServerKey []byte
|
1953
|
|
- }
|
|
2160
|
+ SCRAMCreds
|
1954
|
2161
|
}
|
1955
|
2162
|
|
1956
|
2163
|
func (ac *AccountCredentials) Empty() bool {
|
|
@@ -1970,6 +2177,7 @@ func (ac *AccountCredentials) Serialize() (result string, err error) {
|
1970
|
2177
|
func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint) (err error) {
|
1971
|
2178
|
if passphrase == "" {
|
1972
|
2179
|
ac.PassphraseHash = nil
|
|
2180
|
+ ac.SCRAMCreds = SCRAMCreds{}
|
1973
|
2181
|
return nil
|
1974
|
2182
|
}
|
1975
|
2183
|
|
|
@@ -1994,10 +2202,12 @@ func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint)
|
1994
|
2202
|
// xdg-go/scram says: "Clients have a default minimum PBKDF2 iteration count of 4096."
|
1995
|
2203
|
minIters := 4096
|
1996
|
2204
|
scramCreds := scramClient.GetStoredCredentials(scram.KeyFactors{Salt: string(salt), Iters: minIters})
|
1997
|
|
- ac.SCRAMCreds.Salt = salt
|
1998
|
|
- ac.SCRAMCreds.Iters = minIters
|
1999
|
|
- ac.SCRAMCreds.StoredKey = scramCreds.StoredKey
|
2000
|
|
- ac.SCRAMCreds.ServerKey = scramCreds.ServerKey
|
|
2205
|
+ ac.SCRAMCreds = SCRAMCreds{
|
|
2206
|
+ Salt: salt,
|
|
2207
|
+ Iters: minIters,
|
|
2208
|
+ StoredKey: scramCreds.StoredKey,
|
|
2209
|
+ ServerKey: scramCreds.ServerKey,
|
|
2210
|
+ }
|
2001
|
2211
|
|
2002
|
2212
|
return nil
|
2003
|
2213
|
}
|
|
@@ -2112,6 +2322,7 @@ type AccountSettings struct {
|
2112
|
2322
|
AutoreplayMissed bool
|
2113
|
2323
|
DMHistory HistoryStatus
|
2114
|
2324
|
AutoAway PersistentStatus
|
|
2325
|
+ Email string
|
2115
|
2326
|
}
|
2116
|
2327
|
|
2117
|
2328
|
// ClientAccount represents a user account.
|
|
@@ -2120,7 +2331,6 @@ type ClientAccount struct {
|
2120
|
2331
|
Name string
|
2121
|
2332
|
NameCasefolded string
|
2122
|
2333
|
RegisteredAt time.Time
|
2123
|
|
- Email string
|
2124
|
2334
|
Credentials AccountCredentials
|
2125
|
2335
|
Verified bool
|
2126
|
2336
|
Suspended *AccountSuspension
|
|
@@ -2134,7 +2344,6 @@ type rawClientAccount struct {
|
2134
|
2344
|
Name string
|
2135
|
2345
|
RegisteredAt string
|
2136
|
2346
|
Credentials string
|
2137
|
|
- Callback string
|
2138
|
2347
|
Verified bool
|
2139
|
2348
|
AdditionalNicks string
|
2140
|
2349
|
VHost string
|