The backend for the project formerly known as signet, now known as beignet.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

174 line
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. }