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.
 
 
 
 
 

219 lines
6.5 KiB

  1. /*
  2. * Copyright © 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. "crypto/sha256"
  13. "encoding/hex"
  14. "fmt"
  15. "github.com/writeas/impart"
  16. "github.com/writeas/web-core/auth"
  17. "github.com/writeas/web-core/log"
  18. "github.com/writeas/writefreely/page"
  19. "html/template"
  20. "net/http"
  21. "strings"
  22. "time"
  23. )
  24. type viewOauthSignupVars struct {
  25. page.StaticPage
  26. To string
  27. Message template.HTML
  28. Flashes []template.HTML
  29. AccessToken string
  30. TokenUsername string
  31. TokenAlias string // TODO: rename this to match the data it represents: the collection title
  32. TokenEmail string
  33. TokenRemoteUser string
  34. Provider string
  35. ClientID string
  36. TokenHash string
  37. LoginUsername string
  38. Alias string // TODO: rename this to match the data it represents: the collection title
  39. Email string
  40. }
  41. const (
  42. oauthParamAccessToken = "access_token"
  43. oauthParamTokenUsername = "token_username"
  44. oauthParamTokenAlias = "token_alias"
  45. oauthParamTokenEmail = "token_email"
  46. oauthParamTokenRemoteUserID = "token_remote_user"
  47. oauthParamClientID = "client_id"
  48. oauthParamProvider = "provider"
  49. oauthParamHash = "signature"
  50. oauthParamUsername = "username"
  51. oauthParamAlias = "alias"
  52. oauthParamEmail = "email"
  53. oauthParamPassword = "password"
  54. )
  55. type oauthSignupPageParams struct {
  56. AccessToken string
  57. TokenUsername string
  58. TokenAlias string // TODO: rename this to match the data it represents: the collection title
  59. TokenEmail string
  60. TokenRemoteUser string
  61. ClientID string
  62. Provider string
  63. TokenHash string
  64. }
  65. func (p oauthSignupPageParams) HashTokenParams(key string) string {
  66. hasher := sha256.New()
  67. hasher.Write([]byte(key))
  68. hasher.Write([]byte(p.AccessToken))
  69. hasher.Write([]byte(p.TokenUsername))
  70. hasher.Write([]byte(p.TokenAlias))
  71. hasher.Write([]byte(p.TokenEmail))
  72. hasher.Write([]byte(p.TokenRemoteUser))
  73. hasher.Write([]byte(p.ClientID))
  74. hasher.Write([]byte(p.Provider))
  75. return hex.EncodeToString(hasher.Sum(nil))
  76. }
  77. func (h oauthHandler) viewOauthSignup(app *App, w http.ResponseWriter, r *http.Request) error {
  78. tp := &oauthSignupPageParams{
  79. AccessToken: r.FormValue(oauthParamAccessToken),
  80. TokenUsername: r.FormValue(oauthParamTokenUsername),
  81. TokenAlias: r.FormValue(oauthParamTokenAlias),
  82. TokenEmail: r.FormValue(oauthParamTokenEmail),
  83. TokenRemoteUser: r.FormValue(oauthParamTokenRemoteUserID),
  84. ClientID: r.FormValue(oauthParamClientID),
  85. Provider: r.FormValue(oauthParamProvider),
  86. }
  87. if tp.HashTokenParams(h.Config.Server.HashSeed) != r.FormValue(oauthParamHash) {
  88. return impart.HTTPError{Status: http.StatusBadRequest, Message: "Request has been tampered with."}
  89. }
  90. tp.TokenHash = tp.HashTokenParams(h.Config.Server.HashSeed)
  91. if err := h.validateOauthSignup(r); err != nil {
  92. return h.showOauthSignupPage(app, w, r, tp, err)
  93. }
  94. var err error
  95. hashedPass := []byte{}
  96. clearPass := r.FormValue(oauthParamPassword)
  97. hasPass := clearPass != ""
  98. if hasPass {
  99. hashedPass, err = auth.HashPass([]byte(clearPass))
  100. if err != nil {
  101. return h.showOauthSignupPage(app, w, r, tp, fmt.Errorf("unable to hash password"))
  102. }
  103. }
  104. newUser := &User{
  105. Username: r.FormValue(oauthParamUsername),
  106. HashedPass: hashedPass,
  107. HasPass: hasPass,
  108. Email: prepareUserEmail(r.FormValue(oauthParamEmail), h.EmailKey),
  109. Created: time.Now().Truncate(time.Second).UTC(),
  110. }
  111. displayName := r.FormValue(oauthParamAlias)
  112. if len(displayName) == 0 {
  113. displayName = r.FormValue(oauthParamUsername)
  114. }
  115. err = h.DB.CreateUser(h.Config, newUser, displayName)
  116. if err != nil {
  117. return h.showOauthSignupPage(app, w, r, tp, err)
  118. }
  119. err = h.DB.RecordRemoteUserID(r.Context(), newUser.ID, r.FormValue(oauthParamTokenRemoteUserID), r.FormValue(oauthParamProvider), r.FormValue(oauthParamClientID), r.FormValue(oauthParamAccessToken))
  120. if err != nil {
  121. return h.showOauthSignupPage(app, w, r, tp, err)
  122. }
  123. if err := loginOrFail(h.Store, w, r, newUser); err != nil {
  124. return h.showOauthSignupPage(app, w, r, tp, err)
  125. }
  126. return nil
  127. }
  128. func (h oauthHandler) validateOauthSignup(r *http.Request) error {
  129. username := r.FormValue(oauthParamUsername)
  130. if len(username) < h.Config.App.MinUsernameLen {
  131. return impart.HTTPError{Status: http.StatusBadRequest, Message: "Username is too short."}
  132. }
  133. if len(username) > 100 {
  134. return impart.HTTPError{Status: http.StatusBadRequest, Message: "Username is too long."}
  135. }
  136. collTitle := r.FormValue(oauthParamAlias)
  137. if len(collTitle) == 0 {
  138. collTitle = username
  139. }
  140. email := r.FormValue(oauthParamEmail)
  141. if len(email) > 0 {
  142. parts := strings.Split(email, "@")
  143. if len(parts) != 2 || (len(parts[0]) < 1 || len(parts[1]) < 1) {
  144. return impart.HTTPError{Status: http.StatusBadRequest, Message: "Invalid email address"}
  145. }
  146. }
  147. return nil
  148. }
  149. func (h oauthHandler) showOauthSignupPage(app *App, w http.ResponseWriter, r *http.Request, tp *oauthSignupPageParams, errMsg error) error {
  150. username := tp.TokenUsername
  151. collTitle := tp.TokenAlias
  152. email := tp.TokenEmail
  153. session, err := app.sessionStore.Get(r, cookieName)
  154. if err != nil {
  155. // Ignore this
  156. log.Error("Unable to get session; ignoring: %v", err)
  157. }
  158. if tmpValue := r.FormValue(oauthParamUsername); len(tmpValue) > 0 {
  159. username = tmpValue
  160. }
  161. if tmpValue := r.FormValue(oauthParamAlias); len(tmpValue) > 0 {
  162. collTitle = tmpValue
  163. }
  164. if tmpValue := r.FormValue(oauthParamEmail); len(tmpValue) > 0 {
  165. email = tmpValue
  166. }
  167. p := &viewOauthSignupVars{
  168. StaticPage: pageForReq(app, r),
  169. To: r.FormValue("to"),
  170. Flashes: []template.HTML{},
  171. AccessToken: tp.AccessToken,
  172. TokenUsername: tp.TokenUsername,
  173. TokenAlias: tp.TokenAlias,
  174. TokenEmail: tp.TokenEmail,
  175. TokenRemoteUser: tp.TokenRemoteUser,
  176. Provider: tp.Provider,
  177. ClientID: tp.ClientID,
  178. TokenHash: tp.TokenHash,
  179. LoginUsername: username,
  180. Alias: collTitle,
  181. Email: email,
  182. }
  183. // Display any error messages
  184. flashes, _ := getSessionFlashes(app, w, r, session)
  185. for _, flash := range flashes {
  186. p.Flashes = append(p.Flashes, template.HTML(flash))
  187. }
  188. if errMsg != nil {
  189. p.Flashes = append(p.Flashes, template.HTML(errMsg.Error()))
  190. }
  191. err = pages["signup-oauth.tmpl"].ExecuteTemplate(w, "base", p)
  192. if err != nil {
  193. log.Error("Unable to render signup-oauth: %v", err)
  194. return err
  195. }
  196. return nil
  197. }