You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

accounts.go 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "strconv"
  8. "time"
  9. "github.com/goshuirc/irc-go/ircfmt"
  10. "github.com/oragono/oragono/irc/caps"
  11. "github.com/oragono/oragono/irc/sno"
  12. "github.com/tidwall/buntdb"
  13. )
  14. const (
  15. keyAccountExists = "account.exists %s"
  16. keyAccountVerified = "account.verified %s"
  17. keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
  18. keyAccountRegTime = "account.registered.time %s"
  19. keyAccountCredentials = "account.credentials %s"
  20. keyCertToAccount = "account.creds.certfp %s"
  21. )
  22. var (
  23. // EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
  24. // This can be moved to some other data structure/place if we need to load/unload mechs later.
  25. EnabledSaslMechanisms = map[string]func(*Server, *Client, string, []byte, *ResponseBuffer) bool{
  26. "PLAIN": authPlainHandler,
  27. "EXTERNAL": authExternalHandler,
  28. }
  29. // NoAccount is a placeholder which means that the user is not logged into an account.
  30. NoAccount = ClientAccount{
  31. Name: "*", // * is used until actual account name is set
  32. }
  33. )
  34. // ClientAccount represents a user account.
  35. type ClientAccount struct {
  36. // Name of the account.
  37. Name string
  38. // RegisteredAt represents the time that the account was registered.
  39. RegisteredAt time.Time
  40. // Clients that are currently logged into this account (useful for notifications).
  41. Clients []*Client
  42. }
  43. // loadAccountCredentials loads an account's credentials from the store.
  44. func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
  45. credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
  46. if err != nil {
  47. return nil, err
  48. }
  49. var creds AccountCredentials
  50. err = json.Unmarshal([]byte(credText), &creds)
  51. if err != nil {
  52. return nil, err
  53. }
  54. return &creds, nil
  55. }
  56. // loadAccount loads an account from the store, note that the account must actually exist.
  57. func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
  58. name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
  59. regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
  60. regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
  61. accountInfo := ClientAccount{
  62. Name: name,
  63. RegisteredAt: time.Unix(regTimeInt, 0),
  64. Clients: []*Client{},
  65. }
  66. server.accounts[accountKey] = &accountInfo
  67. return &accountInfo
  68. }
  69. // LoginToAccount logs the client into the given account.
  70. func (client *Client) LoginToAccount(account *ClientAccount) {
  71. if client.account == account {
  72. // already logged into this acct, no changing necessary
  73. return
  74. } else if client.LoggedIntoAccount() {
  75. // logout of existing acct
  76. var newClientAccounts []*Client
  77. for _, c := range account.Clients {
  78. if c != client {
  79. newClientAccounts = append(newClientAccounts, c)
  80. }
  81. }
  82. account.Clients = newClientAccounts
  83. }
  84. account.Clients = append(account.Clients, client)
  85. client.account = account
  86. client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]"), client.nickMaskString, account.Name))
  87. //TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
  88. }
  89. // LogoutOfAccount logs the client out of their current account.
  90. func (client *Client) LogoutOfAccount() {
  91. account := client.account
  92. if account == nil {
  93. // already logged out
  94. return
  95. }
  96. // logout of existing acct
  97. var newClientAccounts []*Client
  98. for _, c := range account.Clients {
  99. if c != client {
  100. newClientAccounts = append(newClientAccounts, c)
  101. }
  102. }
  103. account.Clients = newClientAccounts
  104. client.account = nil
  105. // dispatch account-notify
  106. for friend := range client.Friends(caps.AccountNotify) {
  107. friend.Send(nil, client.nickMaskString, "ACCOUNT", "*")
  108. }
  109. }
  110. // successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
  111. func (client *Client) successfulSaslAuth(rb *ResponseBuffer) {
  112. rb.Add(nil, client.server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
  113. rb.Add(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
  114. // dispatch account-notify
  115. for friend := range client.Friends(caps.AccountNotify) {
  116. friend.Send(nil, client.nickMaskString, "ACCOUNT", client.account.Name)
  117. }
  118. }