The backend for the project formerly known as signet, now known as beignet.
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

174 linhas
4.2 KiB

  1. package endpoints
  2. import (
  3. "crypto/rand"
  4. "crypto/subtle"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "net/http"
  10. "strings"
  11. "github.com/imosed/signet/auth"
  12. . "github.com/imosed/signet/data"
  13. "github.com/rs/zerolog/log"
  14. "github.com/spf13/viper"
  15. "golang.org/x/crypto/argon2"
  16. )
  17. const (
  18. SuperUser uint = iota
  19. AdminPlus
  20. Admin
  21. )
  22. type Params struct {
  23. Iterations uint32
  24. Memory uint32
  25. Parallelism uint8
  26. SaltLength uint32
  27. KeyLength uint32
  28. }
  29. func GenerateRandomBytes(n uint32) ([]byte, error) {
  30. var b = make([]byte, n)
  31. _, err := rand.Read(b)
  32. if err != nil {
  33. return nil, err
  34. }
  35. return b, nil
  36. }
  37. func GenerateHash(password string, p *Params) (encodedHash string, err error) {
  38. salt, err := GenerateRandomBytes(p.SaltLength)
  39. if err != nil {
  40. return "", err
  41. }
  42. var hash = argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)
  43. var bSalt = base64.RawStdEncoding.EncodeToString(salt)
  44. var bHash = base64.RawStdEncoding.EncodeToString(hash)
  45. encodedHash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.Memory, p.Iterations, p.Parallelism, bSalt, bHash)
  46. return encodedHash, nil
  47. }
  48. func ComparePasswordAndHash(password, encodedHash string) (match bool, err error) {
  49. p, salt, hash, err := DecodeHash(encodedHash)
  50. if err != nil {
  51. return false, err
  52. }
  53. var otherHash = argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)
  54. if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
  55. return true, nil
  56. }
  57. return false, nil
  58. }
  59. func DecodeHash(encodedHash string) (p *Params, salt, hash []byte, err error) {
  60. var details = strings.Split(encodedHash, "$")
  61. if len(details) != 6 {
  62. return nil, nil, nil, errors.New("the encoded hash is not in the correct format")
  63. }
  64. var version int
  65. _, err = fmt.Sscanf(details[2], "v=%d", &version)
  66. if err != nil {
  67. return nil, nil, nil, err
  68. }
  69. if version != argon2.Version {
  70. return nil, nil, nil, errors.New("the version of argon2 does not match the hash string")
  71. }
  72. p = &Params{}
  73. _, err = fmt.Sscanf(details[3], "m=%d,t=%d,p=%d", &p.Memory, &p.Iterations, &p.Parallelism)
  74. if err != nil {
  75. return nil, nil, nil, err
  76. }
  77. salt, err = base64.RawStdEncoding.DecodeString(details[4])
  78. if err != nil {
  79. return nil, nil, nil, err
  80. }
  81. p.SaltLength = uint32(len(salt))
  82. hash, err = base64.RawStdEncoding.DecodeString(details[5])
  83. if err != nil {
  84. return nil, nil, nil, err
  85. }
  86. p.KeyLength = uint32(len(hash))
  87. return p, salt, hash, nil
  88. }
  89. type AuthenticationRequest struct {
  90. Username string `json:"username"`
  91. Password string `json:"password"`
  92. }
  93. func noUsersRegistered() bool {
  94. results := Db.Find(&User{})
  95. return results.RowsAffected == 0
  96. }
  97. func determinePrivileges() uint {
  98. if noUsersRegistered() {
  99. return SuperUser
  100. } else {
  101. return Admin
  102. }
  103. }
  104. func Register(w http.ResponseWriter, r *http.Request) {
  105. var req AuthenticationRequest
  106. err := json.NewDecoder(r.Body).Decode(&req)
  107. if err != nil {
  108. log.Error().Err(err).Msg("Could not decode body in Register call")
  109. return
  110. }
  111. var claims *auth.Claims
  112. claims, err = auth.GetUserClaims(r)
  113. if err != nil {
  114. log.Error().Err(err).Msg("Could not determine if user is authenticated")
  115. return
  116. }
  117. if noUsersRegistered() || claims.Privileges <= AdminPlus {
  118. hash, err := GenerateHash(req.Password, &Params{
  119. Memory: uint32(viper.GetInt("hashing.memory")),
  120. Iterations: uint32(viper.GetInt("hashing.iterations")),
  121. Parallelism: uint8(viper.GetInt("hashing.parallelism")),
  122. SaltLength: uint32(viper.GetInt("hashing.saltLength")),
  123. KeyLength: uint32(viper.GetInt("hashing.keyLength")),
  124. })
  125. if err != nil {
  126. log.Error().Err(err).Msg("Could not generate hash for registration")
  127. return
  128. }
  129. Db.Create(&User{
  130. Username: req.Username,
  131. Password: hash,
  132. Privileges: determinePrivileges(),
  133. })
  134. err = json.NewEncoder(w).Encode(SuccessResponse{Success: true})
  135. if err != nil {
  136. log.Error().Err(err).Msg("Could not deliver successful account creation response")
  137. }
  138. } else if !noUsersRegistered() {
  139. err = json.NewEncoder(w).Encode(SuccessResponse{Success: false})
  140. if err != nil {
  141. log.Error().Err(err).Msg("Could not deliver unsuccessful account creation response")
  142. }
  143. } else if claims.Privileges > SuperUser {
  144. w.WriteHeader(403)
  145. }
  146. }