login.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. package accounts
  2. // https://golangcode.com/argon2-password-hashing/
  3. import (
  4. "notabug.org/apiote/amuse/db"
  5. "bytes"
  6. "database/sql"
  7. "encoding/base64"
  8. "errors"
  9. "fmt"
  10. "strings"
  11. "crypto/rand"
  12. "encoding/hex"
  13. "github.com/pquerna/otp/totp"
  14. "golang.org/x/crypto/argon2"
  15. "notabug.org/apiote/gott"
  16. )
  17. type AuthData struct {
  18. username string
  19. password string
  20. passRepeat string
  21. sfa string
  22. remember bool
  23. }
  24. type AuthResult struct {
  25. row *sql.Rows
  26. passwordHash string
  27. sfaSecret string
  28. recoveryCodesRaw string
  29. recoveryCodes []string
  30. token string
  31. }
  32. type Argon struct {
  33. password string
  34. argon string
  35. parts []string
  36. memory uint32
  37. time uint32
  38. threads uint8
  39. salt []byte
  40. hash []byte
  41. keyLen uint32
  42. }
  43. func findUser(args ...interface{}) (interface{}, error) {
  44. authData := args[0].(*AuthData)
  45. authResult := args[1].(*AuthResult)
  46. row, err := db.GetUser(authData.username)
  47. authResult.row = row
  48. if empty, ok := err.(db.EmptyError); ok {
  49. err = AuthError{Err: empty}
  50. }
  51. return gott.Tuple(args), err
  52. }
  53. func unmarshalUser(args ...interface{}) (interface{}, error) {
  54. authResult := args[1].(*AuthResult)
  55. err := authResult.row.Scan(&authResult.passwordHash, &authResult.sfaSecret,
  56. &authResult.recoveryCodesRaw)
  57. authResult.row.Close()
  58. authResult.recoveryCodes = strings.Split(authResult.recoveryCodesRaw, ",")
  59. return gott.Tuple(args), err
  60. }
  61. func splitArgon(args ...interface{}) interface{} {
  62. argon := args[0].(*Argon)
  63. argon.parts = strings.Split(argon.argon, "$")
  64. return gott.Tuple(args)
  65. }
  66. func decodeArgonParams(args ...interface{}) (interface{}, error) {
  67. argon := args[0].(*Argon)
  68. _, err := fmt.Sscanf(argon.parts[3], "m=%d,t=%d,p=%d", &argon.memory,
  69. &argon.time, &argon.threads)
  70. return gott.Tuple(args), err
  71. }
  72. func decodeSalt(args ...interface{}) (interface{}, error) {
  73. argon := args[0].(*Argon)
  74. salt, err := base64.RawStdEncoding.DecodeString(argon.parts[4])
  75. argon.salt = salt
  76. return gott.Tuple(args), err
  77. }
  78. func decodeHash(args ...interface{}) (interface{}, error) {
  79. argon := args[0].(*Argon)
  80. hash, err := base64.RawStdEncoding.DecodeString(argon.parts[5])
  81. argon.hash = hash
  82. argon.keyLen = uint32(len(hash))
  83. return gott.Tuple(args), err
  84. }
  85. func compareArgon(args ...interface{}) (interface{}, error) {
  86. argon := args[0].(*Argon)
  87. comparisonHash := argon2.IDKey([]byte(argon.password), argon.salt, argon.time,
  88. argon.memory, argon.threads, argon.keyLen)
  89. if bytes.Compare(comparisonHash, argon.hash) != 0 {
  90. return gott.Tuple(args), AuthError{Err: errors.New("Password does not match")}
  91. } else {
  92. return gott.Tuple(args), nil
  93. }
  94. }
  95. func checkPassword(args ...interface{}) (interface{}, error) {
  96. authData := args[0].(*AuthData)
  97. authResult := args[1].(*AuthResult)
  98. _, err := gott.
  99. NewResult(gott.Tuple{&Argon{argon: authResult.passwordHash,
  100. password: authData.password}}).
  101. Map(splitArgon).
  102. Bind(decodeArgonParams).
  103. Bind(decodeSalt).
  104. Bind(decodeHash).
  105. Bind(compareArgon).
  106. Finish()
  107. return gott.Tuple(args), err
  108. }
  109. func checkSfa(args ...interface{}) (interface{}, error) {
  110. authData := args[0].(*AuthData)
  111. authResult := args[1].(*AuthResult)
  112. if authResult.sfaSecret == "" {
  113. return gott.Tuple(args), nil
  114. }
  115. for i, code := range authResult.recoveryCodes {
  116. if authData.sfa == code {
  117. authResult.recoveryCodes = append(authResult.recoveryCodes[:i],
  118. authResult.recoveryCodes[i+1:]...)
  119. authResult.recoveryCodesRaw = strings.Join(authResult.recoveryCodes, ",")
  120. return gott.Tuple(args), nil
  121. }
  122. }
  123. if totp.Validate(authData.sfa, authResult.sfaSecret) {
  124. return gott.Tuple(args), nil
  125. }
  126. return gott.Tuple(args), AuthError{Err: errors.New("Wrong TOTP token")}
  127. }
  128. func updateSfa(args ...interface{}) (interface{}, error) {
  129. authData := args[0].(*AuthData)
  130. authResult := args[1].(*AuthResult)
  131. err := db.UpdateRecoveryCodes(authData.username, authResult.recoveryCodesRaw)
  132. return gott.Tuple(args), err
  133. }
  134. func createToken(args ...interface{}) (interface{}, error) {
  135. authResult := args[1].(*AuthResult)
  136. sessionIdRaw := make([]byte, 128)
  137. rand.Read(sessionIdRaw)
  138. sessionId := hex.EncodeToString(sessionIdRaw)
  139. authResult.token = sessionId
  140. return gott.Tuple(args), nil
  141. }
  142. func createSession(args ...interface{}) (interface{}, error) {
  143. authData := args[0].(*AuthData)
  144. authResult := args[1].(*AuthResult)
  145. err := db.CreateSession(authData.username, authResult.token)
  146. return gott.Tuple(args), err
  147. }
  148. func Login(username, password, sfa string, remember bool) (string, error) {
  149. r, err := gott.
  150. NewResult(gott.Tuple{&AuthData{username: username, password: password,
  151. sfa: sfa, remember: remember}, &AuthResult{}}).
  152. Bind(findUser).
  153. //Clear old sessions
  154. Bind(unmarshalUser).
  155. Bind(checkPassword).
  156. Bind(checkSfa).
  157. Bind(updateSfa).
  158. Bind(createToken).
  159. Bind(createSession).
  160. Finish()
  161. if err != nil {
  162. return "", err
  163. }
  164. return r.(gott.Tuple)[1].(*AuthResult).token, err
  165. }