login.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package accounts
  2. import (
  3. "notabug.org/apiote/amuse/db"
  4. "bytes"
  5. "encoding/base64"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "github.com/pquerna/otp/totp"
  10. "golang.org/x/crypto/argon2"
  11. "notabug.org/apiote/gott"
  12. )
  13. type AuthData struct {
  14. username string
  15. password string
  16. sfa string
  17. remember bool
  18. }
  19. type AuthResult struct {
  20. user *db.User
  21. passwordHash string
  22. sfaSecret string
  23. recoveryCodesRaw string
  24. recoveryCodes []string
  25. token string
  26. }
  27. type Argon struct {
  28. password string
  29. argon string
  30. parts []string
  31. memory uint32
  32. time uint32
  33. threads uint8
  34. salt []byte
  35. hash []byte
  36. keyLen uint32
  37. }
  38. func findUser(args ...interface{}) (interface{}, error) {
  39. authData := args[0].(*AuthData)
  40. authResult := args[1].(*AuthResult)
  41. user, err := db.GetUser(authData.username)
  42. authResult.user = user
  43. if empty, ok := err.(db.EmptyError); ok {
  44. err = AuthError{Err: empty}
  45. }
  46. return gott.Tuple(args), err
  47. }
  48. func unmarshalUser(args ...interface{}) interface{} {
  49. authResult := args[1].(*AuthResult)
  50. authResult.passwordHash = authResult.user.PasswordHash
  51. authResult.sfaSecret = authResult.user.Sfa
  52. authResult.recoveryCodesRaw = authResult.user.RecoveryCodes
  53. authResult.recoveryCodes = strings.Split(authResult.recoveryCodesRaw, ",")
  54. return gott.Tuple(args)
  55. }
  56. func splitArgon(args ...interface{}) interface{} {
  57. argon := args[0].(*Argon)
  58. argon.parts = strings.Split(argon.argon, "$")
  59. return gott.Tuple(args)
  60. }
  61. func decodeArgonParams(args ...interface{}) (interface{}, error) {
  62. argon := args[0].(*Argon)
  63. _, err := fmt.Sscanf(argon.parts[3], "m=%d,t=%d,p=%d", &argon.memory,
  64. &argon.time, &argon.threads)
  65. return gott.Tuple(args), err
  66. }
  67. func decodeSalt(args ...interface{}) (interface{}, error) {
  68. argon := args[0].(*Argon)
  69. salt, err := base64.RawStdEncoding.DecodeString(argon.parts[4])
  70. argon.salt = salt
  71. return gott.Tuple(args), err
  72. }
  73. func decodeHash(args ...interface{}) (interface{}, error) {
  74. argon := args[0].(*Argon)
  75. hash, err := base64.RawStdEncoding.DecodeString(argon.parts[5])
  76. argon.hash = hash
  77. argon.keyLen = uint32(len(hash))
  78. return gott.Tuple(args), err
  79. }
  80. func compareArgon(args ...interface{}) (interface{}, error) {
  81. argon := args[0].(*Argon)
  82. comparisonHash := argon2.IDKey([]byte(argon.password), argon.salt, argon.time,
  83. argon.memory, argon.threads, argon.keyLen)
  84. if bytes.Compare(comparisonHash, argon.hash) != 0 {
  85. return gott.Tuple(args), AuthError{Err: errors.New("Password does not match")}
  86. } else {
  87. return gott.Tuple(args), nil
  88. }
  89. }
  90. func checkPassword(args ...interface{}) (interface{}, error) {
  91. authData := args[0].(*AuthData)
  92. authResult := args[1].(*AuthResult)
  93. _, err := gott.
  94. NewResult(gott.Tuple{&Argon{argon: authResult.passwordHash,
  95. password: authData.password}}).
  96. Map(splitArgon).
  97. Bind(decodeArgonParams).
  98. Bind(decodeSalt).
  99. Bind(decodeHash).
  100. Bind(compareArgon).
  101. Finish()
  102. return gott.Tuple(args), err
  103. }
  104. func checkSfa(args ...interface{}) (interface{}, error) {
  105. authData := args[0].(*AuthData)
  106. authResult := args[1].(*AuthResult)
  107. if authResult.sfaSecret == "" {
  108. return gott.Tuple(args), nil
  109. }
  110. for i, code := range authResult.recoveryCodes {
  111. if authData.sfa == code {
  112. authResult.recoveryCodes = append(authResult.recoveryCodes[:i],
  113. authResult.recoveryCodes[i+1:]...)
  114. authResult.recoveryCodesRaw = strings.Join(authResult.recoveryCodes, ",")
  115. return gott.Tuple(args), nil
  116. }
  117. }
  118. authData.sfa = strings.ReplaceAll(authData.sfa, " ", "")
  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. }