A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
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.
 
 
 
 
 

1052 lines
27 KiB

  1. /*
  2. * Copyright © 2018 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "github.com/gorilla/mux"
  15. "github.com/gorilla/sessions"
  16. "github.com/guregu/null/zero"
  17. "github.com/writeas/impart"
  18. "github.com/writeas/web-core/auth"
  19. "github.com/writeas/web-core/data"
  20. "github.com/writeas/web-core/log"
  21. "github.com/writeas/writefreely/author"
  22. "github.com/writeas/writefreely/page"
  23. "html/template"
  24. "net/http"
  25. "regexp"
  26. "strings"
  27. "sync"
  28. "time"
  29. )
  30. type (
  31. userSettings struct {
  32. Username string `schema:"username" json:"username"`
  33. Email string `schema:"email" json:"email"`
  34. NewPass string `schema:"new-pass" json:"new_pass"`
  35. OldPass string `schema:"current-pass" json:"current_pass"`
  36. IsLogOut bool `schema:"logout" json:"logout"`
  37. }
  38. UserPage struct {
  39. page.StaticPage
  40. PageTitle string
  41. Separator template.HTML
  42. IsAdmin bool
  43. }
  44. )
  45. func NewUserPage(app *app, r *http.Request, u *User, title string, flashes []string) *UserPage {
  46. up := &UserPage{
  47. StaticPage: pageForReq(app, r),
  48. PageTitle: title,
  49. }
  50. up.Username = u.Username
  51. up.Flashes = flashes
  52. up.Path = r.URL.Path
  53. up.IsAdmin = u.IsAdmin()
  54. return up
  55. }
  56. func (up *UserPage) SetMessaging(u *User) {
  57. //up.NeedsAuth = app.db.DoesUserNeedAuth(u.ID)
  58. }
  59. const (
  60. loginAttemptExpiration = 3 * time.Second
  61. )
  62. var actuallyUsernameReg = regexp.MustCompile("username is actually ([a-z0-9\\-]+)\\. Please try that, instead")
  63. func apiSignup(app *app, w http.ResponseWriter, r *http.Request) error {
  64. _, err := signup(app, w, r)
  65. return err
  66. }
  67. func signup(app *app, w http.ResponseWriter, r *http.Request) (*AuthUser, error) {
  68. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  69. // Get params
  70. var ur userRegistration
  71. if reqJSON {
  72. decoder := json.NewDecoder(r.Body)
  73. err := decoder.Decode(&ur)
  74. if err != nil {
  75. log.Error("Couldn't parse signup JSON request: %v\n", err)
  76. return nil, ErrBadJSON
  77. }
  78. } else {
  79. // Check if user is already logged in
  80. u := getUserSession(app, r)
  81. if u != nil {
  82. return &AuthUser{User: u}, nil
  83. }
  84. err := r.ParseForm()
  85. if err != nil {
  86. log.Error("Couldn't parse signup form request: %v\n", err)
  87. return nil, ErrBadFormData
  88. }
  89. err = app.formDecoder.Decode(&ur, r.PostForm)
  90. if err != nil {
  91. log.Error("Couldn't decode signup form request: %v\n", err)
  92. return nil, ErrBadFormData
  93. }
  94. }
  95. return signupWithRegistration(app, ur, w, r)
  96. }
  97. func signupWithRegistration(app *app, signup userRegistration, w http.ResponseWriter, r *http.Request) (*AuthUser, error) {
  98. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  99. // Validate required params (alias)
  100. if signup.Alias == "" {
  101. return nil, impart.HTTPError{http.StatusBadRequest, "A username is required."}
  102. }
  103. if signup.Pass == "" {
  104. return nil, impart.HTTPError{http.StatusBadRequest, "A password is required."}
  105. }
  106. var desiredUsername string
  107. if signup.Normalize {
  108. // With this option we simply conform the username to what we expect
  109. // without complaining. Since they might've done something funny, like
  110. // enter: write.as/Way Out There, we'll use their raw input for the new
  111. // collection name and sanitize for the slug / username.
  112. desiredUsername = signup.Alias
  113. signup.Alias = getSlug(signup.Alias, "")
  114. }
  115. if !author.IsValidUsername(app.cfg, signup.Alias) {
  116. // Ensure the username is syntactically correct.
  117. return nil, impart.HTTPError{http.StatusPreconditionFailed, "Username is reserved or isn't valid. It must be at least 3 characters long, and can only include letters, numbers, and hyphens."}
  118. }
  119. // Handle empty optional params
  120. // TODO: remove this var
  121. createdWithPass := true
  122. hashedPass, err := auth.HashPass([]byte(signup.Pass))
  123. if err != nil {
  124. return nil, impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
  125. }
  126. // Create struct to insert
  127. u := &User{
  128. Username: signup.Alias,
  129. HashedPass: hashedPass,
  130. HasPass: createdWithPass,
  131. Email: zero.NewString("", signup.Email != ""),
  132. Created: time.Now().Truncate(time.Second).UTC(),
  133. }
  134. if signup.Email != "" {
  135. encEmail, err := data.Encrypt(app.keys.emailKey, signup.Email)
  136. if err != nil {
  137. log.Error("Unable to encrypt email: %s\n", err)
  138. } else {
  139. u.Email.String = string(encEmail)
  140. }
  141. }
  142. // Create actual user
  143. if err := app.db.CreateUser(u, desiredUsername); err != nil {
  144. return nil, err
  145. }
  146. // Add back unencrypted data for response
  147. if signup.Email != "" {
  148. u.Email.String = signup.Email
  149. }
  150. resUser := &AuthUser{
  151. User: u,
  152. }
  153. if !createdWithPass {
  154. resUser.Password = signup.Pass
  155. }
  156. title := signup.Alias
  157. if signup.Normalize {
  158. title = desiredUsername
  159. }
  160. resUser.Collections = &[]Collection{
  161. {
  162. Alias: signup.Alias,
  163. Title: title,
  164. },
  165. }
  166. var token string
  167. if reqJSON && !signup.Web {
  168. token, err = app.db.GetAccessToken(u.ID)
  169. if err != nil {
  170. return nil, impart.HTTPError{http.StatusInternalServerError, "Could not create access token. Try re-authenticating."}
  171. }
  172. resUser.AccessToken = token
  173. } else {
  174. session, err := app.sessionStore.Get(r, cookieName)
  175. if err != nil {
  176. // The cookie should still save, even if there's an error.
  177. // Source: https://github.com/gorilla/sessions/issues/16#issuecomment-143642144
  178. log.Error("Session: %v; ignoring", err)
  179. }
  180. session.Values[cookieUserVal] = resUser.User.Cookie()
  181. err = session.Save(r, w)
  182. if err != nil {
  183. log.Error("Couldn't save session: %v", err)
  184. return nil, err
  185. }
  186. }
  187. if reqJSON {
  188. return resUser, impart.WriteSuccess(w, resUser, http.StatusCreated)
  189. }
  190. return resUser, nil
  191. }
  192. func viewLogout(app *app, w http.ResponseWriter, r *http.Request) error {
  193. session, err := app.sessionStore.Get(r, cookieName)
  194. if err != nil {
  195. return ErrInternalCookieSession
  196. }
  197. // Ensure user has an email or password set before they go, so they don't
  198. // lose access to their account.
  199. val := session.Values[cookieUserVal]
  200. var u = &User{}
  201. var ok bool
  202. if u, ok = val.(*User); !ok {
  203. log.Error("Error casting user object on logout. Vals: %+v Resetting cookie.", session.Values)
  204. err = session.Save(r, w)
  205. if err != nil {
  206. log.Error("Couldn't save session on logout: %v", err)
  207. return impart.HTTPError{http.StatusInternalServerError, "Unable to save cookie session."}
  208. }
  209. return impart.HTTPError{http.StatusFound, "/"}
  210. }
  211. u, err = app.db.GetUserByID(u.ID)
  212. if err != nil && err != ErrUserNotFound {
  213. return impart.HTTPError{http.StatusInternalServerError, "Unable to fetch user information."}
  214. }
  215. session.Options.MaxAge = -1
  216. err = session.Save(r, w)
  217. if err != nil {
  218. log.Error("Couldn't save session on logout: %v", err)
  219. return impart.HTTPError{http.StatusInternalServerError, "Unable to save cookie session."}
  220. }
  221. return impart.HTTPError{http.StatusFound, "/"}
  222. }
  223. func handleAPILogout(app *app, w http.ResponseWriter, r *http.Request) error {
  224. accessToken := r.Header.Get("Authorization")
  225. if accessToken == "" {
  226. return ErrNoAccessToken
  227. }
  228. t := auth.GetToken(accessToken)
  229. if len(t) == 0 {
  230. return ErrNoAccessToken
  231. }
  232. err := app.db.DeleteToken(t)
  233. if err != nil {
  234. return err
  235. }
  236. return impart.HTTPError{Status: http.StatusNoContent}
  237. }
  238. func viewLogin(app *app, w http.ResponseWriter, r *http.Request) error {
  239. var earlyError string
  240. oneTimeToken := r.FormValue("with")
  241. if oneTimeToken != "" {
  242. log.Info("Calling login with one-time token.")
  243. err := login(app, w, r)
  244. if err != nil {
  245. log.Info("Received error: %v", err)
  246. earlyError = fmt.Sprintf("%s", err)
  247. }
  248. }
  249. session, err := app.sessionStore.Get(r, cookieName)
  250. if err != nil {
  251. // Ignore this
  252. log.Error("Unable to get session; ignoring: %v", err)
  253. }
  254. p := &struct {
  255. page.StaticPage
  256. To string
  257. Message template.HTML
  258. Flashes []template.HTML
  259. Username string
  260. }{
  261. pageForReq(app, r),
  262. r.FormValue("to"),
  263. template.HTML(""),
  264. []template.HTML{},
  265. getTempInfo(app, "login-user", r, w),
  266. }
  267. if earlyError != "" {
  268. p.Flashes = append(p.Flashes, template.HTML(earlyError))
  269. }
  270. // Display any error messages
  271. flashes, _ := getSessionFlashes(app, w, r, session)
  272. for _, flash := range flashes {
  273. p.Flashes = append(p.Flashes, template.HTML(flash))
  274. }
  275. err = pages["login.tmpl"].ExecuteTemplate(w, "base", p)
  276. if err != nil {
  277. log.Error("Unable to render login: %v", err)
  278. return err
  279. }
  280. return nil
  281. }
  282. func webLogin(app *app, w http.ResponseWriter, r *http.Request) error {
  283. err := login(app, w, r)
  284. if err != nil {
  285. username := r.FormValue("alias")
  286. // Login request was unsuccessful; save the error in the session and redirect them
  287. if err, ok := err.(impart.HTTPError); ok {
  288. session, _ := app.sessionStore.Get(r, cookieName)
  289. if session != nil {
  290. session.AddFlash(err.Message)
  291. session.Save(r, w)
  292. }
  293. if m := actuallyUsernameReg.FindStringSubmatch(err.Message); len(m) > 0 {
  294. // Retain fixed username recommendation for the login form
  295. username = m[1]
  296. }
  297. }
  298. // Pass along certain information
  299. saveTempInfo(app, "login-user", username, r, w)
  300. // Retain post-login URL if one was given
  301. redirectTo := "/login"
  302. postLoginRedirect := r.FormValue("to")
  303. if postLoginRedirect != "" {
  304. redirectTo += "?to=" + postLoginRedirect
  305. }
  306. log.Error("Unable to login: %v", err)
  307. return impart.HTTPError{http.StatusTemporaryRedirect, redirectTo}
  308. }
  309. return nil
  310. }
  311. var loginAttemptUsers = sync.Map{}
  312. func login(app *app, w http.ResponseWriter, r *http.Request) error {
  313. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  314. oneTimeToken := r.FormValue("with")
  315. verbose := r.FormValue("all") == "true" || r.FormValue("verbose") == "1" || r.FormValue("verbose") == "true" || (reqJSON && oneTimeToken != "")
  316. redirectTo := r.FormValue("to")
  317. if redirectTo == "" {
  318. if app.cfg.App.SingleUser {
  319. redirectTo = "/me/new"
  320. } else {
  321. redirectTo = "/"
  322. }
  323. }
  324. var u *User
  325. var err error
  326. var signin userCredentials
  327. // Log in with one-time token if one is given
  328. if oneTimeToken != "" {
  329. log.Info("Login: Logging user in via token.")
  330. userID := app.db.GetUserID(oneTimeToken)
  331. if userID == -1 {
  332. log.Error("Login: Got user -1 from token")
  333. err := ErrBadAccessToken
  334. err.Message = "Expired or invalid login code."
  335. return err
  336. }
  337. log.Info("Login: Found user %d.", userID)
  338. u, err = app.db.GetUserByID(userID)
  339. if err != nil {
  340. log.Error("Unable to fetch user on one-time token login: %v", err)
  341. return impart.HTTPError{http.StatusInternalServerError, "There was an error retrieving the user you want."}
  342. }
  343. log.Info("Login: Got user via token")
  344. } else {
  345. // Get params
  346. if reqJSON {
  347. decoder := json.NewDecoder(r.Body)
  348. err := decoder.Decode(&signin)
  349. if err != nil {
  350. log.Error("Couldn't parse signin JSON request: %v\n", err)
  351. return ErrBadJSON
  352. }
  353. } else {
  354. err := r.ParseForm()
  355. if err != nil {
  356. log.Error("Couldn't parse signin form request: %v\n", err)
  357. return ErrBadFormData
  358. }
  359. err = app.formDecoder.Decode(&signin, r.PostForm)
  360. if err != nil {
  361. log.Error("Couldn't decode signin form request: %v\n", err)
  362. return ErrBadFormData
  363. }
  364. }
  365. log.Info("Login: Attempting login for '%s'", signin.Alias)
  366. // Validate required params (all)
  367. if signin.Alias == "" {
  368. msg := "Parameter `alias` required."
  369. if signin.Web {
  370. msg = "A username is required."
  371. }
  372. return impart.HTTPError{http.StatusBadRequest, msg}
  373. }
  374. if !signin.EmailLogin && signin.Pass == "" {
  375. msg := "Parameter `pass` required."
  376. if signin.Web {
  377. msg = "A password is required."
  378. }
  379. return impart.HTTPError{http.StatusBadRequest, msg}
  380. }
  381. // Prevent excessive login attempts on the same account
  382. // Skip this check in dev environment
  383. if !app.cfg.Server.Dev {
  384. now := time.Now()
  385. attemptExp, att := loginAttemptUsers.LoadOrStore(signin.Alias, now.Add(loginAttemptExpiration))
  386. if att {
  387. if attemptExpTime, ok := attemptExp.(time.Time); ok {
  388. if attemptExpTime.After(now) {
  389. // This user attempted previously, and the period hasn't expired yet
  390. return impart.HTTPError{http.StatusTooManyRequests, "You're doing that too much."}
  391. } else {
  392. // This user attempted previously, but the time expired; free up space
  393. loginAttemptUsers.Delete(signin.Alias)
  394. }
  395. } else {
  396. log.Error("Unable to cast expiration to time")
  397. }
  398. }
  399. }
  400. // Retrieve password
  401. u, err = app.db.GetUserForAuth(signin.Alias)
  402. if err != nil {
  403. log.Info("Unable to getUserForAuth on %s: %v", signin.Alias, err)
  404. if strings.IndexAny(signin.Alias, "@") > 0 {
  405. log.Info("Suggesting: %s", ErrUserNotFoundEmail.Message)
  406. return ErrUserNotFoundEmail
  407. }
  408. return err
  409. }
  410. // Authenticate
  411. if u.Email.String == "" {
  412. // User has no email set, so check if they haven't added a password, either,
  413. // so we can return a more helpful error message.
  414. if hasPass, _ := app.db.IsUserPassSet(u.ID); !hasPass {
  415. log.Info("Tried logging in to %s, but no password or email.", signin.Alias)
  416. return impart.HTTPError{http.StatusPreconditionFailed, "This user never added a password or email address. Please contact us for help."}
  417. }
  418. }
  419. if !auth.Authenticated(u.HashedPass, []byte(signin.Pass)) {
  420. return impart.HTTPError{http.StatusUnauthorized, "Incorrect password."}
  421. }
  422. }
  423. if reqJSON && !signin.Web {
  424. var token string
  425. if r.Header.Get("User-Agent") == "" {
  426. // Get last created token when User-Agent is empty
  427. token = app.db.FetchLastAccessToken(u.ID)
  428. if token == "" {
  429. token, err = app.db.GetAccessToken(u.ID)
  430. }
  431. } else {
  432. token, err = app.db.GetAccessToken(u.ID)
  433. }
  434. if err != nil {
  435. log.Error("Login: Unable to create access token: %v", err)
  436. return impart.HTTPError{http.StatusInternalServerError, "Could not create access token. Try re-authenticating."}
  437. }
  438. resUser := getVerboseAuthUser(app, token, u, verbose)
  439. return impart.WriteSuccess(w, resUser, http.StatusOK)
  440. }
  441. session, err := app.sessionStore.Get(r, cookieName)
  442. if err != nil {
  443. // The cookie should still save, even if there's an error.
  444. log.Error("Login: Session: %v; ignoring", err)
  445. }
  446. // Remove unwanted data
  447. session.Values[cookieUserVal] = u.Cookie()
  448. err = session.Save(r, w)
  449. if err != nil {
  450. log.Error("Login: Couldn't save session: %v", err)
  451. // TODO: return error
  452. }
  453. // Send success
  454. if reqJSON {
  455. return impart.WriteSuccess(w, &AuthUser{User: u}, http.StatusOK)
  456. }
  457. log.Info("Login: Redirecting to %s", redirectTo)
  458. w.Header().Set("Location", redirectTo)
  459. w.WriteHeader(http.StatusFound)
  460. return nil
  461. }
  462. func getVerboseAuthUser(app *app, token string, u *User, verbose bool) *AuthUser {
  463. resUser := &AuthUser{
  464. AccessToken: token,
  465. User: u,
  466. }
  467. // Fetch verbose user data if requested
  468. if verbose {
  469. posts, err := app.db.GetUserPosts(u)
  470. if err != nil {
  471. log.Error("Login: Unable to get user posts: %v", err)
  472. }
  473. colls, err := app.db.GetCollections(u)
  474. if err != nil {
  475. log.Error("Login: Unable to get user collections: %v", err)
  476. }
  477. passIsSet, err := app.db.IsUserPassSet(u.ID)
  478. if err != nil {
  479. // TODO: correct error meesage
  480. log.Error("Login: Unable to get user collections: %v", err)
  481. }
  482. resUser.Posts = posts
  483. resUser.Collections = colls
  484. resUser.User.HasPass = passIsSet
  485. }
  486. return resUser
  487. }
  488. func viewExportOptions(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  489. // Fetch extra user data
  490. p := NewUserPage(app, r, u, "Export", nil)
  491. showUserPage(w, "export", p)
  492. return nil
  493. }
  494. func viewExportPosts(app *app, w http.ResponseWriter, r *http.Request) ([]byte, string, error) {
  495. var filename string
  496. var u = &User{}
  497. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  498. if reqJSON {
  499. // Use given Authorization header
  500. accessToken := r.Header.Get("Authorization")
  501. if accessToken == "" {
  502. return nil, filename, ErrNoAccessToken
  503. }
  504. userID := app.db.GetUserID(accessToken)
  505. if userID == -1 {
  506. return nil, filename, ErrBadAccessToken
  507. }
  508. var err error
  509. u, err = app.db.GetUserByID(userID)
  510. if err != nil {
  511. return nil, filename, impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve requested user."}
  512. }
  513. } else {
  514. // Use user cookie
  515. session, err := app.sessionStore.Get(r, cookieName)
  516. if err != nil {
  517. // The cookie should still save, even if there's an error.
  518. log.Error("Session: %v; ignoring", err)
  519. }
  520. val := session.Values[cookieUserVal]
  521. var ok bool
  522. if u, ok = val.(*User); !ok {
  523. return nil, filename, ErrNotLoggedIn
  524. }
  525. }
  526. filename = u.Username + "-posts-" + time.Now().Truncate(time.Second).UTC().Format("200601021504")
  527. // Fetch data we're exporting
  528. var err error
  529. var data []byte
  530. posts, err := app.db.GetUserPosts(u)
  531. if err != nil {
  532. return data, filename, err
  533. }
  534. // Export as CSV
  535. if strings.HasSuffix(r.URL.Path, ".csv") {
  536. data = exportPostsCSV(u, posts)
  537. return data, filename, err
  538. }
  539. if strings.HasSuffix(r.URL.Path, ".zip") {
  540. data = exportPostsZip(u, posts)
  541. return data, filename, err
  542. }
  543. if r.FormValue("pretty") == "1" {
  544. data, err = json.MarshalIndent(posts, "", "\t")
  545. } else {
  546. data, err = json.Marshal(posts)
  547. }
  548. return data, filename, err
  549. }
  550. func viewExportFull(app *app, w http.ResponseWriter, r *http.Request) ([]byte, string, error) {
  551. var err error
  552. filename := ""
  553. u := getUserSession(app, r)
  554. if u == nil {
  555. return nil, filename, ErrNotLoggedIn
  556. }
  557. filename = u.Username + "-" + time.Now().Truncate(time.Second).UTC().Format("200601021504")
  558. exportUser := compileFullExport(app, u)
  559. var data []byte
  560. if r.FormValue("pretty") == "1" {
  561. data, err = json.MarshalIndent(exportUser, "", "\t")
  562. } else {
  563. data, err = json.Marshal(exportUser)
  564. }
  565. return data, filename, err
  566. }
  567. func viewMeAPI(app *app, w http.ResponseWriter, r *http.Request) error {
  568. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  569. uObj := struct {
  570. ID int64 `json:"id,omitempty"`
  571. Username string `json:"username,omitempty"`
  572. }{}
  573. var err error
  574. if reqJSON {
  575. _, uObj.Username, err = app.db.GetUserDataFromToken(r.Header.Get("Authorization"))
  576. if err != nil {
  577. return err
  578. }
  579. } else {
  580. u := getUserSession(app, r)
  581. if u == nil {
  582. return impart.WriteSuccess(w, uObj, http.StatusOK)
  583. }
  584. uObj.Username = u.Username
  585. }
  586. return impart.WriteSuccess(w, uObj, http.StatusOK)
  587. }
  588. func viewMyPostsAPI(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  589. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  590. if !reqJSON {
  591. return ErrBadRequestedType
  592. }
  593. var err error
  594. p := GetPostsCache(u.ID)
  595. if p == nil {
  596. userPostsCache.Lock()
  597. if userPostsCache.users[u.ID].ready == nil {
  598. userPostsCache.users[u.ID] = postsCacheItem{ready: make(chan struct{})}
  599. userPostsCache.Unlock()
  600. p, err = app.db.GetUserPosts(u)
  601. if err != nil {
  602. return err
  603. }
  604. CachePosts(u.ID, p)
  605. } else {
  606. userPostsCache.Unlock()
  607. <-userPostsCache.users[u.ID].ready
  608. p = GetPostsCache(u.ID)
  609. }
  610. }
  611. return impart.WriteSuccess(w, p, http.StatusOK)
  612. }
  613. func viewMyCollectionsAPI(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  614. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  615. if !reqJSON {
  616. return ErrBadRequestedType
  617. }
  618. p, err := app.db.GetCollections(u)
  619. if err != nil {
  620. return err
  621. }
  622. return impart.WriteSuccess(w, p, http.StatusOK)
  623. }
  624. func viewArticles(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  625. p, err := app.db.GetAnonymousPosts(u)
  626. if err != nil {
  627. log.Error("unable to fetch anon posts: %v", err)
  628. }
  629. // nil-out AnonymousPosts slice for easy detection in the template
  630. if p != nil && len(*p) == 0 {
  631. p = nil
  632. }
  633. f, err := getSessionFlashes(app, w, r, nil)
  634. if err != nil {
  635. log.Error("unable to fetch flashes: %v", err)
  636. }
  637. c, err := app.db.GetPublishableCollections(u)
  638. if err != nil {
  639. log.Error("unable to fetch collections: %v", err)
  640. }
  641. d := struct {
  642. *UserPage
  643. AnonymousPosts *[]PublicPost
  644. Collections *[]Collection
  645. }{
  646. UserPage: NewUserPage(app, r, u, u.Username+"'s Posts", f),
  647. AnonymousPosts: p,
  648. Collections: c,
  649. }
  650. d.UserPage.SetMessaging(u)
  651. w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  652. w.Header().Set("Expires", "Thu, 04 Oct 1990 20:00:00 GMT")
  653. showUserPage(w, "articles", d)
  654. return nil
  655. }
  656. func viewCollections(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  657. c, err := app.db.GetCollections(u)
  658. if err != nil {
  659. log.Error("unable to fetch collections: %v", err)
  660. return fmt.Errorf("No collections")
  661. }
  662. f, _ := getSessionFlashes(app, w, r, nil)
  663. uc, _ := app.db.GetUserCollectionCount(u.ID)
  664. // TODO: handle any errors
  665. d := struct {
  666. *UserPage
  667. Collections *[]Collection
  668. UsedCollections, TotalCollections int
  669. NewBlogsDisabled bool
  670. }{
  671. UserPage: NewUserPage(app, r, u, u.Username+"'s Blogs", f),
  672. Collections: c,
  673. UsedCollections: int(uc),
  674. NewBlogsDisabled: !app.cfg.App.CanCreateBlogs(uc),
  675. }
  676. d.UserPage.SetMessaging(u)
  677. showUserPage(w, "collections", d)
  678. return nil
  679. }
  680. func viewEditCollection(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  681. vars := mux.Vars(r)
  682. c, err := app.db.GetCollection(vars["collection"])
  683. if err != nil {
  684. return err
  685. }
  686. if c.OwnerID != u.ID {
  687. return ErrCollectionNotFound
  688. }
  689. flashes, _ := getSessionFlashes(app, w, r, nil)
  690. obj := struct {
  691. *UserPage
  692. *Collection
  693. }{
  694. UserPage: NewUserPage(app, r, u, "Edit "+c.DisplayTitle(), flashes),
  695. Collection: c,
  696. }
  697. if err := userPages["user/collection.tmpl"].ExecuteTemplate(w, "collection", obj); err != nil {
  698. log.Error("Error parsing user collection: %v", err)
  699. }
  700. return nil
  701. }
  702. func updateSettings(app *app, w http.ResponseWriter, r *http.Request) error {
  703. reqJSON := IsJSON(r.Header.Get("Content-Type"))
  704. var s userSettings
  705. var u *User
  706. var sess *sessions.Session
  707. var err error
  708. if reqJSON {
  709. accessToken := r.Header.Get("Authorization")
  710. if accessToken == "" {
  711. return ErrNoAccessToken
  712. }
  713. u, err = app.db.GetAPIUser(accessToken)
  714. if err != nil {
  715. return ErrBadAccessToken
  716. }
  717. decoder := json.NewDecoder(r.Body)
  718. err := decoder.Decode(&s)
  719. if err != nil {
  720. log.Error("Couldn't parse settings JSON request: %v\n", err)
  721. return ErrBadJSON
  722. }
  723. // Prevent all username updates
  724. // TODO: support changing username via JSON API request
  725. s.Username = ""
  726. } else {
  727. u, sess = getUserAndSession(app, r)
  728. if u == nil {
  729. return ErrNotLoggedIn
  730. }
  731. err := r.ParseForm()
  732. if err != nil {
  733. log.Error("Couldn't parse settings form request: %v\n", err)
  734. return ErrBadFormData
  735. }
  736. err = app.formDecoder.Decode(&s, r.PostForm)
  737. if err != nil {
  738. log.Error("Couldn't decode settings form request: %v\n", err)
  739. return ErrBadFormData
  740. }
  741. }
  742. // Do update
  743. postUpdateReturn := r.FormValue("return")
  744. redirectTo := "/me/settings"
  745. if s.IsLogOut {
  746. redirectTo += "?logout=1"
  747. } else if postUpdateReturn != "" {
  748. redirectTo = postUpdateReturn
  749. }
  750. // Only do updates on values we need
  751. if s.Username != "" && s.Username == u.Username {
  752. // Username hasn't actually changed; blank it out
  753. s.Username = ""
  754. }
  755. err = app.db.ChangeSettings(app, u, &s)
  756. if err != nil {
  757. if reqJSON {
  758. return err
  759. }
  760. if err, ok := err.(impart.HTTPError); ok {
  761. addSessionFlash(app, w, r, err.Message, nil)
  762. }
  763. } else {
  764. // Successful update.
  765. if reqJSON {
  766. return impart.WriteSuccess(w, u, http.StatusOK)
  767. }
  768. if s.IsLogOut {
  769. redirectTo = "/me/logout"
  770. } else {
  771. sess.Values[cookieUserVal] = u.Cookie()
  772. addSessionFlash(app, w, r, "Account updated.", nil)
  773. }
  774. }
  775. w.Header().Set("Location", redirectTo)
  776. w.WriteHeader(http.StatusFound)
  777. return nil
  778. }
  779. func updatePassphrase(app *app, w http.ResponseWriter, r *http.Request) error {
  780. accessToken := r.Header.Get("Authorization")
  781. if accessToken == "" {
  782. return ErrNoAccessToken
  783. }
  784. curPass := r.FormValue("current")
  785. newPass := r.FormValue("new")
  786. // Ensure a new password is given (always required)
  787. if newPass == "" {
  788. return impart.HTTPError{http.StatusBadRequest, "Provide a new password."}
  789. }
  790. userID, sudo := app.db.GetUserIDPrivilege(accessToken)
  791. if userID == -1 {
  792. return ErrBadAccessToken
  793. }
  794. // Ensure a current password is given if the access token doesn't have sudo
  795. // privileges.
  796. if !sudo && curPass == "" {
  797. return impart.HTTPError{http.StatusBadRequest, "Provide current password."}
  798. }
  799. // Hash the new password
  800. hashedPass, err := auth.HashPass([]byte(newPass))
  801. if err != nil {
  802. return impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
  803. }
  804. // Do update
  805. err = app.db.ChangePassphrase(userID, sudo, curPass, hashedPass)
  806. if err != nil {
  807. return err
  808. }
  809. return impart.WriteSuccess(w, struct{}{}, http.StatusOK)
  810. }
  811. func viewStats(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  812. var c *Collection
  813. var err error
  814. vars := mux.Vars(r)
  815. alias := vars["collection"]
  816. if alias != "" {
  817. c, err = app.db.GetCollection(alias)
  818. if err != nil {
  819. return err
  820. }
  821. if c.OwnerID != u.ID {
  822. return ErrCollectionNotFound
  823. }
  824. }
  825. topPosts, err := app.db.GetTopPosts(u, alias)
  826. if err != nil {
  827. log.Error("Unable to get top posts: %v", err)
  828. return err
  829. }
  830. flashes, _ := getSessionFlashes(app, w, r, nil)
  831. titleStats := ""
  832. if c != nil {
  833. titleStats = c.DisplayTitle() + " "
  834. }
  835. obj := struct {
  836. *UserPage
  837. VisitsBlog string
  838. Collection *Collection
  839. TopPosts *[]PublicPost
  840. APFollowers int
  841. }{
  842. UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes),
  843. VisitsBlog: alias,
  844. Collection: c,
  845. TopPosts: topPosts,
  846. }
  847. if app.cfg.App.Federation {
  848. folls, err := app.db.GetAPFollowers(c)
  849. if err != nil {
  850. return err
  851. }
  852. obj.APFollowers = len(*folls)
  853. }
  854. showUserPage(w, "stats", obj)
  855. return nil
  856. }
  857. func viewSettings(app *app, u *User, w http.ResponseWriter, r *http.Request) error {
  858. fullUser, err := app.db.GetUserByID(u.ID)
  859. if err != nil {
  860. log.Error("Unable to get user for settings: %s", err)
  861. return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."}
  862. }
  863. passIsSet, err := app.db.IsUserPassSet(u.ID)
  864. if err != nil {
  865. log.Error("Unable to get isUserPassSet for settings: %s", err)
  866. return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."}
  867. }
  868. flashes, _ := getSessionFlashes(app, w, r, nil)
  869. obj := struct {
  870. *UserPage
  871. Email string
  872. HasPass bool
  873. IsLogOut bool
  874. }{
  875. UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
  876. Email: fullUser.EmailClear(app.keys),
  877. HasPass: passIsSet,
  878. IsLogOut: r.FormValue("logout") == "1",
  879. }
  880. showUserPage(w, "settings", obj)
  881. return nil
  882. }
  883. func saveTempInfo(app *app, key, val string, r *http.Request, w http.ResponseWriter) error {
  884. session, err := app.sessionStore.Get(r, "t")
  885. if err != nil {
  886. return ErrInternalCookieSession
  887. }
  888. session.Values[key] = val
  889. err = session.Save(r, w)
  890. if err != nil {
  891. log.Error("Couldn't saveTempInfo for key-val (%s:%s): %v", key, val, err)
  892. }
  893. return err
  894. }
  895. func getTempInfo(app *app, key string, r *http.Request, w http.ResponseWriter) string {
  896. session, err := app.sessionStore.Get(r, "t")
  897. if err != nil {
  898. return ""
  899. }
  900. // Get the information
  901. var s = ""
  902. var ok bool
  903. if s, ok = session.Values[key].(string); !ok {
  904. return ""
  905. }
  906. // Delete cookie
  907. session.Options.MaxAge = -1
  908. err = session.Save(r, w)
  909. if err != nil {
  910. log.Error("Couldn't erase temp data for key %s: %v", key, err)
  911. }
  912. // Return value
  913. return s
  914. }