api_mfa.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package httpd
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "github.com/go-chi/render"
  7. "github.com/drakkan/sftpgo/v2/dataprovider"
  8. "github.com/drakkan/sftpgo/v2/kms"
  9. "github.com/drakkan/sftpgo/v2/mfa"
  10. "github.com/drakkan/sftpgo/v2/util"
  11. )
  12. type generateTOTPRequest struct {
  13. ConfigName string `json:"config_name"`
  14. }
  15. type generateTOTPResponse struct {
  16. ConfigName string `json:"config_name"`
  17. Issuer string `json:"issuer"`
  18. Secret string `json:"secret"`
  19. QRCode []byte `json:"qr_code"`
  20. }
  21. type validateTOTPRequest struct {
  22. ConfigName string `json:"config_name"`
  23. Passcode string `json:"passcode"`
  24. Secret string `json:"secret"`
  25. }
  26. type recoveryCode struct {
  27. Code string `json:"code"`
  28. Used bool `json:"used"`
  29. }
  30. func getTOTPConfigs(w http.ResponseWriter, r *http.Request) {
  31. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  32. render.JSON(w, r, mfa.GetAvailableTOTPConfigs())
  33. }
  34. func generateTOTPSecret(w http.ResponseWriter, r *http.Request) {
  35. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  36. claims, err := getTokenClaims(r)
  37. if err != nil || claims.Username == "" {
  38. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  39. return
  40. }
  41. var accountName string
  42. if claims.hasUserAudience() {
  43. accountName = fmt.Sprintf("User %#v", claims.Username)
  44. } else {
  45. accountName = fmt.Sprintf("Admin %#v", claims.Username)
  46. }
  47. var req generateTOTPRequest
  48. err = render.DecodeJSON(r.Body, &req)
  49. if err != nil {
  50. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  51. return
  52. }
  53. configName, issuer, secret, qrCode, err := mfa.GenerateTOTPSecret(req.ConfigName, accountName)
  54. if err != nil {
  55. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  56. return
  57. }
  58. render.JSON(w, r, generateTOTPResponse{
  59. ConfigName: configName,
  60. Issuer: issuer,
  61. Secret: secret,
  62. QRCode: qrCode,
  63. })
  64. }
  65. func saveTOTPConfig(w http.ResponseWriter, r *http.Request) {
  66. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  67. claims, err := getTokenClaims(r)
  68. if err != nil || claims.Username == "" {
  69. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  70. return
  71. }
  72. recoveryCodes := make([]dataprovider.RecoveryCode, 0, 12)
  73. for i := 0; i < 12; i++ {
  74. code := getNewRecoveryCode()
  75. recoveryCodes = append(recoveryCodes, dataprovider.RecoveryCode{Secret: kms.NewPlainSecret(code)})
  76. }
  77. if claims.hasUserAudience() {
  78. if err := saveUserTOTPConfig(claims.Username, r, recoveryCodes); err != nil {
  79. sendAPIResponse(w, r, err, "", getRespStatus(err))
  80. return
  81. }
  82. } else {
  83. if err := saveAdminTOTPConfig(claims.Username, r, recoveryCodes); err != nil {
  84. sendAPIResponse(w, r, err, "", getRespStatus(err))
  85. return
  86. }
  87. }
  88. sendAPIResponse(w, r, nil, "TOTP configuration saved", http.StatusOK)
  89. }
  90. func validateTOTPPasscode(w http.ResponseWriter, r *http.Request) {
  91. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  92. var req validateTOTPRequest
  93. err := render.DecodeJSON(r.Body, &req)
  94. if err != nil {
  95. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  96. return
  97. }
  98. match, err := mfa.ValidateTOTPPasscode(req.ConfigName, req.Passcode, req.Secret)
  99. if !match || err != nil {
  100. sendAPIResponse(w, r, err, "Invalid passcode", http.StatusBadRequest)
  101. return
  102. }
  103. sendAPIResponse(w, r, nil, "Passcode successfully validated", http.StatusOK)
  104. }
  105. func getRecoveryCodes(w http.ResponseWriter, r *http.Request) {
  106. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  107. claims, err := getTokenClaims(r)
  108. if err != nil || claims.Username == "" {
  109. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  110. return
  111. }
  112. recoveryCodes := make([]recoveryCode, 0, 12)
  113. var accountRecoveryCodes []dataprovider.RecoveryCode
  114. if claims.hasUserAudience() {
  115. user, err := dataprovider.UserExists(claims.Username)
  116. if err != nil {
  117. sendAPIResponse(w, r, err, "", getRespStatus(err))
  118. return
  119. }
  120. accountRecoveryCodes = user.Filters.RecoveryCodes
  121. } else {
  122. admin, err := dataprovider.AdminExists(claims.Username)
  123. if err != nil {
  124. sendAPIResponse(w, r, err, "", getRespStatus(err))
  125. return
  126. }
  127. accountRecoveryCodes = admin.Filters.RecoveryCodes
  128. }
  129. for _, code := range accountRecoveryCodes {
  130. if err := code.Secret.Decrypt(); err != nil {
  131. sendAPIResponse(w, r, err, "Unable to decrypt recovery codes", getRespStatus(err))
  132. return
  133. }
  134. recoveryCodes = append(recoveryCodes, recoveryCode{
  135. Code: code.Secret.GetPayload(),
  136. Used: code.Used,
  137. })
  138. }
  139. render.JSON(w, r, recoveryCodes)
  140. }
  141. func generateRecoveryCodes(w http.ResponseWriter, r *http.Request) {
  142. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  143. claims, err := getTokenClaims(r)
  144. if err != nil || claims.Username == "" {
  145. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  146. return
  147. }
  148. recoveryCodes := make([]string, 0, 12)
  149. accountRecoveryCodes := make([]dataprovider.RecoveryCode, 0, 12)
  150. for i := 0; i < 12; i++ {
  151. code := getNewRecoveryCode()
  152. recoveryCodes = append(recoveryCodes, code)
  153. accountRecoveryCodes = append(accountRecoveryCodes, dataprovider.RecoveryCode{Secret: kms.NewPlainSecret(code)})
  154. }
  155. if claims.hasUserAudience() {
  156. user, err := dataprovider.UserExists(claims.Username)
  157. if err != nil {
  158. sendAPIResponse(w, r, err, "", getRespStatus(err))
  159. return
  160. }
  161. user.Filters.RecoveryCodes = accountRecoveryCodes
  162. if err := dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
  163. sendAPIResponse(w, r, err, "", getRespStatus(err))
  164. return
  165. }
  166. } else {
  167. admin, err := dataprovider.AdminExists(claims.Username)
  168. if err != nil {
  169. sendAPIResponse(w, r, err, "", getRespStatus(err))
  170. return
  171. }
  172. admin.Filters.RecoveryCodes = accountRecoveryCodes
  173. if err := dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
  174. sendAPIResponse(w, r, err, "", getRespStatus(err))
  175. return
  176. }
  177. }
  178. render.JSON(w, r, recoveryCodes)
  179. }
  180. func getNewRecoveryCode() string {
  181. return fmt.Sprintf("RC-%v", strings.ToUpper(util.GenerateUniqueID()))
  182. }
  183. func saveUserTOTPConfig(username string, r *http.Request, recoveryCodes []dataprovider.RecoveryCode) error {
  184. user, err := dataprovider.UserExists(username)
  185. if err != nil {
  186. return err
  187. }
  188. currentTOTPSecret := user.Filters.TOTPConfig.Secret
  189. user.Filters.TOTPConfig.Secret = nil
  190. err = render.DecodeJSON(r.Body, &user.Filters.TOTPConfig)
  191. if err != nil {
  192. return util.NewValidationError(fmt.Sprintf("unable to decode JSON body: %v", err))
  193. }
  194. if user.Filters.TOTPConfig.Secret == nil || !user.Filters.TOTPConfig.Secret.IsPlain() {
  195. user.Filters.TOTPConfig.Secret = currentTOTPSecret
  196. }
  197. if user.CountUnusedRecoveryCodes() < 5 && user.Filters.TOTPConfig.Enabled {
  198. user.Filters.RecoveryCodes = recoveryCodes
  199. }
  200. return dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr))
  201. }
  202. func saveAdminTOTPConfig(username string, r *http.Request, recoveryCodes []dataprovider.RecoveryCode) error {
  203. admin, err := dataprovider.AdminExists(username)
  204. if err != nil {
  205. return err
  206. }
  207. currentTOTPSecret := admin.Filters.TOTPConfig.Secret
  208. admin.Filters.TOTPConfig.Secret = nil
  209. err = render.DecodeJSON(r.Body, &admin.Filters.TOTPConfig)
  210. if err != nil {
  211. return util.NewValidationError(fmt.Sprintf("unable to decode JSON body: %v", err))
  212. }
  213. if admin.CountUnusedRecoveryCodes() < 5 && admin.Filters.TOTPConfig.Enabled {
  214. admin.Filters.RecoveryCodes = recoveryCodes
  215. }
  216. if admin.Filters.TOTPConfig.Secret == nil || !admin.Filters.TOTPConfig.Secret.IsPlain() {
  217. admin.Filters.TOTPConfig.Secret = currentTOTPSecret
  218. }
  219. return dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr))
  220. }