- Database schema changes, removing obsolete custom domain-related code - Missing user structs - Setup verbiage changes - Missing routes - Missing error messagestags/v0.1.0
@@ -12,14 +12,18 @@ 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" | ||||
) | ) | ||||
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,7 +10,8 @@ const ( | |||||
type ( | type ( | ||||
ServerCfg struct { | ServerCfg struct { | ||||
Port int `ini:"port"` | |||||
HiddenHost string `ini:"hidden_host"` | |||||
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 | |||||
row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, format, owner_id, privacy, handle, view_count FROM collections WHERE "+condition, value) | |||||
var styleSheet, script, format zero.String | |||||
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, ¤tDomain) | |||||
if err != nil { | |||||
log.Error("Failed selecting from domains: %v", err) | |||||
return impart.HTTPError{http.StatusInternalServerError, "Couldn't update custom domain."} | |||||
} | |||||
var err error | |||||
// 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."} | |||||
ErrUnauthorizedEditPost = impart.HTTPError{http.StatusUnauthorized, "Invalid editing credentials."} | |||||
ErrUnauthorizedGeneral = impart.HTTPError{http.StatusUnauthorized, "You don't have permission to do that."} | |||||
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."} | |||||
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."} | |||||
ErrInternalGeneral = impart.HTTPError{http.StatusInternalServerError, "The humans messed something up. They've been notified."} | |||||
ErrNoPublishableContent = impart.HTTPError{http.StatusBadRequest, "Supply something to publish."} | |||||
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 | ||||