A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

248 lines
13 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. "net/http"
  13. "net/url"
  14. "path/filepath"
  15. "strings"
  16. "github.com/gorilla/csrf"
  17. "github.com/gorilla/mux"
  18. "github.com/writeas/go-webfinger"
  19. "github.com/writeas/web-core/log"
  20. "github.com/writefreely/go-nodeinfo"
  21. )
  22. // InitStaticRoutes adds routes for serving static files.
  23. // TODO: this should just be a func, not method
  24. func (app *App) InitStaticRoutes(r *mux.Router) {
  25. // Handle static files
  26. fs := http.FileServer(http.Dir(filepath.Join(app.cfg.Server.StaticParentDir, staticDir)))
  27. fs = cacheControl(fs)
  28. app.shttp = http.NewServeMux()
  29. app.shttp.Handle("/", fs)
  30. r.PathPrefix("/").Handler(fs)
  31. }
  32. // InitRoutes adds dynamic routes for the given mux.Router.
  33. func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
  34. // Create handler
  35. handler := NewWFHandler(apper)
  36. // Set up routes
  37. hostSubroute := apper.App().cfg.App.Host[strings.Index(apper.App().cfg.App.Host, "://")+3:]
  38. if apper.App().cfg.App.SingleUser {
  39. hostSubroute = "{domain}"
  40. } else {
  41. if strings.HasPrefix(hostSubroute, "localhost") {
  42. hostSubroute = "localhost"
  43. }
  44. }
  45. if apper.App().cfg.App.SingleUser {
  46. log.Info("Adding %s routes (single user)...", hostSubroute)
  47. } else {
  48. log.Info("Adding %s routes (multi-user)...", hostSubroute)
  49. }
  50. // Primary app routes
  51. write := r.PathPrefix("/").Subrouter()
  52. // Federation endpoint configurations
  53. wf := webfinger.Default(wfResolver{apper.App().db, apper.App().cfg})
  54. wf.NoTLSHandler = nil
  55. // Federation endpoints
  56. // host-meta
  57. write.HandleFunc("/.well-known/host-meta", handler.Web(handleViewHostMeta, UserLevelReader))
  58. // webfinger
  59. write.HandleFunc(webfinger.WebFingerPath, handler.LogHandlerFunc(http.HandlerFunc(wf.Webfinger)))
  60. // nodeinfo
  61. niCfg := nodeInfoConfig(apper.App().db, apper.App().cfg)
  62. ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{apper.App().cfg, apper.App().db})
  63. write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
  64. write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
  65. // handle mentions
  66. write.HandleFunc("/@/{handle}", handler.Web(handleViewMention, UserLevelReader))
  67. configureSlackOauth(handler, write, apper.App())
  68. configureWriteAsOauth(handler, write, apper.App())
  69. configureGitlabOauth(handler, write, apper.App())
  70. configureGenericOauth(handler, write, apper.App())
  71. configureGiteaOauth(handler, write, apper.App())
  72. // Set up dynamic page handlers
  73. // Handle auth
  74. auth := write.PathPrefix("/api/auth/").Subrouter()
  75. if apper.App().cfg.App.OpenRegistration {
  76. auth.HandleFunc("/signup", handler.All(apiSignup)).Methods("POST")
  77. }
  78. auth.HandleFunc("/login", handler.All(login)).Methods("POST")
  79. auth.HandleFunc("/read", handler.WebErrors(handleWebCollectionUnlock, UserLevelNone)).Methods("POST")
  80. auth.HandleFunc("/me", handler.All(handleAPILogout)).Methods("DELETE")
  81. // Handle logged in user sections
  82. me := write.PathPrefix("/me").Subrouter()
  83. me.HandleFunc("/", handler.Redirect("/me", UserLevelUser))
  84. me.HandleFunc("/c", handler.Redirect("/me/c/", UserLevelUser)).Methods("GET")
  85. me.HandleFunc("/c/", handler.User(viewCollections)).Methods("GET")
  86. me.HandleFunc("/c/{collection}", handler.User(viewEditCollection)).Methods("GET")
  87. me.HandleFunc("/c/{collection}/stats", handler.User(viewStats)).Methods("GET")
  88. me.HandleFunc("/c/{collection}/subscribers", handler.User(handleViewSubscribers)).Methods("GET")
  89. me.Path("/delete").Handler(csrf.Protect(apper.App().keys.CSRFKey)(handler.User(handleUserDelete))).Methods("POST")
  90. me.HandleFunc("/posts", handler.Redirect("/me/posts/", UserLevelUser)).Methods("GET")
  91. me.HandleFunc("/posts/", handler.User(viewArticles)).Methods("GET")
  92. me.HandleFunc("/posts/export.csv", handler.Download(viewExportPosts, UserLevelUser)).Methods("GET")
  93. me.HandleFunc("/posts/export.zip", handler.Download(viewExportPosts, UserLevelUser)).Methods("GET")
  94. me.HandleFunc("/posts/export.json", handler.Download(viewExportPosts, UserLevelUser)).Methods("GET")
  95. me.HandleFunc("/export", handler.User(viewExportOptions)).Methods("GET")
  96. me.HandleFunc("/export.json", handler.Download(viewExportFull, UserLevelUser)).Methods("GET")
  97. me.HandleFunc("/import", handler.User(viewImport)).Methods("GET")
  98. me.Path("/settings").Handler(csrf.Protect(apper.App().keys.CSRFKey)(handler.User(viewSettings))).Methods("GET")
  99. me.HandleFunc("/invites", handler.User(handleViewUserInvites)).Methods("GET")
  100. me.HandleFunc("/logout", handler.Web(viewLogout, UserLevelNone)).Methods("GET")
  101. write.HandleFunc("/api/me", handler.All(viewMeAPI)).Methods("GET")
  102. apiMe := write.PathPrefix("/api/me/").Subrouter()
  103. apiMe.HandleFunc("/", handler.All(viewMeAPI)).Methods("GET")
  104. apiMe.HandleFunc("/posts", handler.UserWebAPI(viewMyPostsAPI)).Methods("GET")
  105. apiMe.HandleFunc("/collections", handler.UserAPI(viewMyCollectionsAPI)).Methods("GET")
  106. apiMe.HandleFunc("/password", handler.All(updatePassphrase)).Methods("POST")
  107. apiMe.HandleFunc("/self", handler.All(updateSettings)).Methods("POST")
  108. apiMe.HandleFunc("/invites", handler.User(handleCreateUserInvite)).Methods("POST")
  109. apiMe.HandleFunc("/import", handler.User(handleImport)).Methods("POST")
  110. apiMe.HandleFunc("/oauth/remove", handler.User(removeOauth)).Methods("POST")
  111. // Sign up validation
  112. write.HandleFunc("/api/alias", handler.All(handleUsernameCheck)).Methods("POST")
  113. write.HandleFunc("/api/markdown", handler.All(handleRenderMarkdown)).Methods("POST")
  114. instanceURL, _ := url.Parse(apper.App().Config().App.Host)
  115. host := instanceURL.Host
  116. // Handle collections
  117. write.HandleFunc("/api/collections", handler.All(newCollection)).Methods("POST")
  118. apiColls := write.PathPrefix("/api/collections/").Subrouter()
  119. apiColls.HandleFunc("/monetization-pointer", handler.PlainTextAPI(handleSPSPEndpoint)).Methods("GET")
  120. apiColls.HandleFunc("/"+host, handler.AllReader(fetchCollection)).Methods("GET")
  121. apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.AllReader(fetchCollection)).Methods("GET")
  122. apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(existingCollection)).Methods("POST", "DELETE")
  123. apiColls.HandleFunc("/{alias}/posts", handler.AllReader(fetchCollectionPosts)).Methods("GET")
  124. apiColls.HandleFunc("/{alias}/posts", handler.All(newPost)).Methods("POST")
  125. apiColls.HandleFunc("/{alias}/posts/{post}", handler.AllReader(fetchPost)).Methods("GET")
  126. apiColls.HandleFunc("/{alias}/posts/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST")
  127. apiColls.HandleFunc("/{alias}/posts/{post}/splitcontent", handler.AllReader(handleGetSplitContent)).Methods("GET", "POST")
  128. apiColls.HandleFunc("/{alias}/posts/{post}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET")
  129. apiColls.HandleFunc("/{alias}/collect", handler.All(addPost)).Methods("POST")
  130. apiColls.HandleFunc("/{alias}/pin", handler.All(pinPost)).Methods("POST")
  131. apiColls.HandleFunc("/{alias}/unpin", handler.All(pinPost)).Methods("POST")
  132. apiColls.HandleFunc("/{alias}/email/subscribe", handler.All(handleCreateEmailSubscription)).Methods("POST")
  133. apiColls.HandleFunc("/{alias}/email/subscribe", handler.All(handleDeleteEmailSubscription)).Methods("DELETE")
  134. apiColls.HandleFunc("/{collection}/email/unsubscribe", handler.All(handleDeleteEmailSubscription)).Methods("GET")
  135. apiColls.HandleFunc("/{alias}/inbox", handler.All(handleFetchCollectionInbox)).Methods("POST")
  136. apiColls.HandleFunc("/{alias}/outbox", handler.AllReader(handleFetchCollectionOutbox)).Methods("GET")
  137. apiColls.HandleFunc("/{alias}/following", handler.AllReader(handleFetchCollectionFollowing)).Methods("GET")
  138. apiColls.HandleFunc("/{alias}/followers", handler.AllReader(handleFetchCollectionFollowers)).Methods("GET")
  139. // Handle posts
  140. write.HandleFunc("/api/posts", handler.All(newPost)).Methods("POST")
  141. posts := write.PathPrefix("/api/posts/").Subrouter()
  142. posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.AllReader(fetchPost)).Methods("GET")
  143. posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST", "PUT")
  144. posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(deletePost)).Methods("DELETE")
  145. posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET")
  146. posts.HandleFunc("/claim", handler.All(addPost)).Methods("POST")
  147. posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST")
  148. write.HandleFunc("/auth/signup", handler.Web(handleWebSignup, UserLevelNoneRequired)).Methods("POST")
  149. write.HandleFunc("/auth/login", handler.Web(webLogin, UserLevelNoneRequired)).Methods("POST")
  150. write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET")
  151. write.HandleFunc("/admin/monitor", handler.Admin(handleViewAdminMonitor)).Methods("GET")
  152. write.HandleFunc("/admin/settings", handler.Admin(handleViewAdminSettings)).Methods("GET")
  153. write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET")
  154. write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET")
  155. write.HandleFunc("/admin/user/{username}/delete", handler.Admin(handleAdminDeleteUser)).Methods("POST")
  156. write.HandleFunc("/admin/user/{username}/status", handler.Admin(handleAdminToggleUserStatus)).Methods("POST")
  157. write.HandleFunc("/admin/user/{username}/passphrase", handler.Admin(handleAdminResetUserPass)).Methods("POST")
  158. write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
  159. write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET")
  160. write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST")
  161. write.HandleFunc("/admin/update/{page}", handler.Admin(handleAdminUpdateSite)).Methods("POST")
  162. write.HandleFunc("/admin/updates", handler.Admin(handleViewAdminUpdates)).Methods("GET")
  163. // Handle special pages first
  164. write.Path("/reset").Handler(csrf.Protect(apper.App().keys.CSRFKey)(handler.Web(viewResetPassword, UserLevelNoneRequired)))
  165. write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired))
  166. write.HandleFunc("/signup", handler.Web(handleViewLanding, UserLevelNoneRequired))
  167. write.HandleFunc("/invite/{code:[a-zA-Z0-9]+}", handler.Web(handleViewInvite, UserLevelOptional)).Methods("GET")
  168. // TODO: show a reader-specific 404 page if the function is disabled
  169. write.HandleFunc("/read", handler.Web(viewLocalTimeline, UserLevelReader))
  170. RouteRead(handler, UserLevelReader, write.PathPrefix("/read").Subrouter())
  171. draftEditPrefix := ""
  172. if apper.App().cfg.App.SingleUser {
  173. draftEditPrefix = "/d"
  174. write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelUser)).Methods("GET")
  175. } else {
  176. write.HandleFunc("/new", handler.Web(handleViewPad, UserLevelUser)).Methods("GET")
  177. }
  178. // All the existing stuff
  179. write.HandleFunc(draftEditPrefix+"/{action}/edit", handler.Web(handleViewPad, UserLevelUser)).Methods("GET")
  180. write.HandleFunc(draftEditPrefix+"/{action}/meta", handler.Web(handleViewMeta, UserLevelUser)).Methods("GET")
  181. // Collections
  182. if apper.App().cfg.App.SingleUser {
  183. RouteCollections(handler, write.PathPrefix("/").Subrouter())
  184. } else {
  185. write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelReader))
  186. write.HandleFunc("/{collection}/", handler.Web(handleViewCollection, UserLevelReader))
  187. RouteCollections(handler, write.PathPrefix("/{prefix:[@~$!\\-+]?}{collection}").Subrouter())
  188. // Posts
  189. }
  190. write.HandleFunc(draftEditPrefix+"/{post}", handler.Web(handleViewPost, UserLevelOptional))
  191. write.HandleFunc("/", handler.Web(handleViewHome, UserLevelOptional))
  192. return r
  193. }
  194. func RouteCollections(handler *Handler, r *mux.Router) {
  195. r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional))
  196. r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
  197. r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional))
  198. r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional))
  199. r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))
  200. r.HandleFunc("/tag:{tag}/page/{page:[0-9]+}", handler.Web(handleViewCollectionTag, UserLevelReader))
  201. r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader))
  202. r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap))
  203. r.HandleFunc("/feed/", handler.AllReader(ViewFeed))
  204. r.HandleFunc("/email/confirm/{subscriber}", handler.All(handleConfirmEmailSubscription)).Methods("GET")
  205. r.HandleFunc("/email/unsubscribe/{subscriber}", handler.All(handleDeleteEmailSubscription)).Methods("GET")
  206. r.HandleFunc("/{slug}", handler.CollectionPostOrStatic)
  207. r.HandleFunc("/{slug}/edit", handler.Web(handleViewPad, UserLevelUser))
  208. r.HandleFunc("/{slug}/edit/meta", handler.Web(handleViewMeta, UserLevelUser))
  209. r.HandleFunc("/{slug}/", handler.Web(handleCollectionPostRedirect, UserLevelReader)).Methods("GET")
  210. }
  211. func RouteRead(handler *Handler, readPerm UserLevelFunc, r *mux.Router) {
  212. r.HandleFunc("/api/posts", handler.Web(viewLocalTimelineAPI, readPerm))
  213. r.HandleFunc("/p/{page}", handler.Web(viewLocalTimeline, readPerm))
  214. r.HandleFunc("/feed/", handler.Web(viewLocalTimelineFeed, readPerm))
  215. r.HandleFunc("/t/{tag}", handler.Web(viewLocalTimeline, readPerm))
  216. r.HandleFunc("/a/{post}", handler.Web(handlePostIDRedirect, readPerm))
  217. r.HandleFunc("/{author}", handler.Web(viewLocalTimeline, readPerm))
  218. r.HandleFunc("/", handler.Web(viewLocalTimeline, readPerm))
  219. }