@ -1,5 +1,5 @@
/ *
/ *
* Copyright © 2018 A Bunch Tell LLC .
* Copyright © 2018 - 2020 A Bunch Tell LLC .
*
*
* This file is part of WriteFreely .
* This file is part of WriteFreely .
*
*
@ -22,6 +22,7 @@ import (
"github.com/guregu/null"
"github.com/guregu/null"
"github.com/guregu/null/zero"
"github.com/guregu/null/zero"
uuid "github.com/nu7hatch/gouuid"
uuid "github.com/nu7hatch/gouuid"
"github.com/writeas/activityserve"
"github.com/writeas/impart"
"github.com/writeas/impart"
"github.com/writeas/nerds/store"
"github.com/writeas/nerds/store"
"github.com/writeas/web-core/activitypub"
"github.com/writeas/web-core/activitypub"
@ -37,6 +38,7 @@ import (
const (
const (
mySQLErrDuplicateKey = 1062
mySQLErrDuplicateKey = 1062
mySQLErrCollationMix = 1267
driverMySQL = "mysql"
driverMySQL = "mysql"
driverSQLite = "sqlite3"
driverSQLite = "sqlite3"
@ -63,7 +65,7 @@ type writestore interface {
GetAccessToken ( userID int64 ) ( string , error )
GetAccessToken ( userID int64 ) ( string , error )
GetTemporaryAccessToken ( userID int64 , validSecs int ) ( string , error )
GetTemporaryAccessToken ( userID int64 , validSecs int ) ( string , error )
GetTemporaryOneTimeAccessToken ( userID int64 , validSecs int , oneTime bool ) ( string , error )
GetTemporaryOneTimeAccessToken ( userID int64 , validSecs int , oneTime bool ) ( string , error )
DeleteAccount ( userID int64 ) ( l * string , err error )
DeleteAccount ( userID int64 ) error
ChangeSettings ( app * App , u * User , s * userSettings ) error
ChangeSettings ( app * App , u * User , s * userSettings ) error
ChangePassphrase ( userID int64 , sudo bool , curPass string , hashedPass [ ] byte ) error
ChangePassphrase ( userID int64 , sudo bool , curPass string , hashedPass [ ] byte ) error
@ -319,18 +321,18 @@ func (db *datastore) GetUserByID(id int64) (*User, error) {
return u , nil
return u , nil
}
}
// IsUserSuspend ed returns true if the user account associated with id is
// IsUserSilenc ed returns true if the user account associated with id is
// currently suspend ed.
// currently silenc ed.
func ( db * datastore ) IsUserSuspend ed ( id int64 ) ( bool , error ) {
func ( db * datastore ) IsUserSilenc ed ( id int64 ) ( bool , error ) {
u := & User { ID : id }
u := & User { ID : id }
err := db . QueryRow ( "SELECT status FROM users WHERE id = ?" , id ) . Scan ( & u . Status )
err := db . QueryRow ( "SELECT status FROM users WHERE id = ?" , id ) . Scan ( & u . Status )
switch {
switch {
case err == sql . ErrNoRows :
case err == sql . ErrNoRows :
return false , fmt . Errorf ( "is user suspend ed: %v" , ErrUserNotFound )
return false , fmt . Errorf ( "is user silenc ed: %v" , ErrUserNotFound )
case err != nil :
case err != nil :
log . Error ( "Couldn't SELECT user password : %v" , err )
log . Error ( "Couldn't SELECT user status : %v" , err )
return false , fmt . Errorf ( "is user suspend ed: %v" , err )
return false , fmt . Errorf ( "is user silenc ed: %v" , err )
}
}
return u . IsSilenced ( ) , nil
return u . IsSilenced ( ) , nil
@ -2115,22 +2117,13 @@ func (db *datastore) CollectionHasAttribute(id int64, attr string) bool {
return true
return true
}
}
func ( db * datastore ) DeleteAccount ( userID int64 ) ( l * string , err error ) {
// DeleteAccount will delete the entire account for userID
debug := ""
func ( db * datastore ) DeleteAccount ( userID int64 ) error {
l = & debug
t , err := db . Begin ( )
if err != nil {
stringLogln ( l , "Unable to begin: %v" , err )
return
}
// Get all collections
// Get all collections
rows , err := db . Query ( "SELECT id, alias FROM collections WHERE owner_id = ?" , userID )
rows , err := db . Query ( "SELECT id, alias FROM collections WHERE owner_id = ?" , userID )
if err != nil {
if err != nil {
t . Rollback ( )
log . Error ( "Unable to get collections: %v" , err )
stringLogln ( l , "Unable to get collections: %v" , err )
return err
return
}
}
defer rows . Close ( )
defer rows . Close ( )
colls := [ ] Collection { }
colls := [ ] Collection { }
@ -2138,103 +2131,158 @@ func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
for rows . Next ( ) {
for rows . Next ( ) {
err = rows . Scan ( & c . ID , & c . Alias )
err = rows . Scan ( & c . ID , & c . Alias )
if err != nil {
if err != nil {
t . Rollback ( )
log . Error ( "Unable to scan collection cols: %v" , err )
stringLogln ( l , "Unable to scan collection cols: %v" , err )
return err
return
}
}
colls = append ( colls , c )
colls = append ( colls , c )
}
}
// Start transaction
t , err := db . Begin ( )
if err != nil {
log . Error ( "Unable to begin: %v" , err )
return err
}
// Clean up all collection related information
var res sql . Result
var res sql . Result
for _ , c := range colls {
for _ , c := range colls {
// TODO: user deleteCollection() func
// Delete tokens
// Delete tokens
res , err = t . Exec ( "DELETE FROM collectionattributes WHERE collection_id = ?" , c . ID )
res , err = t . Exec ( "DELETE FROM collectionattributes WHERE collection_id = ?" , c . ID )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete attributes on %s: %v" , c . Alias , err )
log . Error ( "Unable to delete attributes on %s: %v" , c . Alias , err )
return
return err
}
}
rs , _ := res . RowsAffected ( )
rs , _ := res . RowsAffected ( )
stringLogln ( l , "Deleted %d for %s from collectionattributes" , rs , c . Alias )
log . Info ( "Deleted %d for %s from collectionattributes" , 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 {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete passwords on %s: %v" , c . Alias , err )
log . Error ( "Unable to delete passwords on %s: %v" , c . Alias , err )
return
return err
}
}
rs , _ = res . RowsAffected ( )
rs , _ = res . RowsAffected ( )
stringLogln ( l , "Deleted %d for %s from collectionpasswords" , rs , c . Alias )
log . Info ( "Deleted %d for %s from collectionpasswords" , rs , c . Alias )
// Remove redirects to this collection
// Remove redirects to this collection
res , err = t . Exec ( "DELETE FROM collectionredirects WHERE new_alias = ?" , c . Alias )
res , err = t . Exec ( "DELETE FROM collectionredirects WHERE new_alias = ?" , c . Alias )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete redirects on %s: %v" , c . Alias , err )
log . Error ( "Unable to delete redirects on %s: %v" , c . Alias , err )
return
return err
}
}
rs , _ = res . RowsAffected ( )
rs , _ = res . RowsAffected ( )
stringLogln ( l , "Deleted %d for %s from collectionredirects" , rs , c . Alias )
log . Info ( "Deleted %d for %s from collectionredirects" , rs , c . Alias )
// Remove any collection keys
res , err = t . Exec ( "DELETE FROM collectionkeys WHERE collection_id = ?" , c . ID )
if err != nil {
t . Rollback ( )
log . Error ( "Unable to delete keys on %s: %v" , c . Alias , err )
return err
}
rs , _ = res . RowsAffected ( )
log . Info ( "Deleted %d for %s from collectionkeys" , rs , c . Alias )
// TODO: federate delete collection
// Remove remote follows
res , err = t . Exec ( "DELETE FROM remotefollows WHERE collection_id = ?" , c . ID )
if err != nil {
t . Rollback ( )
log . Error ( "Unable to delete remote follows on %s: %v" , c . Alias , err )
return err
}
rs , _ = res . RowsAffected ( )
log . Info ( "Deleted %d for %s from remotefollows" , rs , c . Alias )
}
}
// Delete collections
// Delete collections
res , err = t . Exec ( "DELETE FROM collections WHERE owner_id = ?" , userID )
res , err = t . Exec ( "DELETE FROM collections WHERE owner_id = ?" , userID )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete collections: %v" , err )
log . Error ( "Unable to delete collections: %v" , err )
return
return err
}
}
rs , _ := res . RowsAffected ( )
rs , _ := res . RowsAffected ( )
stringLogln ( l , "Deleted %d from collections" , rs )
log . Info ( "Deleted %d from collections" , rs )
// Delete tokens
// Delete tokens
res , err = t . Exec ( "DELETE FROM accesstokens WHERE user_id = ?" , userID )
res , err = t . Exec ( "DELETE FROM accesstokens WHERE user_id = ?" , userID )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete access tokens: %v" , err )
log . Error ( "Unable to delete access tokens: %v" , err )
return
return err
}
}
rs , _ = res . RowsAffected ( )
rs , _ = res . RowsAffected ( )
stringLogln ( l , "Deleted %d from accesstokens" , rs )
log . Info ( "Deleted %d from accesstokens" , rs )
// Delete user attributes
res , err = t . Exec ( "DELETE FROM oauth_users WHERE user_id = ?" , userID )
if err != nil {
t . Rollback ( )
log . Error ( "Unable to delete oauth_users: %v" , err )
return err
}
rs , _ = res . RowsAffected ( )
log . Info ( "Deleted %d from oauth_users" , rs )
// Delete posts
// Delete posts
// TODO: should maybe get each row so we can federate a delete
// if so needs to be outside of transaction like collections
res , err = t . Exec ( "DELETE FROM posts WHERE owner_id = ?" , userID )
res , err = t . Exec ( "DELETE FROM posts WHERE owner_id = ?" , userID )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete posts: %v" , err )
log . Error ( "Unable to delete posts: %v" , err )
return
return err
}
}
rs , _ = res . RowsAffected ( )
rs , _ = res . RowsAffected ( )
stringLogln ( l , "Deleted %d from posts" , rs )
log . Info ( "Deleted %d from posts" , rs )
// Delete user attributes
res , err = t . Exec ( "DELETE FROM userattributes WHERE user_id = ?" , userID )
res , err = t . Exec ( "DELETE FROM userattributes WHERE user_id = ?" , userID )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete attributes: %v" , err )
log . Error ( "Unable to delete attributes: %v" , err )
return
return err
}
}
rs , _ = res . RowsAffected ( )
rs , _ = res . RowsAffected ( )
stringLogln ( l , "Deleted %d from userattributes" , rs )
log . Info ( "Deleted %d from userattributes" , rs )
// Delete user invites
res , err = t . Exec ( "DELETE FROM userinvites WHERE owner_id = ?" , userID )
if err != nil {
t . Rollback ( )
log . Error ( "Unable to delete invites: %v" , err )
return err
}
rs , _ = res . RowsAffected ( )
log . Info ( "Deleted %d from userinvites" , rs )
// Delete the user
res , err = t . Exec ( "DELETE FROM users WHERE id = ?" , userID )
res , err = t . Exec ( "DELETE FROM users WHERE id = ?" , userID )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to delete user: %v" , err )
log . Error ( "Unable to delete user: %v" , err )
return
return err
}
}
rs , _ = res . RowsAffected ( )
rs , _ = res . RowsAffected ( )
stringLogln ( l , "Deleted %d from users" , rs )
log . Info ( "Deleted %d from users" , rs )
// Commit all changes to the database
err = t . Commit ( )
err = t . Commit ( )
if err != nil {
if err != nil {
t . Rollback ( )
t . Rollback ( )
stringLogln ( l , "Unable to commit: %v" , err )
log . Error ( "Unable to commit: %v" , err )
return
return err
}
}
return
// TODO: federate delete actor
return nil
}
}
func ( db * datastore ) GetAPActorKeys ( collectionID int64 ) ( [ ] byte , [ ] byte ) {
func ( db * datastore ) GetAPActorKeys ( collectionID int64 ) ( [ ] byte , [ ] byte ) {
@ -2283,7 +2331,7 @@ func (db *datastore) GetUserInvite(id string) (*Invite, error) {
var i Invite
var i Invite
err := db . QueryRow ( "SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE id = ?" , id ) . Scan ( & i . ID , & i . MaxUses , & i . Created , & i . Expires , & i . Inactive )
err := db . QueryRow ( "SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE id = ?" , id ) . Scan ( & i . ID , & i . MaxUses , & i . Created , & i . Expires , & i . Inactive )
switch {
switch {
case err == sql . ErrNoRows :
case err == sql . ErrNoRows , db . isIgnorableError ( err ) :
return nil , impart . HTTPError { http . StatusNotFound , "Invite doesn't exist." }
return nil , impart . HTTPError { http . StatusNotFound , "Invite doesn't exist." }
case err != nil :
case err != nil :
log . Error ( "Failed selecting invite: %v" , err )
log . Error ( "Failed selecting invite: %v" , err )
@ -2592,3 +2640,40 @@ func handleFailedPostInsert(err error) error {
log . Error ( "Couldn't insert into posts: %v" , err )
log . Error ( "Couldn't insert into posts: %v" , err )
return err
return err
}
}
func ( db * datastore ) GetProfilePageFromHandle ( app * App , handle string ) ( string , error ) {
actorIRI := ""
remoteUser , err := getRemoteUserFromHandle ( app , handle )
if err != nil {
// can't find using handle in the table but the table may already have this user without
// handle from a previous version
// TODO: Make this determination. We should know whether a user exists without a handle, or doesn't exist at all
actorIRI = RemoteLookup ( handle )
_ , errRemoteUser := getRemoteUser ( app , actorIRI )
// if it exists then we need to update the handle
if errRemoteUser == nil {
_ , err := app . db . Exec ( "UPDATE remoteusers SET handle = ? WHERE actor_id = ?" , handle , actorIRI )
if err != nil {
log . Error ( "Can't update handle (" + handle + ") in database for user " + actorIRI )
}
} else {
// this probably means we don't have the user in the table so let's try to insert it
// here we need to ask the server for the inboxes
remoteActor , err := activityserve . NewRemoteActor ( actorIRI )
if err != nil {
log . Error ( "Couldn't fetch remote actor" , err )
}
if debugging {
log . Info ( "%s %s %s %s" , actorIRI , remoteActor . GetInbox ( ) , remoteActor . GetSharedInbox ( ) , handle )
}
_ , err = app . db . Exec ( "INSERT INTO remoteusers (actor_id, inbox, shared_inbox, handle) VALUES(?, ?, ?, ?)" , actorIRI , remoteActor . GetInbox ( ) , remoteActor . GetSharedInbox ( ) , handle )
if err != nil {
log . Error ( "Can't insert remote user in database" , err )
return "" , err
}
}
} else {
actorIRI = remoteUser . ActorID
}
return actorIRI , nil
}