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.
 
 
 
 
 

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