A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 

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