A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

994 linhas
25 KiB

  1. /*
  2. * Copyright © 2018-2021 Musing Studio 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. "crypto/tls"
  13. "database/sql"
  14. _ "embed"
  15. "fmt"
  16. "html/template"
  17. "io/ioutil"
  18. "net"
  19. "net/http"
  20. "net/url"
  21. "os"
  22. "os/signal"
  23. "path/filepath"
  24. "regexp"
  25. "strings"
  26. "syscall"
  27. "time"
  28. "github.com/gorilla/mux"
  29. "github.com/gorilla/schema"
  30. "github.com/gorilla/sessions"
  31. "github.com/manifoldco/promptui"
  32. stripmd "github.com/writeas/go-strip-markdown/v2"
  33. "github.com/writeas/impart"
  34. "github.com/writeas/web-core/auth"
  35. "github.com/writeas/web-core/converter"
  36. "github.com/writeas/web-core/log"
  37. "golang.org/x/crypto/acme/autocert"
  38. "github.com/writefreely/writefreely/author"
  39. "github.com/writefreely/writefreely/config"
  40. "github.com/writefreely/writefreely/key"
  41. "github.com/writefreely/writefreely/migrations"
  42. "github.com/writefreely/writefreely/page"
  43. )
  44. const (
  45. staticDir = "static"
  46. assumedTitleLen = 80
  47. postsPerPage = 10
  48. serverSoftware = "WriteFreely"
  49. softwareURL = "https://writefreely.org"
  50. )
  51. var (
  52. debugging bool
  53. // Software version can be set from git env using -ldflags
  54. softwareVer = "0.13.2"
  55. // DEPRECATED VARS
  56. isSingleUser bool
  57. )
  58. // App holds data and configuration for an individual WriteFreely instance.
  59. type App struct {
  60. router *mux.Router
  61. shttp *http.ServeMux
  62. db *datastore
  63. cfg *config.Config
  64. cfgFile string
  65. keys *key.Keychain
  66. sessionStore sessions.Store
  67. formDecoder *schema.Decoder
  68. updates *updatesCache
  69. timeline *localTimeline
  70. }
  71. // DB returns the App's datastore
  72. func (app *App) DB() *datastore {
  73. return app.db
  74. }
  75. // Router returns the App's router
  76. func (app *App) Router() *mux.Router {
  77. return app.router
  78. }
  79. // Config returns the App's current configuration.
  80. func (app *App) Config() *config.Config {
  81. return app.cfg
  82. }
  83. // SetConfig updates the App's Config to the given value.
  84. func (app *App) SetConfig(cfg *config.Config) {
  85. app.cfg = cfg
  86. }
  87. // SetKeys updates the App's Keychain to the given value.
  88. func (app *App) SetKeys(k *key.Keychain) {
  89. app.keys = k
  90. }
  91. func (app *App) SessionStore() sessions.Store {
  92. return app.sessionStore
  93. }
  94. func (app *App) SetSessionStore(s sessions.Store) {
  95. app.sessionStore = s
  96. }
  97. // Apper is the interface for getting data into and out of a WriteFreely
  98. // instance (or "App").
  99. //
  100. // App returns the App for the current instance.
  101. //
  102. // LoadConfig reads an app configuration into the App, returning any error
  103. // encountered.
  104. //
  105. // SaveConfig persists the current App configuration.
  106. //
  107. // LoadKeys reads the App's encryption keys and loads them into its
  108. // key.Keychain.
  109. type Apper interface {
  110. App() *App
  111. LoadConfig() error
  112. SaveConfig(*config.Config) error
  113. LoadKeys() error
  114. ReqLog(r *http.Request, status int, timeSince time.Duration) string
  115. }
  116. // App returns the App
  117. func (app *App) App() *App {
  118. return app
  119. }
  120. // LoadConfig loads and parses a config file.
  121. func (app *App) LoadConfig() error {
  122. log.Info("Loading %s configuration...", app.cfgFile)
  123. cfg, err := config.Load(app.cfgFile)
  124. if err != nil {
  125. log.Error("Unable to load configuration: %v", err)
  126. os.Exit(1)
  127. return err
  128. }
  129. app.cfg = cfg
  130. return nil
  131. }
  132. // SaveConfig saves the given Config to disk -- namely, to the App's cfgFile.
  133. func (app *App) SaveConfig(c *config.Config) error {
  134. return config.Save(c, app.cfgFile)
  135. }
  136. // LoadKeys reads all needed keys from disk into the App. In order to use the
  137. // configured `Server.KeysParentDir`, you must call initKeyPaths(App) before
  138. // this.
  139. func (app *App) LoadKeys() error {
  140. var err error
  141. app.keys = &key.Keychain{}
  142. if debugging {
  143. log.Info(" %s", emailKeyPath)
  144. }
  145. executable, err := os.Executable()
  146. if err != nil {
  147. executable = "writefreely"
  148. } else {
  149. executable = filepath.Base(executable)
  150. }
  151. app.keys.EmailKey, err = ioutil.ReadFile(emailKeyPath)
  152. if err != nil {
  153. return err
  154. }
  155. if debugging {
  156. log.Info(" %s", cookieAuthKeyPath)
  157. }
  158. app.keys.CookieAuthKey, err = ioutil.ReadFile(cookieAuthKeyPath)
  159. if err != nil {
  160. return err
  161. }
  162. if debugging {
  163. log.Info(" %s", cookieKeyPath)
  164. }
  165. app.keys.CookieKey, err = ioutil.ReadFile(cookieKeyPath)
  166. if err != nil {
  167. return err
  168. }
  169. if debugging {
  170. log.Info(" %s", csrfKeyPath)
  171. }
  172. app.keys.CSRFKey, err = ioutil.ReadFile(csrfKeyPath)
  173. if err != nil {
  174. if os.IsNotExist(err) {
  175. log.Error(`Missing key: %s.
  176. Run this command to generate missing keys:
  177. %s keys generate
  178. `, csrfKeyPath, executable)
  179. }
  180. return err
  181. }
  182. return nil
  183. }
  184. func (app *App) ReqLog(r *http.Request, status int, timeSince time.Duration) string {
  185. return fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, timeSince, r.UserAgent())
  186. }
  187. // handleViewHome shows page at root path. It checks the configuration and
  188. // authentication state to show the correct page.
  189. func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error {
  190. if app.cfg.App.SingleUser {
  191. // Render blog index
  192. return handleViewCollection(app, w, r)
  193. }
  194. // Multi-user instance
  195. forceLanding := r.FormValue("landing") == "1"
  196. if !forceLanding {
  197. // Show correct page based on user auth status and configured landing path
  198. u := getUserSession(app, r)
  199. if app.cfg.App.Chorus {
  200. // This instance is focused on reading, so show Reader on home route if not
  201. // private or a private-instance user is logged in.
  202. if !app.cfg.App.Private || u != nil {
  203. return viewLocalTimeline(app, w, r)
  204. }
  205. }
  206. if u != nil {
  207. // User is logged in, so show the Pad
  208. return handleViewPad(app, w, r)
  209. }
  210. if app.cfg.App.Private {
  211. return viewLogin(app, w, r)
  212. }
  213. if land := app.cfg.App.LandingPath(); land != "/" {
  214. return impart.HTTPError{http.StatusFound, land}
  215. }
  216. }
  217. return handleViewLanding(app, w, r)
  218. }
  219. func handleViewLanding(app *App, w http.ResponseWriter, r *http.Request) error {
  220. forceLanding := r.FormValue("landing") == "1"
  221. p := struct {
  222. page.StaticPage
  223. *OAuthButtons
  224. Flashes []template.HTML
  225. Banner template.HTML
  226. Content template.HTML
  227. ForcedLanding bool
  228. }{
  229. StaticPage: pageForReq(app, r),
  230. OAuthButtons: NewOAuthButtons(app.Config()),
  231. ForcedLanding: forceLanding,
  232. }
  233. banner, err := getLandingBanner(app)
  234. if err != nil {
  235. log.Error("unable to get landing banner: %v", err)
  236. return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get banner: %v", err)}
  237. }
  238. p.Banner = template.HTML(applyMarkdown([]byte(banner.Content), "", app.cfg))
  239. content, err := getLandingBody(app)
  240. if err != nil {
  241. log.Error("unable to get landing content: %v", err)
  242. return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get content: %v", err)}
  243. }
  244. p.Content = template.HTML(applyMarkdown([]byte(content.Content), "", app.cfg))
  245. // Get error messages
  246. session, err := app.sessionStore.Get(r, cookieName)
  247. if err != nil {
  248. // Ignore this
  249. log.Error("Unable to get session in handleViewHome; ignoring: %v", err)
  250. }
  251. flashes, _ := getSessionFlashes(app, w, r, session)
  252. for _, flash := range flashes {
  253. p.Flashes = append(p.Flashes, template.HTML(flash))
  254. }
  255. // Show landing page
  256. return renderPage(w, "landing.tmpl", p)
  257. }
  258. func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *template.Template) error {
  259. p := struct {
  260. page.StaticPage
  261. ContentTitle string
  262. Content template.HTML
  263. PlainContent string
  264. Updated string
  265. AboutStats *InstanceStats
  266. }{
  267. StaticPage: pageForReq(app, r),
  268. }
  269. if r.URL.Path == "/about" || r.URL.Path == "/contact" || r.URL.Path == "/privacy" {
  270. var c *instanceContent
  271. var err error
  272. if r.URL.Path == "/about" {
  273. c, err = getAboutPage(app)
  274. // Fetch stats
  275. p.AboutStats = &InstanceStats{}
  276. p.AboutStats.NumPosts, _ = app.db.GetTotalPosts()
  277. p.AboutStats.NumBlogs, _ = app.db.GetTotalCollections()
  278. } else if r.URL.Path == "/contact" {
  279. c, err = getContactPage(app)
  280. if c.Updated.IsZero() {
  281. // Page was never set up, so return 404
  282. return ErrPostNotFound
  283. }
  284. } else {
  285. c, err = getPrivacyPage(app)
  286. }
  287. if err != nil {
  288. return err
  289. }
  290. p.ContentTitle = c.Title.String
  291. p.Content = template.HTML(applyMarkdown([]byte(c.Content), "", app.cfg))
  292. p.PlainContent = shortPostDescription(stripmd.Strip(c.Content))
  293. if !c.Updated.IsZero() {
  294. p.Updated = c.Updated.Format("January 2, 2006")
  295. }
  296. }
  297. // Serve templated page
  298. err := t.ExecuteTemplate(w, "base", p)
  299. if err != nil {
  300. log.Error("Unable to render page: %v", err)
  301. }
  302. return nil
  303. }
  304. func pageForReq(app *App, r *http.Request) page.StaticPage {
  305. p := page.StaticPage{
  306. AppCfg: app.cfg.App,
  307. Path: r.URL.Path,
  308. Version: "v" + softwareVer,
  309. }
  310. // Use custom style, if file exists
  311. if _, err := os.Stat(filepath.Join(staticDir, "local", "custom.css")); err == nil {
  312. p.CustomCSS = true
  313. }
  314. // Add user information, if given
  315. var u *User
  316. accessToken := r.FormValue("t")
  317. if accessToken != "" {
  318. userID := app.db.GetUserID(accessToken)
  319. if userID != -1 {
  320. var err error
  321. u, err = app.db.GetUserByID(userID)
  322. if err == nil {
  323. p.Username = u.Username
  324. }
  325. }
  326. } else {
  327. u = getUserSession(app, r)
  328. if u != nil {
  329. p.Username = u.Username
  330. p.IsAdmin = u != nil && u.IsAdmin()
  331. p.CanInvite = canUserInvite(app.cfg, p.IsAdmin)
  332. }
  333. }
  334. p.CanViewReader = !app.cfg.App.Private || u != nil
  335. return p
  336. }
  337. var fileRegex = regexp.MustCompile("/([^/]*\\.[^/]*)$")
  338. // Initialize loads the app configuration and initializes templates, keys,
  339. // session, route handlers, and the database connection.
  340. func Initialize(apper Apper, debug bool) (*App, error) {
  341. debugging = debug
  342. apper.LoadConfig()
  343. // Load templates
  344. err := InitTemplates(apper.App().Config())
  345. if err != nil {
  346. return nil, fmt.Errorf("load templates: %s", err)
  347. }
  348. // Load keys and set up session
  349. initKeyPaths(apper.App()) // TODO: find a better way to do this, since it's unneeded in all Apper implementations
  350. err = InitKeys(apper)
  351. if err != nil {
  352. return nil, fmt.Errorf("init keys: %s", err)
  353. }
  354. apper.App().InitUpdates()
  355. apper.App().InitSession()
  356. apper.App().InitDecoder()
  357. err = ConnectToDatabase(apper.App())
  358. if err != nil {
  359. return nil, fmt.Errorf("connect to DB: %s", err)
  360. }
  361. initActivityPub(apper.App())
  362. // Handle local timeline, if enabled
  363. if apper.App().cfg.App.LocalTimeline {
  364. log.Info("Initializing local timeline...")
  365. initLocalTimeline(apper.App())
  366. }
  367. return apper.App(), nil
  368. }
  369. func Serve(app *App, r *mux.Router) {
  370. log.Info("Going to serve...")
  371. isSingleUser = app.cfg.App.SingleUser
  372. app.cfg.Server.Dev = debugging
  373. // Handle shutdown
  374. c := make(chan os.Signal, 2)
  375. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  376. go func() {
  377. <-c
  378. log.Info("Shutting down...")
  379. shutdown(app)
  380. log.Info("Done.")
  381. os.Exit(0)
  382. }()
  383. // Start gopher server
  384. if app.cfg.Server.GopherPort > 0 && !app.cfg.App.Private {
  385. go initGopher(app)
  386. }
  387. // Start web application server
  388. var bindAddress = app.cfg.Server.Bind
  389. if bindAddress == "" {
  390. bindAddress = "localhost"
  391. }
  392. var err error
  393. if app.cfg.IsSecureStandalone() {
  394. if app.cfg.Server.Autocert {
  395. m := &autocert.Manager{
  396. Prompt: autocert.AcceptTOS,
  397. Cache: autocert.DirCache(app.cfg.Server.TLSCertPath),
  398. }
  399. host, err := url.Parse(app.cfg.App.Host)
  400. if err != nil {
  401. log.Error("[WARNING] Unable to parse configured host! %s", err)
  402. log.Error(`[WARNING] ALL hosts are allowed, which can open you to an attack where
  403. clients connect to a server by IP address and pretend to be asking for an
  404. incorrect host name, and cause you to reach the CA's rate limit for certificate
  405. requests. We recommend supplying a valid host name.`)
  406. log.Info("Using autocert on ANY host")
  407. } else {
  408. log.Info("Using autocert on host %s", host.Host)
  409. m.HostPolicy = autocert.HostWhitelist(host.Host)
  410. }
  411. s := &http.Server{
  412. Addr: ":https",
  413. Handler: r,
  414. TLSConfig: &tls.Config{
  415. GetCertificate: m.GetCertificate,
  416. },
  417. }
  418. s.SetKeepAlivesEnabled(false)
  419. go func() {
  420. log.Info("Serving redirects on http://%s:80", bindAddress)
  421. err = http.ListenAndServe(":80", m.HTTPHandler(nil))
  422. log.Error("Unable to start redirect server: %v", err)
  423. }()
  424. log.Info("Serving on https://%s:443", bindAddress)
  425. log.Info("---")
  426. err = s.ListenAndServeTLS("", "")
  427. } else {
  428. go func() {
  429. log.Info("Serving redirects on http://%s:80", bindAddress)
  430. err = http.ListenAndServe(fmt.Sprintf("%s:80", bindAddress), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  431. http.Redirect(w, r, app.cfg.App.Host, http.StatusMovedPermanently)
  432. }))
  433. log.Error("Unable to start redirect server: %v", err)
  434. }()
  435. log.Info("Serving on https://%s:443", bindAddress)
  436. log.Info("Using manual certificates")
  437. log.Info("---")
  438. err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r)
  439. }
  440. } else {
  441. network := "tcp"
  442. protocol := "http"
  443. if strings.HasPrefix(bindAddress, "/") {
  444. network = "unix"
  445. protocol = "http+unix"
  446. // old sockets will remain after server closes;
  447. // we need to delete them in order to open new ones
  448. err = os.Remove(bindAddress)
  449. if err != nil && !os.IsNotExist(err) {
  450. log.Error("%s already exists but could not be removed: %v", bindAddress, err)
  451. os.Exit(1)
  452. }
  453. } else {
  454. bindAddress = fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port)
  455. }
  456. log.Info("Serving on %s://%s", protocol, bindAddress)
  457. log.Info("---")
  458. listener, err := net.Listen(network, bindAddress)
  459. if err != nil {
  460. log.Error("Could not bind to address: %v", err)
  461. os.Exit(1)
  462. }
  463. if network == "unix" {
  464. err = os.Chmod(bindAddress, 0o666)
  465. if err != nil {
  466. log.Error("Could not update socket permissions: %v", err)
  467. os.Exit(1)
  468. }
  469. }
  470. defer listener.Close()
  471. err = http.Serve(listener, r)
  472. }
  473. if err != nil {
  474. log.Error("Unable to start: %v", err)
  475. os.Exit(1)
  476. }
  477. }
  478. func (app *App) InitDecoder() {
  479. // TODO: do this at the package level, instead of the App level
  480. // Initialize modules
  481. app.formDecoder = schema.NewDecoder()
  482. app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
  483. app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
  484. app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
  485. app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
  486. app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
  487. app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
  488. }
  489. // ConnectToDatabase validates and connects to the configured database, then
  490. // tests the connection.
  491. func ConnectToDatabase(app *App) error {
  492. // Check database configuration
  493. if app.cfg.Database.Type == driverMySQL && app.cfg.Database.User == "" {
  494. return fmt.Errorf("Database user not set.")
  495. }
  496. if app.cfg.Database.Host == "" {
  497. app.cfg.Database.Host = "localhost"
  498. }
  499. if app.cfg.Database.Database == "" {
  500. app.cfg.Database.Database = "writefreely"
  501. }
  502. // TODO: check err
  503. connectToDatabase(app)
  504. // Test database connection
  505. err := app.db.Ping()
  506. if err != nil {
  507. return fmt.Errorf("Database ping failed: %s", err)
  508. }
  509. return nil
  510. }
  511. // FormatVersion constructs the version string for the application
  512. func FormatVersion() string {
  513. return serverSoftware + " " + softwareVer
  514. }
  515. // OutputVersion prints out the version of the application.
  516. func OutputVersion() {
  517. fmt.Println(FormatVersion())
  518. }
  519. // NewApp creates a new app instance.
  520. func NewApp(cfgFile string) *App {
  521. return &App{
  522. cfgFile: cfgFile,
  523. }
  524. }
  525. // CreateConfig creates a default configuration and saves it to the app's cfgFile.
  526. func CreateConfig(app *App) error {
  527. log.Info("Creating configuration...")
  528. c := config.New()
  529. log.Info("Saving configuration %s...", app.cfgFile)
  530. err := config.Save(c, app.cfgFile)
  531. if err != nil {
  532. return fmt.Errorf("Unable to save configuration: %v", err)
  533. }
  534. return nil
  535. }
  536. // DoConfig runs the interactive configuration process.
  537. func DoConfig(app *App, configSections string) {
  538. if configSections == "" {
  539. configSections = "server db app"
  540. }
  541. // let's check there aren't any garbage in the list
  542. configSectionsArray := strings.Split(configSections, " ")
  543. for _, element := range configSectionsArray {
  544. if element != "server" && element != "db" && element != "app" {
  545. log.Error("Invalid argument to --sections. Valid arguments are only \"server\", \"db\" and \"app\"")
  546. os.Exit(1)
  547. }
  548. }
  549. d, err := config.Configure(app.cfgFile, configSections)
  550. if err != nil {
  551. log.Error("Unable to configure: %v", err)
  552. os.Exit(1)
  553. }
  554. app.cfg = d.Config
  555. connectToDatabase(app)
  556. defer shutdown(app)
  557. if !app.db.DatabaseInitialized() {
  558. err = adminInitDatabase(app)
  559. if err != nil {
  560. log.Error(err.Error())
  561. os.Exit(1)
  562. }
  563. } else {
  564. log.Info("Database already initialized.")
  565. }
  566. if d.User != nil {
  567. u := &User{
  568. Username: d.User.Username,
  569. HashedPass: d.User.HashedPass,
  570. Created: time.Now().Truncate(time.Second).UTC(),
  571. }
  572. // Create blog
  573. log.Info("Creating user %s...\n", u.Username)
  574. err = app.db.CreateUser(app.cfg, u, app.cfg.App.SiteName, "")
  575. if err != nil {
  576. log.Error("Unable to create user: %s", err)
  577. os.Exit(1)
  578. }
  579. log.Info("Done!")
  580. }
  581. os.Exit(0)
  582. }
  583. // GenerateKeyFiles creates app encryption keys and saves them into the configured KeysParentDir.
  584. func GenerateKeyFiles(app *App) error {
  585. // Read keys path from config
  586. app.LoadConfig()
  587. // Create keys dir if it doesn't exist yet
  588. fullKeysDir := filepath.Join(app.cfg.Server.KeysParentDir, keysDir)
  589. if _, err := os.Stat(fullKeysDir); os.IsNotExist(err) {
  590. err = os.Mkdir(fullKeysDir, 0700)
  591. if err != nil {
  592. return err
  593. }
  594. }
  595. // Generate keys
  596. initKeyPaths(app)
  597. // TODO: use something like https://github.com/hashicorp/go-multierror to return errors
  598. var keyErrs error
  599. err := generateKey(emailKeyPath)
  600. if err != nil {
  601. keyErrs = err
  602. }
  603. err = generateKey(cookieAuthKeyPath)
  604. if err != nil {
  605. keyErrs = err
  606. }
  607. err = generateKey(cookieKeyPath)
  608. if err != nil {
  609. keyErrs = err
  610. }
  611. err = generateKey(csrfKeyPath)
  612. if err != nil {
  613. keyErrs = err
  614. }
  615. return keyErrs
  616. }
  617. // CreateSchema creates all database tables needed for the application.
  618. func CreateSchema(apper Apper) error {
  619. apper.LoadConfig()
  620. connectToDatabase(apper.App())
  621. defer shutdown(apper.App())
  622. err := adminInitDatabase(apper.App())
  623. if err != nil {
  624. return err
  625. }
  626. return nil
  627. }
  628. // Migrate runs all necessary database migrations.
  629. func Migrate(apper Apper) error {
  630. apper.LoadConfig()
  631. connectToDatabase(apper.App())
  632. defer shutdown(apper.App())
  633. err := migrations.Migrate(migrations.NewDatastore(apper.App().db.DB, apper.App().db.driverName))
  634. if err != nil {
  635. return fmt.Errorf("migrate: %s", err)
  636. }
  637. return nil
  638. }
  639. // ResetPassword runs the interactive password reset process.
  640. func ResetPassword(apper Apper, username string) error {
  641. // Connect to the database
  642. apper.LoadConfig()
  643. connectToDatabase(apper.App())
  644. defer shutdown(apper.App())
  645. // Fetch user
  646. u, err := apper.App().db.GetUserForAuth(username)
  647. if err != nil {
  648. log.Error("Get user: %s", err)
  649. os.Exit(1)
  650. }
  651. // Prompt for new password
  652. prompt := promptui.Prompt{
  653. Templates: &promptui.PromptTemplates{
  654. Success: "{{ . | bold | faint }}: ",
  655. },
  656. Label: "New password",
  657. Mask: '*',
  658. }
  659. newPass, err := prompt.Run()
  660. if err != nil {
  661. log.Error("%s", err)
  662. os.Exit(1)
  663. }
  664. // Do the update
  665. log.Info("Updating...")
  666. err = adminResetPassword(apper.App(), u, newPass)
  667. if err != nil {
  668. log.Error("%s", err)
  669. os.Exit(1)
  670. }
  671. log.Info("Success.")
  672. return nil
  673. }
  674. // DoDeleteAccount runs the confirmation and account delete process.
  675. func DoDeleteAccount(apper Apper, username string) error {
  676. // Connect to the database
  677. apper.LoadConfig()
  678. connectToDatabase(apper.App())
  679. defer shutdown(apper.App())
  680. // check user exists
  681. u, err := apper.App().db.GetUserForAuth(username)
  682. if err != nil {
  683. log.Error("%s", err)
  684. os.Exit(1)
  685. }
  686. userID := u.ID
  687. // do not delete the admin account
  688. // TODO: check for other admins and skip?
  689. if u.IsAdmin() {
  690. log.Error("Can not delete admin account")
  691. os.Exit(1)
  692. }
  693. // confirm deletion, w/ w/out posts
  694. prompt := promptui.Prompt{
  695. Templates: &promptui.PromptTemplates{
  696. Success: "{{ . | bold | faint }}: ",
  697. },
  698. Label: fmt.Sprintf("Really delete user : %s", username),
  699. IsConfirm: true,
  700. }
  701. _, err = prompt.Run()
  702. if err != nil {
  703. log.Info("Aborted...")
  704. os.Exit(0)
  705. }
  706. log.Info("Deleting...")
  707. err = apper.App().db.DeleteAccount(userID)
  708. if err != nil {
  709. log.Error("%s", err)
  710. os.Exit(1)
  711. }
  712. log.Info("Success.")
  713. return nil
  714. }
  715. func connectToDatabase(app *App) {
  716. log.Info("Connecting to %s database...", app.cfg.Database.Type)
  717. var db *sql.DB
  718. var err error
  719. if app.cfg.Database.Type == driverMySQL {
  720. db, err = sql.Open(app.cfg.Database.Type, fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=%s&tls=%t", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database, url.QueryEscape(time.Local.String()), app.cfg.Database.TLS))
  721. db.SetMaxOpenConns(50)
  722. } else if app.cfg.Database.Type == driverSQLite {
  723. if !SQLiteEnabled {
  724. log.Error("Invalid database type '%s'. Binary wasn't compiled with SQLite3 support.", app.cfg.Database.Type)
  725. os.Exit(1)
  726. }
  727. if app.cfg.Database.FileName == "" {
  728. log.Error("SQLite database filename value in config.ini is empty.")
  729. os.Exit(1)
  730. }
  731. db, err = sql.Open("sqlite3_with_regex", app.cfg.Database.FileName+"?parseTime=true&cached=shared")
  732. db.SetMaxOpenConns(2)
  733. } else {
  734. log.Error("Invalid database type '%s'. Only 'mysql' and 'sqlite3' are supported right now.", app.cfg.Database.Type)
  735. os.Exit(1)
  736. }
  737. if err != nil {
  738. log.Error("%s", err)
  739. os.Exit(1)
  740. }
  741. app.db = &datastore{db, app.cfg.Database.Type}
  742. }
  743. func shutdown(app *App) {
  744. log.Info("Closing database connection...")
  745. app.db.Close()
  746. if strings.HasPrefix(app.cfg.Server.Bind, "/") {
  747. // Clean up socket
  748. log.Info("Removing socket file...")
  749. err := os.Remove(app.cfg.Server.Bind)
  750. if err != nil {
  751. log.Error("Unable to remove socket: %s", err)
  752. os.Exit(1)
  753. }
  754. log.Info("Success.")
  755. }
  756. }
  757. // CreateUser creates a new admin or normal user from the given credentials.
  758. func CreateUser(apper Apper, username, password string, isAdmin bool) error {
  759. // Create an admin user with --create-admin
  760. apper.LoadConfig()
  761. connectToDatabase(apper.App())
  762. defer shutdown(apper.App())
  763. // Ensure an admin / first user doesn't already exist
  764. firstUser, _ := apper.App().db.GetUserByID(1)
  765. if isAdmin {
  766. // Abort if trying to create admin user, but one already exists
  767. if firstUser != nil {
  768. return fmt.Errorf("Admin user already exists (%s). Create a regular user with: writefreely --create-user", firstUser.Username)
  769. }
  770. } else {
  771. // Abort if trying to create regular user, but no admin exists yet
  772. if firstUser == nil {
  773. return fmt.Errorf("No admin user exists yet. Create an admin first with: writefreely --create-admin")
  774. }
  775. }
  776. // Create the user
  777. // Normalize and validate username
  778. desiredUsername := username
  779. username = getSlug(username, "")
  780. usernameDesc := username
  781. if username != desiredUsername {
  782. usernameDesc += " (originally: " + desiredUsername + ")"
  783. }
  784. if !author.IsValidUsername(apper.App().cfg, username) {
  785. return fmt.Errorf("Username %s is invalid, reserved, or shorter than configured minimum length (%d characters).", usernameDesc, apper.App().cfg.App.MinUsernameLen)
  786. }
  787. // Hash the password
  788. hashedPass, err := auth.HashPass([]byte(password))
  789. if err != nil {
  790. return fmt.Errorf("Unable to hash password: %v", err)
  791. }
  792. u := &User{
  793. Username: username,
  794. HashedPass: hashedPass,
  795. Created: time.Now().Truncate(time.Second).UTC(),
  796. }
  797. userType := "user"
  798. if isAdmin {
  799. userType = "admin"
  800. }
  801. log.Info("Creating %s %s...", userType, usernameDesc)
  802. err = apper.App().db.CreateUser(apper.App().Config(), u, desiredUsername, "")
  803. if err != nil {
  804. return fmt.Errorf("Unable to create user: %s", err)
  805. }
  806. log.Info("Done!")
  807. return nil
  808. }
  809. //go:embed schema.sql
  810. var schemaSql string
  811. //go:embed sqlite.sql
  812. var sqliteSql string
  813. func adminInitDatabase(app *App) error {
  814. var schema string
  815. if app.cfg.Database.Type == driverSQLite {
  816. schema = sqliteSql
  817. } else {
  818. schema = schemaSql
  819. }
  820. tblReg := regexp.MustCompile("CREATE TABLE (IF NOT EXISTS )?`([a-z_]+)`")
  821. queries := strings.Split(string(schema), ";\n")
  822. for _, q := range queries {
  823. if strings.TrimSpace(q) == "" {
  824. continue
  825. }
  826. parts := tblReg.FindStringSubmatch(q)
  827. if len(parts) >= 3 {
  828. log.Info("Creating table %s...", parts[2])
  829. } else {
  830. log.Info("Creating table ??? (Weird query) No match in: %v", parts)
  831. }
  832. _, err := app.db.Exec(q)
  833. if err != nil {
  834. log.Error("%s", err)
  835. } else {
  836. log.Info("Created.")
  837. }
  838. }
  839. // Set up migrations table
  840. log.Info("Initializing appmigrations table...")
  841. err := migrations.SetInitialMigrations(migrations.NewDatastore(app.db.DB, app.db.driverName))
  842. if err != nil {
  843. return fmt.Errorf("Unable to set initial migrations: %v", err)
  844. }
  845. log.Info("Running migrations...")
  846. err = migrations.Migrate(migrations.NewDatastore(app.db.DB, app.db.driverName))
  847. if err != nil {
  848. return fmt.Errorf("migrate: %s", err)
  849. }
  850. log.Info("Done.")
  851. return nil
  852. }
  853. // ServerUserAgent returns a User-Agent string to use in external requests. The
  854. // hostName parameter may be left empty.
  855. func ServerUserAgent(hostName string) string {
  856. hostUAStr := ""
  857. if hostName != "" {
  858. hostUAStr = "; +" + hostName
  859. }
  860. return "Go (" + serverSoftware + "/" + softwareVer + hostUAStr + ")"
  861. }