A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

940 lignes
24 KiB

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