A More Secure Internet Connection for Your Home https://fen.gg
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.
 
 
 
 
 
 

205 lines
5.8 KiB

  1. package model
  2. //
  3. // Fengg Security Gateway Server Application
  4. // Copyright (C) 2020 Lukas Matt <support@fen.gg>
  5. //
  6. // This program is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. //
  18. import (
  19. "time"
  20. "crypto/rand"
  21. "crypto/rsa"
  22. "crypto/x509"
  23. "crypto/sha256"
  24. "encoding/base64"
  25. "encoding/pem"
  26. "errors"
  27. "fmt"
  28. "golang.org/x/crypto/bcrypt"
  29. "github.com/jmoiron/sqlx"
  30. _ "github.com/lib/pq"
  31. )
  32. const (
  33. UserQueryTmpl = `SELECT * FROM users %s;`
  34. UserNamedUpdateTmpl = `
  35. UPDATE users
  36. SET updated_at=now(), name=:name, surname=:surname,
  37. bio=:bio, email=:email %s
  38. WHERE %s;`
  39. UserNamedInsertTmpl = `
  40. INSERT INTO users (
  41. created_at, updated_at, name, surname, bio, email, nickname, hashed_password,
  42. fingerprint, serialized_public_key, serialized_private_key
  43. ) VALUES (
  44. now(), now(), :name, :surname, :bio, :email, :nickname, :hashed_password,
  45. :fingerprint, :serialized_public_key, :serialized_private_key
  46. ) RETURNING id;`
  47. )
  48. type User struct {
  49. ID uint `db:"id" json:"id" binding:"-"`
  50. CreatedAt time.Time `db:"created_at" json:"createdAt" binding:"-"`
  51. UpdatedAt time.Time `db:"updated_at" json:"updatedAt" binding:"-"`
  52. Name *string `db:"name" json:"name,omitempty"`
  53. Surname *string `db:"surname" json:"surname,omitempty"`
  54. Bio *string `db:"bio" json:"bio,omitempty"`
  55. Email *string `db:"email" json:"email,omitempty" binding="email,omitempty"`
  56. Nickname string `db:"nickname" json:"nickname" binding:"alphanum,min=4"`
  57. Password *string `db:"-" json:"password,omitempty" binding:"min=4,required"`
  58. // XXX Repassword must be validated manually
  59. Repassword *string `db:"-" json:"repassword,omitempty" binding:"-"`
  60. HashedPassword string `db:"hashed_password" json:"-" binding:"-"`
  61. Fingerprint string `db:"fingerprint" json:"fingerprint" binding:"fingerprint"`
  62. SerializedPublicKey string `db:"serialized_public_key" json:"serializedPublicKey" binding:"publickey"`
  63. SerializedPrivateKey string `db:"serialized_private_key" json:"-" binding:"privatekey"`
  64. }
  65. type Users []User
  66. func NewUser() *User {
  67. return &User{
  68. CreatedAt: time.Now(),
  69. UpdatedAt: time.Now(),
  70. }
  71. }
  72. func (users *Users) FindAll() error {
  73. db, err := sqlx.Connect(dbDriver, dbConnect)
  74. if err != nil {
  75. return err
  76. }
  77. defer db.Close()
  78. return db.Select(users, fmt.Sprintf(UserQueryTmpl, ""))
  79. }
  80. func (user *User) FindByFingerprint(fingerprint string) error {
  81. db, err := sqlx.Connect(dbDriver, dbConnect)
  82. if err != nil {
  83. return err
  84. }
  85. defer db.Close()
  86. return db.Get(user, fmt.Sprintf(UserQueryTmpl, "WHERE fingerprint = $1"), fingerprint)
  87. }
  88. func (user *User) FindByID(id uint) error {
  89. db, err := sqlx.Connect(dbDriver, dbConnect)
  90. if err != nil {
  91. return err
  92. }
  93. defer db.Close()
  94. return db.Get(user, fmt.Sprintf(UserQueryTmpl, "WHERE id = $1"), id)
  95. }
  96. func (user *User) FindByNickname(nickname string) error {
  97. db, err := sqlx.Connect(dbDriver, dbConnect)
  98. if err != nil {
  99. return err
  100. }
  101. defer db.Close()
  102. return db.Get(user, fmt.Sprintf(UserQueryTmpl, "WHERE nickname = $1"), nickname)
  103. }
  104. func (user *User) Create() error {
  105. db, err := sqlx.Connect(dbDriver, dbConnect)
  106. if err != nil {
  107. return err
  108. }
  109. defer db.Close()
  110. if user.Password == nil {
  111. return errors.New("cannot create user without password")
  112. }
  113. hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*user.Password), bcrypt.DefaultCost)
  114. if err != nil {
  115. return err
  116. }
  117. user.HashedPassword = string(hashedPassword)
  118. user.Password = nil
  119. user.Repassword = nil
  120. // generate private and public key
  121. key, err := rsa.GenerateKey(rand.Reader, 2048)
  122. if err != nil {
  123. return err
  124. }
  125. // marshal and encode private key
  126. marshaledKey := x509.MarshalPKCS1PrivateKey(key)
  127. block := pem.Block{Type: "PRIVATE KEY", Bytes: marshaledKey}
  128. user.SerializedPrivateKey = string(pem.EncodeToMemory(&block))
  129. // marshal and encode public key
  130. marshaledPublicKey, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
  131. if err != nil {
  132. return err
  133. }
  134. pBlock := pem.Block{Type: "PUBLIC KEY", Bytes: marshaledPublicKey}
  135. user.SerializedPublicKey = string(pem.EncodeToMemory(&pBlock))
  136. // generate fingerprint according to
  137. // https://www.openssh.com/txt/release-6.8
  138. // https://tools.ietf.org/html/rfc4648#section-3.2
  139. sha256sum := sha256.Sum256(marshaledPublicKey)
  140. hash := base64.RawURLEncoding.EncodeToString(sha256sum[:])
  141. user.Fingerprint = fmt.Sprintf("SHA256:%s", hash)
  142. rows, err := db.NamedQuery(UserNamedInsertTmpl, user)
  143. if err != nil {
  144. return err
  145. }
  146. defer rows.Close()
  147. for rows.Next() {
  148. err = rows.Scan(&user.ID)
  149. if err != nil {
  150. return err
  151. }
  152. }
  153. return nil
  154. }
  155. func (user *User) Update() error {
  156. db, err := sqlx.Connect(dbDriver, dbConnect)
  157. if err != nil {
  158. return err
  159. }
  160. defer db.Close()
  161. var pwQuery string
  162. if user.Password != nil && user.Repassword != nil && *user.Password == *user.Repassword {
  163. hashedPassword, err := bcrypt.GenerateFromPassword(
  164. []byte(*user.Password), bcrypt.DefaultCost)
  165. if err != nil {
  166. return err
  167. }
  168. user.HashedPassword = string(hashedPassword)
  169. user.Password = nil
  170. user.Repassword = nil
  171. pwQuery = ", hashed_password=:hashed_password"
  172. }
  173. _, err = db.NamedExec(fmt.Sprintf(
  174. UserNamedUpdateTmpl, pwQuery, "id=:id OR nickname=:nickname"), user)
  175. return err
  176. }