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.
 
 
 
 
 

1042 rivejä
27 KiB

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