A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 

1160 rindas
30 KiB

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