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
This commit is contained in:
parent
6dbf0c8764
commit
55ada67170
22
app.go
22
app.go
|
@ -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 ")
|
||||||
|
|
126
database.go
126
database.go
|
@ -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, ¤tDomain)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
errors.go
24
errors.go
|
@ -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."}
|
||||||
ErrUnauthorizedEditPost = impart.HTTPError{http.StatusUnauthorized, "Invalid editing credentials."}
|
ErrForbiddenEditPost = impart.HTTPError{http.StatusForbidden, "You don't have permission to update this post."}
|
||||||
ErrUnauthorizedGeneral = impart.HTTPError{http.StatusUnauthorized, "You don't have permission to do that."}
|
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
|
||||||
|
|
18
routes.go
18
routes.go
|
@ -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 {
|
||||||
|
|
43
users.go
43
users.go
|
@ -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…
Reference in New Issue