The new Ripple frontend.
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.

2fa.go 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package main
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. "time"
  10. "golang.org/x/crypto/bcrypt"
  11. "github.com/gin-gonic/gin"
  12. "github.com/pquerna/otp"
  13. "github.com/pquerna/otp/totp"
  14. "zxq.co/ripple/rippleapi/common"
  15. "zxq.co/x/rs"
  16. )
  17. var allowedPaths = [...]string{
  18. "/logout",
  19. "/2fa_gateway",
  20. "/2fa_gateway/verify",
  21. "/2fa_gateway/clear",
  22. "/2fa_gateway/recover",
  23. "/favicon.ico",
  24. }
  25. // middleware to deny all requests to non-allowed pages
  26. func twoFALock(c *gin.Context) {
  27. // check it's not a static file
  28. if len(c.Request.URL.Path) >= 8 && c.Request.URL.Path[:8] == "/static/" {
  29. c.Next()
  30. return
  31. }
  32. ctx := getContext(c)
  33. if ctx.User.ID == 0 {
  34. c.Next()
  35. return
  36. }
  37. sess := getSession(c)
  38. if v, _ := sess.Get("2fa_must_validate").(bool); !v {
  39. // * check 2fa is enabled.
  40. // if it is,
  41. // * check whether the current ip is found in the database.
  42. // if it is, move on and show the page.
  43. // if it isn't, set 2fa_must_validate
  44. // if it isn't, move on.
  45. if is2faEnabled(ctx.User.ID) > 0 {
  46. err := db.QueryRow("SELECT 1 FROM ip_user WHERE userid = ? AND ip = ? LIMIT 1", ctx.User.ID, clientIP(c)).Scan(new(int))
  47. if err != sql.ErrNoRows {
  48. c.Next()
  49. return
  50. }
  51. sess.Set("2fa_must_validate", true)
  52. } else {
  53. c.Next()
  54. return
  55. }
  56. }
  57. // check it's one of the few approved paths
  58. for _, a := range allowedPaths {
  59. if a == c.Request.URL.Path {
  60. sess.Save()
  61. c.Next()
  62. return
  63. }
  64. }
  65. addMessage(c, warningMessage{T(c, "You need to complete the 2fa challenge first.")})
  66. sess.Save()
  67. query := c.Request.URL.RawQuery
  68. if query != "" {
  69. query = "?" + query
  70. }
  71. c.Redirect(302, "/2fa_gateway?redir="+url.QueryEscape(c.Request.URL.Path+query))
  72. c.Abort()
  73. }
  74. const (
  75. tfaEnabledTelegram = 1 << iota
  76. tfaEnabledTOTP
  77. )
  78. // is2faEnabled checks 2fa is enabled for an user.
  79. func is2faEnabled(user int) int {
  80. var enabled int
  81. db.QueryRow("SELECT IFNULL((SELECT 1 FROM 2fa_telegram WHERE userid = ?), 0) | IFNULL((SELECT 2 FROM 2fa_totp WHERE userid = ? AND enabled = 1), 0) as x", user, user).
  82. Scan(&enabled)
  83. return enabled
  84. }
  85. func tfaGateway(c *gin.Context) {
  86. sess := getSession(c)
  87. redir := c.Query("redir")
  88. switch {
  89. case redir == "":
  90. redir = "/"
  91. case redir[0] != '/':
  92. redir = "/"
  93. }
  94. i, _ := sess.Get("userid").(int)
  95. if i == 0 {
  96. c.Redirect(302, redir)
  97. }
  98. // check 2fa hasn't been disabled
  99. e := is2faEnabled(i)
  100. if e == 0 {
  101. sess.Delete("2fa_must_validate")
  102. sess.Save()
  103. c.Redirect(302, redir)
  104. return
  105. }
  106. if e == 1 {
  107. // check previous 2fa thing is still valid
  108. err := db.QueryRow("SELECT 1 FROM 2fa WHERE userid = ? AND ip = ? AND expire > ?",
  109. i, clientIP(c), time.Now().Unix()).Scan(new(int))
  110. if err != nil {
  111. db.Exec("INSERT INTO 2fa(userid, token, ip, expire, sent) VALUES (?, ?, ?, ?, 0);",
  112. i, strings.ToUpper(rs.String(8)), clientIP(c), time.Now().Add(time.Hour).Unix())
  113. http.Get("http://127.0.0.1:8888/update")
  114. }
  115. }
  116. resp(c, 200, "2fa_gateway.html", &baseTemplateData{
  117. TitleBar: "Two Factor Authentication",
  118. KyutGrill: "2fa.jpg",
  119. RequestInfo: map[string]interface{}{
  120. "redir": redir,
  121. },
  122. })
  123. }
  124. func clientIP(c *gin.Context) string {
  125. ff := c.Request.Header.Get("CF-Connecting-IP")
  126. if ff != "" {
  127. return ff
  128. }
  129. return c.ClientIP()
  130. }
  131. func clear2fa(c *gin.Context) {
  132. // basically deletes from db 2fa tokens, so that it gets regenerated when user hits gateway page
  133. sess := getSession(c)
  134. i, _ := sess.Get("userid").(int)
  135. if i == 0 {
  136. c.Redirect(302, "/")
  137. }
  138. db.Exec("DELETE FROM 2fa WHERE userid = ? AND ip = ?", i, clientIP(c))
  139. addMessage(c, successMessage{T(c, "A new code has been generated and sent to you through Telegram.")})
  140. sess.Save()
  141. c.Redirect(302, "/2fa_gateway")
  142. }
  143. func verify2fa(c *gin.Context) {
  144. sess := getSession(c)
  145. i, _ := sess.Get("userid").(int)
  146. if i == 0 {
  147. c.Redirect(302, "/")
  148. }
  149. e := is2faEnabled(i)
  150. switch e {
  151. case 1:
  152. var id int
  153. var expire common.UnixTimestamp
  154. err := db.QueryRow("SELECT id, expire FROM 2fa WHERE userid = ? AND ip = ? AND token = ?", i, clientIP(c), strings.ToUpper(c.Query("token"))).Scan(&id, &expire)
  155. if err == sql.ErrNoRows {
  156. c.String(200, "1")
  157. return
  158. }
  159. if time.Now().After(time.Time(expire)) {
  160. c.String(200, "1")
  161. db.Exec("INSERT INTO 2fa(userid, token, ip, expire, sent) VALUES (?, ?, ?, ?, 0);",
  162. i, strings.ToUpper(rs.String(8)), clientIP(c), time.Now().Add(time.Hour).Unix())
  163. http.Get("http://127.0.0.1:8888/update")
  164. return
  165. }
  166. case 2:
  167. var secret string
  168. db.Get(&secret, "SELECT secret FROM 2fa_totp WHERE userid = ?", i)
  169. if !totp.Validate(strings.Replace(c.Query("token"), " ", "", -1), secret) {
  170. c.String(200, "1")
  171. return
  172. }
  173. }
  174. loginUser(c, i)
  175. db.Exec("DELETE FROM 2fa WHERE id = ?", i)
  176. c.String(200, "0")
  177. }
  178. func loginUser(c *gin.Context, i int) {
  179. var d struct {
  180. Country string
  181. Flags uint
  182. }
  183. err := db.Get(&d, "SELECT users_stats.country, users.flags FROM users_stats "+
  184. "LEFT JOIN users ON users.id = users_stats.id WHERE users_stats.id = ?", i)
  185. if err != nil {
  186. c.Error(err)
  187. }
  188. afterLogin(c, i, d.Country, d.Flags)
  189. addMessage(c, successMessage{T(c, "You've been successfully logged in.")})
  190. sess := getSession(c)
  191. sess.Delete("2fa_must_validate")
  192. sess.Save()
  193. }
  194. func recover2fa(c *gin.Context) {
  195. sess := getSession(c)
  196. i, _ := sess.Get("userid").(int)
  197. if i == 0 {
  198. c.Redirect(302, "/")
  199. }
  200. e := is2faEnabled(i)
  201. if e != 2 {
  202. respEmpty(c, "Recover account", warningMessage{T(c, "Oh no you don't.")})
  203. return
  204. }
  205. resp(c, 200, "2fa_gateway_recover.html", &baseTemplateData{
  206. TitleBar: T(c, "Recover account"),
  207. KyutGrill: "2fa.jpg",
  208. })
  209. }
  210. func recover2faSubmit(c *gin.Context) {
  211. sess := getSession(c)
  212. i, _ := sess.Get("userid").(int)
  213. if i == 0 {
  214. c.Redirect(302, "/")
  215. }
  216. if is2faEnabled(i) != 2 {
  217. respEmpty(c, T(c, "Recover account"), warningMessage{T(c, "Get out.")})
  218. return
  219. }
  220. var codesRaw string
  221. db.Get(&codesRaw, "SELECT recovery FROM 2fa_totp WHERE userid = ?", i)
  222. var codes []string
  223. json.Unmarshal([]byte(codesRaw), &codes)
  224. for k, v := range codes {
  225. if v == c.PostForm("recovery_code") {
  226. codes[k] = codes[len(codes)-1]
  227. codes = codes[:len(codes)-1]
  228. b, _ := json.Marshal(codes)
  229. db.Exec("UPDATE 2fa_totp SET recovery = ? WHERE userid = ?", string(b), i)
  230. loginUser(c, i)
  231. c.Redirect(302, "/")
  232. return
  233. }
  234. }
  235. resp(c, 200, "2fa_gateway_recover.html", &baseTemplateData{
  236. TitleBar: T(c, "Recover account"),
  237. KyutGrill: "2fa.jpg",
  238. Messages: []message{errorMessage{T(c, "Recovery code is invalid.")}},
  239. })
  240. }
  241. // deletes expired 2fa confirmation tokens. gets current confirmation token.
  242. // if it does not exist, generates one.
  243. func get2faConfirmationToken(user int) (token string) {
  244. db.Exec("DELETE FROM 2fa_confirmation WHERE expire < ?", time.Now().Unix())
  245. db.Get(&token, "SELECT token FROM 2fa_confirmation WHERE userid = ? LIMIT 1", user)
  246. if token != "" {
  247. return
  248. }
  249. token = rs.String(32)
  250. db.Exec("INSERT INTO 2fa_confirmation (userid, token, expire) VALUES (?, ?, ?)",
  251. user, token, time.Now().Add(time.Hour).Unix())
  252. return
  253. }
  254. func disable2fa(c *gin.Context) {
  255. ctx := getContext(c)
  256. if ctx.User.ID == 0 {
  257. resp403(c)
  258. return
  259. }
  260. s := getSession(c)
  261. var m message
  262. defer func() {
  263. addMessage(c, m)
  264. s.Save()
  265. c.Redirect(302, "/settings/2fa")
  266. }()
  267. if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok {
  268. m = errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")}
  269. return
  270. }
  271. var pass string
  272. db.Get(&pass, "SELECT password_md5 FROM users WHERE id = ?", ctx.User.ID)
  273. if err := bcrypt.CompareHashAndPassword(
  274. []byte(pass),
  275. []byte(cmd5(c.PostForm("password"))),
  276. ); err != nil {
  277. m = errorMessage{"Wrong password."}
  278. return
  279. }
  280. db.Exec("DELETE FROM 2fa_telegram WHERE userid = ?", ctx.User.ID)
  281. db.Exec("DELETE FROM 2fa_totp WHERE userid = ?", ctx.User.ID)
  282. m = successMessage{T(c, "2FA disabled successfully.")}
  283. }
  284. func totpSetup(c *gin.Context) {
  285. ctx := getContext(c)
  286. sess := getSession(c)
  287. if ctx.User.ID == 0 {
  288. resp403(c)
  289. return
  290. }
  291. defer c.Redirect(302, "/settings/2fa")
  292. defer sess.Save()
  293. if ok, _ := CSRF.Validate(ctx.User.ID, c.PostForm("csrf")); !ok {
  294. addMessage(c, errorMessage{T(c, "Your session has expired. Please try redoing what you were trying to do.")})
  295. return
  296. }
  297. switch is2faEnabled(ctx.User.ID) {
  298. case 1:
  299. addMessage(c, errorMessage{T(c, "You currently have Telegram 2FA enabled. You first need to disable that if you want to use TOTP-based 2FA.")})
  300. return
  301. case 2:
  302. addMessage(c, errorMessage{T(c, "TOTP-based 2FA is already enabled!")})
  303. return
  304. }
  305. pc := strings.Replace(c.PostForm("passcode"), " ", "", -1)
  306. var secret string
  307. db.Get(&secret, "SELECT secret FROM 2fa_totp WHERE userid = ?", ctx.User.ID)
  308. if secret == "" || pc == "" {
  309. addMessage(c, errorMessage{T(c, "No passcode/secret was given. Please try again")})
  310. return
  311. }
  312. fmt.Println(pc, secret)
  313. if !totp.Validate(pc, secret) {
  314. addMessage(c, errorMessage{T(c, "Passcode is invalid. Perhaps it expired?")})
  315. return
  316. }
  317. codes, _ := json.Marshal(generateRecoveryCodes())
  318. db.Exec("UPDATE 2fa_totp SET recovery = ?, enabled = 1 WHERE userid = ?", string(codes), ctx.User.ID)
  319. addMessage(c, successMessage{T(c, "TOTP-based 2FA has been enabled on your account.")})
  320. }
  321. func generateRecoveryCodes() []string {
  322. x := make([]string, 8)
  323. for i := range x {
  324. x[i] = rs.StringFromChars(6, "QWERTYUIOPASDFGHJKLZXCVBNM1234567890")
  325. }
  326. return x
  327. }
  328. func generateKey(ctx context) *otp.Key {
  329. k, err := totp.Generate(totp.GenerateOpts{
  330. Issuer: "Ripple",
  331. AccountName: ctx.User.Username,
  332. })
  333. if err != nil {
  334. return nil
  335. }
  336. db.Exec("INSERT INTO 2fa_totp(userid, secret) VALUES (?, ?) ON DUPLICATE KEY UPDATE secret = VALUES(secret)", ctx.User.ID, k.Secret())
  337. return k
  338. }