Fill in remaining missing pieces

- Database schema changes, removing obsolete custom domain-related code
- Missing user structs
- Setup verbiage changes
- Missing routes
- Missing error messages
pull/24/head
Matt Baer 6 years ago
parent 6dbf0c8764
commit 55ada67170
  1. 20
      app.go
  2. 18
      auth.go
  3. 1
      config/config.go
  4. 5
      config/setup.go
  5. 126
      database.go
  6. 14
      errors.go
  7. 18
      routes.go
  8. 43
      users.go

@ -12,7 +12,9 @@ import (
"syscall" "syscall"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/writeas/web-core/converter"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config" "github.com/writeas/writefreely/config"
"github.com/writeas/writefreely/page" "github.com/writeas/writefreely/page"
@ -20,6 +22,8 @@ import (
const ( const (
staticDir = "static/" staticDir = "static/"
assumedTitleLen = 80
postsPerPage = 10
serverSoftware = "Write Freely" serverSoftware = "Write Freely"
softwareURL = "https://writefreely.org" softwareURL = "https://writefreely.org"
@ -28,6 +32,12 @@ const (
var ( var (
debugging bool debugging bool
// DEPRECATED VARS
// TODO: pass app.cfg into GetCollection* calls so we can get these values
// from Collection methods and we no longer need these.
hostName string
isSingleUser bool
) )
type app struct { type app struct {
@ -36,6 +46,7 @@ type app struct {
cfg *config.Config cfg *config.Config
keys *keychain keys *keychain
sessionStore *sessions.CookieStore sessionStore *sessions.CookieStore
formDecoder *schema.Decoder
} }
// handleViewHome shows page at root path. Will be the Pad if logged in and the // handleViewHome shows page at root path. Will be the Pad if logged in and the
@ -128,6 +139,8 @@ func Serve() {
cfg: cfg, cfg: cfg,
} }
hostName = cfg.App.Host
isSingleUser = cfg.App.SingleUser
app.cfg.Server.Dev = *debugPtr app.cfg.Server.Dev = *debugPtr
initTemplates() initTemplates()
@ -141,6 +154,13 @@ func Serve() {
// Initialize modules // Initialize modules
app.sessionStore = initSession(app) app.sessionStore = initSession(app)
app.formDecoder = schema.NewDecoder()
app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
// Check database configuration // Check database configuration
if app.cfg.Database.User == "" || app.cfg.Database.Password == "" { if app.cfg.Database.User == "" || app.cfg.Database.Password == "" {

@ -0,0 +1,18 @@
package writefreely
// AuthenticateUser ensures a user with the given accessToken is valid. Call
// it before any operations that require authentication or optionally associate
// data with a user account.
// Returns an error if the given accessToken is invalid. Otherwise the
// associated user ID is returned.
func AuthenticateUser(db writestore, accessToken string) (int64, error) {
if accessToken == "" {
return 0, ErrNoAccessToken
}
userID := db.GetUserID(accessToken)
if userID == -1 {
return 0, ErrBadAccessToken
}
return userID, nil
}

@ -10,6 +10,7 @@ const (
type ( type (
ServerCfg struct { ServerCfg struct {
HiddenHost string `ini:"hidden_host"`
Port int `ini:"port"` Port int `ini:"port"`
Dev bool `ini:"-"` Dev bool `ini:"-"`

@ -10,11 +10,14 @@ import (
func Configure() error { func Configure() error {
c, err := Load() c, err := Load()
var action string
if err != nil { if err != nil {
fmt.Println("No configuration yet. Creating new.") fmt.Println("No configuration yet. Creating new.")
c = New() c = New()
action = "generate"
} else { } else {
fmt.Println("Configuration loaded.") fmt.Println("Configuration loaded.")
action = "update"
} }
title := color.New(color.Bold, color.BgGreen).PrintlnFunc() title := color.New(color.Bold, color.BgGreen).PrintlnFunc()
@ -22,7 +25,7 @@ func Configure() error {
fmt.Println() fmt.Println()
intro(" ✍ Write Freely Configuration ✍") intro(" ✍ Write Freely Configuration ✍")
fmt.Println() fmt.Println()
fmt.Println(wordwrap.WrapString(" This quick configuration process will generate the application's config file, "+FileName+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75)) fmt.Println(wordwrap.WrapString(" This quick configuration process will "+action+" the application's config file, "+FileName+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75))
fmt.Println() fmt.Println()
title(" Server setup ") title(" Server setup ")

@ -65,11 +65,10 @@ type writestore interface {
CreateCollectionFromToken(string, string, string) (*Collection, error) CreateCollectionFromToken(string, string, string) (*Collection, error)
CreateCollection(string, string, int64) (*Collection, error) CreateCollection(string, string, int64) (*Collection, error)
GetFuzzyDomain(host string) string
GetCollectionBy(condition string, value interface{}) (*Collection, error) GetCollectionBy(condition string, value interface{}) (*Collection, error)
GetCollection(alias string) (*Collection, error) GetCollection(alias string) (*Collection, error)
GetCollectionForPad(alias string) (*Collection, error) GetCollectionForPad(alias string) (*Collection, error)
GetCollectionFromDomain(host string) (*Collection, error) GetCollectionByID(id int64) (*Collection, error)
UpdateCollection(c *SubmittedCollection, alias string) error UpdateCollection(c *SubmittedCollection, alias string) error
DeleteCollection(alias string, userID int64) error DeleteCollection(alias string, userID int64) error
@ -256,7 +255,7 @@ func (db *datastore) DoesUserNeedAuth(id int64) bool {
var pass, email []byte var pass, email []byte
// Find out if user has an email set first // Find out if user has an email set first
err := db.QueryRow("SELECT pass, email FROM users WHERE id = ?", id).Scan(&pass, &email) err := db.QueryRow("SELECT password, email FROM users WHERE id = ?", id).Scan(&pass, &email)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
// ERROR. Don't give false positives on needing auth methods // ERROR. Don't give false positives on needing auth methods
@ -272,7 +271,7 @@ func (db *datastore) DoesUserNeedAuth(id int64) bool {
func (db *datastore) IsUserPassSet(id int64) (bool, error) { func (db *datastore) IsUserPassSet(id int64) (bool, error) {
var pass []byte var pass []byte
err := db.QueryRow("SELECT pass FROM users WHERE id = ?", id).Scan(&pass) err := db.QueryRow("SELECT password FROM users WHERE id = ?", id).Scan(&pass)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return false, nil return false, nil
@ -672,10 +671,10 @@ func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Coll
c := &Collection{} c := &Collection{}
// FIXME: change Collection to reflect database values. Add helper functions to get actual values // FIXME: change Collection to reflect database values. Add helper functions to get actual values
var styleSheet, script, format, customHandle zero.String var styleSheet, script, format zero.String
row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, format, owner_id, privacy, handle, view_count FROM collections WHERE "+condition, value) row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, format, owner_id, privacy, view_count FROM collections WHERE "+condition, value)
err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &styleSheet, &script, &format, &c.OwnerID, &c.Visibility, &customHandle, &c.Views) err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &styleSheet, &script, &format, &c.OwnerID, &c.Visibility, &c.Views)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."} return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
@ -683,12 +682,12 @@ func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Coll
log.Error("Failed selecting from collections: %v", err) log.Error("Failed selecting from collections: %v", err)
return nil, err return nil, err
} }
c.CustomHandle = customHandle.String
c.StyleSheet = styleSheet.String c.StyleSheet = styleSheet.String
c.Script = script.String c.Script = script.String
c.Format = format.String c.Format = format.String
c.Public = c.IsPublic() c.Public = c.IsPublic()
// TODO: set app to c
c.db = db
return c, nil return c, nil
} }
@ -715,6 +714,10 @@ func (db *datastore) GetCollectionForPad(alias string) (*Collection, error) {
return c, nil return c, nil
} }
func (db *datastore) GetCollectionByID(id int64) (*Collection, error) {
return db.GetCollectionBy("id = ?", id)
}
func (db *datastore) GetCollectionFromDomain(host string) (*Collection, error) { func (db *datastore) GetCollectionFromDomain(host string) (*Collection, error) {
return db.GetCollectionBy("host = ?", host) return db.GetCollectionBy("host = ?", host)
} }
@ -723,7 +726,6 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
q := query.NewUpdate(). q := query.NewUpdate().
SetStringPtr(c.Title, "title"). SetStringPtr(c.Title, "title").
SetStringPtr(c.Description, "description"). SetStringPtr(c.Description, "description").
SetBoolPtr(c.PreferSubdomain, "prefer_subdomain").
SetNullString(c.StyleSheet, "style_sheet"). SetNullString(c.StyleSheet, "style_sheet").
SetNullString(c.Script, "script") SetNullString(c.Script, "script")
@ -751,15 +753,10 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
// Find any current domain // Find any current domain
var collID int64 var collID int64
var currentDomain sql.NullString
var rowsAffected int64 var rowsAffected int64
var changed bool var changed bool
var res sql.Result var res sql.Result
err := db.QueryRow("SELECT id, host FROM collections LEFT JOIN domains ON id = collection_id WHERE alias = ?", alias).Scan(&collID, &currentDomain) var err error
if err != nil {
log.Error("Failed selecting from domains: %v", err)
return impart.HTTPError{http.StatusInternalServerError, "Couldn't update custom domain."}
}
// Update MathJax value // Update MathJax value
if c.MathJax { if c.MathJax {
@ -776,42 +773,6 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
} }
} }
if currentDomain.String != c.Domain.String {
if c.Domain.String == "" {
_, err := db.Exec("DELETE FROM domains WHERE collection_id = ?", collID)
if err != nil {
log.Error("Unable to delete domain %s from domains: %s", currentDomain.String, err)
}
} else if !currentDomain.Valid {
c.Domain.String = strings.ToLower(c.Domain.String)
// There is no current domain; add it
res, err = db.Exec("INSERT INTO domains (host, collection_id, handle) VALUES (?, ?, ?)", c.Domain, collID, c.FediverseHandle())
if err != nil {
log.Error("Unable to insert domain: %v", err)
return err
}
changed = true
} else {
c.Domain.String = strings.ToLower(c.Domain.String)
// Update the current domain
res, err = db.Exec("UPDATE domains SET host = ?, handle = ?, last_checked = NULL WHERE collection_id = ?", c.Domain, c.FediverseHandle(), collID)
if err != nil {
log.Error("Unable to update domain: %v", err)
} else {
rowsAffected, _ = res.RowsAffected()
if rowsAffected > 0 {
changed = true
}
}
}
} else if c.Handle != "" {
_, err = db.Exec("UPDATE domains SET handle = ? WHERE collection_id = ?", c.FediverseHandle(), collID)
if err != nil {
log.Error("Unable to update domain handle (only): %v", err)
return err
}
}
// Update rest of the collection data // Update rest of the collection data
res, err = db.Exec("UPDATE collections SET "+q.Updates+" WHERE "+q.Conditions, q.Params...) res, err = db.Exec("UPDATE collections SET "+q.Updates+" WHERE "+q.Conditions, q.Params...)
if err != nil { if err != nil {
@ -850,34 +811,6 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
return nil return nil
} }
// GetFuzzyDomain takes an attempted host and finds any potential authoritative
// domains where the user should be redirected
func (db *datastore) GetFuzzyDomain(host string) string {
if strings.HasPrefix(host, "www.") {
host = host[strings.Index(host, ".")+1:]
} else {
return ""
}
var curHost string
var active, secure bool
err := db.QueryRow("SELECT host, is_active, is_secure FROM domains WHERE host = ?", host).Scan(&curHost, &active, &secure)
if err != nil {
if err != sql.ErrNoRows {
log.Error("Failed fuzzy domain check for %s: %v", host, err)
}
return ""
}
if !active {
return ""
}
if secure {
curHost = "https://" + curHost
} else {
curHost = "http://" + curHost
}
return curHost
}
const postCols = "id, slug, text_appearance, language, rtl, privacy, owner_id, collection_id, pinned_position, created, updated, view_count, title, content" const postCols = "id, slug, text_appearance, language, rtl, privacy, owner_id, collection_id, pinned_position, created, updated, view_count, title, content"
// getEditablePost returns a PublicPost with the given ID only if the given // getEditablePost returns a PublicPost with the given ID only if the given
@ -1407,7 +1340,7 @@ func (db *datastore) ClaimPosts(userID int64, collAlias string, posts *[]ClaimPo
qRes, err = db.AttemptClaim(&p, query, params, slugIdx) qRes, err = db.AttemptClaim(&p, query, params, slugIdx)
if err != nil { if err != nil {
r.Code = http.StatusInternalServerError r.Code = http.StatusInternalServerError
r.ErrorMessage = "A Write.as error occurred. The humans have been alerted." r.ErrorMessage = "An unknown error occurred."
r.ID = p.ID r.ID = p.ID
res = append(res, r) res = append(res, r)
log.Error("claimPosts (post %s): %v", p.ID, err) log.Error("claimPosts (post %s): %v", p.ID, err)
@ -1523,8 +1456,6 @@ func (db *datastore) GetCollections(u *User) (*[]Collection, error) {
defer rows.Close() defer rows.Close()
colls := []Collection{} colls := []Collection{}
var domain zero.String
var isActive, isSecure null.Bool
for rows.Next() { for rows.Next() {
c := Collection{} c := Collection{}
err = rows.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility, &c.Views) err = rows.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility, &c.Views)
@ -1532,9 +1463,6 @@ func (db *datastore) GetCollections(u *User) (*[]Collection, error) {
log.Error("Failed scanning row: %v", err) log.Error("Failed scanning row: %v", err)
break break
} }
c.Domain = domain.String
c.IsDomainActive = isActive.Bool
c.IsSecure = isSecure.Bool
c.URL = c.CanonicalURL() c.URL = c.CanonicalURL()
c.Public = c.IsPublic() c.Public = c.IsPublic()
@ -2051,16 +1979,6 @@ func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
rs, _ := res.RowsAffected() rs, _ := res.RowsAffected()
stringLogln(l, "Deleted %d for %s from collectionattributes", rs, c.Alias) stringLogln(l, "Deleted %d for %s from collectionattributes", rs, c.Alias)
// Delete collection email address
res, err = t.Exec("DELETE FROM collectionemails WHERE collection_id = ?", c.ID)
if err != nil {
t.Rollback()
stringLogln(l, "Unable to delete emails on %s: %v", c.Alias, err)
return
}
rs, _ = res.RowsAffected()
stringLogln(l, "Deleted %d for %s from collectionemails", rs, c.Alias)
// Remove any optional collection password // Remove any optional collection password
res, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID) res, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
if err != nil { if err != nil {
@ -2080,16 +1998,6 @@ func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
} }
rs, _ = res.RowsAffected() rs, _ = res.RowsAffected()
stringLogln(l, "Deleted %d for %s from collectionredirects", rs, c.Alias) stringLogln(l, "Deleted %d for %s from collectionredirects", rs, c.Alias)
// Remove any associated custom domains
res, err = t.Exec("DELETE FROM domains WHERE collection_id = ?", c.ID)
if err != nil {
t.Rollback()
stringLogln(l, "Unable to delete domains on %s: %v", c.Alias, err)
return
}
rs, _ = res.RowsAffected()
stringLogln(l, "Deleted %d for %s from domains", rs, c.Alias)
} }
// Delete collections // Delete collections
@ -2152,18 +2060,18 @@ func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) { func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) {
var pub, priv []byte var pub, priv []byte
err := db.QueryRow("SELECT public_key, private_key FROM activitypubkeys WHERE collection_id = ?", collectionID).Scan(&pub, &priv) err := db.QueryRow("SELECT public_key, private_key FROM collectionkeys WHERE collection_id = ?", collectionID).Scan(&pub, &priv)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
// Generate keys // Generate keys
pub, priv = activitypub.GenerateKeys() pub, priv = activitypub.GenerateKeys()
_, err = db.Exec("INSERT INTO activitypubkeys (collection_id, public_key, private_key) VALUES (?, ?, ?)", collectionID, pub, priv) _, err = db.Exec("INSERT INTO collectionkeys (collection_id, public_key, private_key) VALUES (?, ?, ?)", collectionID, pub, priv)
if err != nil { if err != nil {
log.Error("Unable to INSERT new activitypub keypair: %v", err) log.Error("Unable to INSERT new activitypub keypair: %v", err)
return nil, nil return nil, nil
} }
case err != nil: case err != nil:
log.Error("Couldn't SELECT activitypubkeys: %v", err) log.Error("Couldn't SELECT collectionkeys: %v", err)
return nil, nil return nil, nil
} }

@ -7,21 +7,35 @@ import (
// Commonly returned HTTP errors // Commonly returned HTTP errors
var ( var (
ErrBadFormData = impart.HTTPError{http.StatusBadRequest, "Expected valid form data."}
ErrBadJSON = impart.HTTPError{http.StatusBadRequest, "Expected valid JSON object."}
ErrBadJSONArray = impart.HTTPError{http.StatusBadRequest, "Expected valid JSON array."}
ErrBadAccessToken = impart.HTTPError{http.StatusUnauthorized, "Invalid access token."} ErrBadAccessToken = impart.HTTPError{http.StatusUnauthorized, "Invalid access token."}
ErrNoAccessToken = impart.HTTPError{http.StatusBadRequest, "Authorization token required."} ErrNoAccessToken = impart.HTTPError{http.StatusBadRequest, "Authorization token required."}
ErrNotLoggedIn = impart.HTTPError{http.StatusUnauthorized, "Not logged in."}
ErrForbiddenCollection = impart.HTTPError{http.StatusForbidden, "You don't have permission to add to this collection."} ErrForbiddenCollection = impart.HTTPError{http.StatusForbidden, "You don't have permission to add to this collection."}
ErrForbiddenEditPost = impart.HTTPError{http.StatusForbidden, "You don't have permission to update this post."}
ErrUnauthorizedEditPost = impart.HTTPError{http.StatusUnauthorized, "Invalid editing credentials."} ErrUnauthorizedEditPost = impart.HTTPError{http.StatusUnauthorized, "Invalid editing credentials."}
ErrUnauthorizedGeneral = impart.HTTPError{http.StatusUnauthorized, "You don't have permission to do that."} ErrUnauthorizedGeneral = impart.HTTPError{http.StatusUnauthorized, "You don't have permission to do that."}
ErrBadRequestedType = impart.HTTPError{http.StatusNotAcceptable, "Bad requested Content-Type."}
ErrCollectionUnauthorizedRead = impart.HTTPError{http.StatusUnauthorized, "You don't have permission to access this collection."}
ErrNoPublishableContent = impart.HTTPError{http.StatusBadRequest, "Supply something to publish."}
ErrInternalGeneral = impart.HTTPError{http.StatusInternalServerError, "The humans messed something up. They've been notified."} ErrInternalGeneral = impart.HTTPError{http.StatusInternalServerError, "The humans messed something up. They've been notified."}
ErrInternalCookieSession = impart.HTTPError{http.StatusInternalServerError, "Could not get cookie session."}
ErrCollectionNotFound = impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
ErrCollectionGone = impart.HTTPError{http.StatusGone, "This blog was unpublished."}
ErrCollectionPageNotFound = impart.HTTPError{http.StatusNotFound, "Collection page doesn't exist."} ErrCollectionPageNotFound = impart.HTTPError{http.StatusNotFound, "Collection page doesn't exist."}
ErrPostNotFound = impart.HTTPError{Status: http.StatusNotFound, Message: "Post not found."} ErrPostNotFound = impart.HTTPError{Status: http.StatusNotFound, Message: "Post not found."}
ErrPostBanned = impart.HTTPError{Status: http.StatusGone, Message: "Post removed."}
ErrPostUnpublished = impart.HTTPError{Status: http.StatusGone, Message: "Post unpublished by author."} ErrPostUnpublished = impart.HTTPError{Status: http.StatusGone, Message: "Post unpublished by author."}
ErrPostFetchError = impart.HTTPError{Status: http.StatusInternalServerError, Message: "We encountered an error getting the post. The humans have been alerted."} ErrPostFetchError = impart.HTTPError{Status: http.StatusInternalServerError, Message: "We encountered an error getting the post. The humans have been alerted."}
ErrUserNotFound = impart.HTTPError{http.StatusNotFound, "User doesn't exist."} ErrUserNotFound = impart.HTTPError{http.StatusNotFound, "User doesn't exist."}
ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."}
) )
// Post operation errors // Post operation errors

@ -44,6 +44,16 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover))) write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo))) write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
// Set up dyamic page handlers
// Handle auth
auth := write.PathPrefix("/api/auth/").Subrouter()
if cfg.App.OpenRegistration {
auth.HandleFunc("/signup", handler.All(apiSignup)).Methods("POST")
}
auth.HandleFunc("/login", handler.All(login)).Methods("POST")
auth.HandleFunc("/read", handler.WebErrors(handleWebCollectionUnlock, UserLevelNone)).Methods("POST")
auth.HandleFunc("/me", handler.All(handleAPILogout)).Methods("DELETE")
// Handle logged in user sections // Handle logged in user sections
me := write.PathPrefix("/me").Subrouter() me := write.PathPrefix("/me").Subrouter()
me.HandleFunc("/", handler.Redirect("/me", UserLevelUser)) me.HandleFunc("/", handler.Redirect("/me", UserLevelUser))
@ -100,6 +110,14 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
posts.HandleFunc("/claim", handler.All(addPost)).Methods("POST") posts.HandleFunc("/claim", handler.All(addPost)).Methods("POST")
posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST") posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST")
if cfg.App.OpenRegistration {
write.HandleFunc("/auth/signup", handler.Web(handleWebSignup, UserLevelNoneRequired)).Methods("POST")
}
write.HandleFunc("/auth/login", handler.Web(webLogin, UserLevelNoneRequired)).Methods("POST")
// Handle special pages first
write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired))
if cfg.App.SingleUser { if cfg.App.SingleUser {
write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET") write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
} else { } else {

@ -9,6 +9,35 @@ import (
) )
type ( type (
userCredentials struct {
Alias string `json:"alias" schema:"alias"`
Pass string `json:"pass" schema:"pass"`
Email string `json:"email" schema:"email"`
Web bool `json:"web" schema:"-"`
To string `json:"-" schema:"to"`
EmailLogin bool `json:"via_email" schema:"via_email"`
}
userRegistration struct {
userCredentials
Honeypot string `json:"fullname" schema:"fullname"`
Normalize bool `json:"normalize" schema:"normalize"`
Signup bool `json:"signup" schema:"signup"`
}
// AuthUser contains information for a newly authenticated user (either
// from signing up or logging in).
AuthUser struct {
AccessToken string `json:"access_token,omitempty"`
Password string `json:"password,omitempty"`
User *User `json:"user"`
// Verbose user data
Posts *[]PublicPost `json:"posts,omitempty"`
Collections *[]Collection `json:"collections,omitempty"`
}
// User is a consistent user object in the database and all contexts (auth // User is a consistent user object in the database and all contexts (auth
// and non-auth) in the API. // and non-auth) in the API.
User struct { User struct {
@ -21,6 +50,20 @@ type (
clearEmail string `json:"email"` clearEmail string `json:"email"`
} }
userMeStats struct {
TotalCollections, TotalArticles, CollectionPosts uint64
}
ExportUser struct {
*User
Collections *[]CollectionObj `json:"collections"`
AnonymousPosts []PublicPost `json:"posts"`
}
PublicUser struct {
Username string `json:"username"`
}
) )
// EmailClear decrypts and returns the user's email, caching it in the user // EmailClear decrypts and returns the user's email, caching it in the user

Loading…
Cancel
Save