signup.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. package accounts
  2. import (
  3. "notabug.org/apiote/amuse/db"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "math/rand"
  8. "strings"
  9. "golang.org/x/crypto/argon2"
  10. "notabug.org/apiote/gott"
  11. )
  12. func findNoUser(args ...interface{}) (interface{}, error) {
  13. authData := args[0].(*AuthData)
  14. authResult := args[1].(*AuthResult)
  15. user, err := db.GetUser(authData.username)
  16. authResult.user = user
  17. if _, ok := err.(db.EmptyError); ok {
  18. err = nil
  19. } else if err == nil {
  20. err = AuthError{
  21. Err: errors.New("user_exists"),
  22. }
  23. }
  24. return gott.Tuple(args), err
  25. }
  26. func prepareSalt(args ...interface{}) (interface{}, error) {
  27. argon := args[2].(*Argon)
  28. salt := make([]byte, 16)
  29. _, err := rand.Read(salt)
  30. argon.salt = salt
  31. return gott.Tuple(args), err
  32. }
  33. func hashPassword(args ...interface{}) interface{} {
  34. authData := args[0].(*AuthData)
  35. argon := args[2].(*Argon)
  36. password := authData.password
  37. hash := argon2.IDKey([]byte(password), argon.salt, 1, 64*1024, 4, 32)
  38. b64Salt := base64.RawStdEncoding.EncodeToString(argon.salt)
  39. b64Hash := base64.RawStdEncoding.EncodeToString(hash)
  40. format := "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s"
  41. fullHash := fmt.Sprintf(format, argon2.Version, 64*1024, 1, 4, b64Salt, b64Hash)
  42. authData.password = fullHash
  43. return gott.Tuple(args)
  44. }
  45. func createRecoveryCodes(args ...interface{}) interface{} {
  46. authData := args[0].(*AuthData)
  47. sfaSecret := authData.sfa
  48. if sfaSecret != "" {
  49. result := args[1].(*AuthResult)
  50. codes := []string{}
  51. for i := 0; i < 12; i++ {
  52. code := rand.Int63n(999999999999)
  53. codeStr := fmt.Sprintf("%012d", code)
  54. codes = append(codes, codeStr)
  55. }
  56. result.recoveryCodesRaw = strings.Join(codes, ",")
  57. }
  58. return gott.Tuple(args)
  59. }
  60. func insertUser(args ...interface{}) (interface{}, error) {
  61. authData := args[0].(*AuthData)
  62. result := args[1].(*AuthResult)
  63. sfaSecret := authData.sfa
  64. err := db.InsertUser(authData.username, authData.password, sfaSecret, result.recoveryCodesRaw)
  65. return gott.Tuple(args), err
  66. }
  67. func Signup(username, password, sfaSecret string) (string, error) {
  68. r, err := gott.
  69. NewResult(gott.Tuple{&AuthData{username: username, password: password,
  70. sfa: sfaSecret}, &AuthResult{}, &Argon{}}).
  71. Bind(findNoUser).
  72. Bind(prepareSalt).
  73. Map(hashPassword).
  74. Map(createRecoveryCodes).
  75. Bind(insertUser).
  76. Finish()
  77. if err != nil {
  78. return "", err
  79. }
  80. return r.(gott.Tuple)[1].(*AuthResult).recoveryCodesRaw, err
  81. }