A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 

1252 righe
34 KiB

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