login.go 4.7 KB

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