2021-08-20 11:26:56 +01:00
|
|
|
/*
|
|
|
|
GoToSocial
|
|
|
|
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
package bundb
|
2021-08-20 11:26:56 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
2021-08-25 14:34:33 +01:00
|
|
|
"database/sql"
|
2021-08-20 11:26:56 +01:00
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/mail"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
)
|
|
|
|
|
|
|
|
type adminDB struct {
|
|
|
|
config *config.Config
|
2021-08-29 15:41:41 +01:00
|
|
|
conn *DBConn
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) {
|
|
|
|
q := a.conn.
|
|
|
|
NewSelect().
|
|
|
|
Model(>smodel.Account{}).
|
|
|
|
Where("username = ?", username).
|
|
|
|
Where("domain = ?", nil)
|
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
return a.conn.NotExists(ctx, q)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
func (a *adminDB) IsEmailAvailable(ctx context.Context, email string) (bool, db.Error) {
|
2021-08-20 11:26:56 +01:00
|
|
|
// parse the domain from the email
|
|
|
|
m, err := mail.ParseAddress(email)
|
|
|
|
if err != nil {
|
2021-08-25 14:34:33 +01:00
|
|
|
return false, fmt.Errorf("error parsing email address %s: %s", email, err)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @
|
|
|
|
|
|
|
|
// check if the email domain is blocked
|
2021-08-25 14:34:33 +01:00
|
|
|
if err := a.conn.
|
|
|
|
NewSelect().
|
|
|
|
Model(>smodel.EmailDomainBlock{}).
|
|
|
|
Where("domain = ?", domain).
|
|
|
|
Scan(ctx); err == nil {
|
2021-08-20 11:26:56 +01:00
|
|
|
// fail because we found something
|
2021-08-25 14:34:33 +01:00
|
|
|
return false, fmt.Errorf("email domain %s is blocked", domain)
|
|
|
|
} else if err != sql.ErrNoRows {
|
2021-08-29 15:41:41 +01:00
|
|
|
return false, a.conn.ProcessError(err)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// check if this email is associated with a user already
|
2021-08-25 14:34:33 +01:00
|
|
|
q := a.conn.
|
|
|
|
NewSelect().
|
|
|
|
Model(>smodel.User{}).
|
|
|
|
Where("email = ?", email).
|
|
|
|
WhereOr("unconfirmed_email = ?", email)
|
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
return a.conn.NotExists(ctx, q)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, db.Error) {
|
2021-08-20 11:26:56 +01:00
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
if err != nil {
|
2021-08-29 15:41:41 +01:00
|
|
|
a.conn.log.Errorf("error creating new rsa key: %s", err)
|
2021-08-20 11:26:56 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// if something went wrong while creating a user, we might already have an account, so check here first...
|
|
|
|
acct := >smodel.Account{}
|
2021-08-25 14:34:33 +01:00
|
|
|
err = a.conn.NewSelect().
|
|
|
|
Model(acct).
|
|
|
|
Where("username = ?", username).
|
2021-08-26 10:28:16 +01:00
|
|
|
WhereGroup(" AND ", whereEmptyOrNull("domain")).
|
2021-08-25 14:34:33 +01:00
|
|
|
Scan(ctx)
|
2021-08-20 11:26:56 +01:00
|
|
|
if err != nil {
|
|
|
|
// we just don't have an account yet create one
|
|
|
|
newAccountURIs := util.GenerateURIsForAccount(username, a.config.Protocol, a.config.Host)
|
|
|
|
newAccountID, err := id.NewRandomULID()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
acct = >smodel.Account{
|
|
|
|
ID: newAccountID,
|
|
|
|
Username: username,
|
|
|
|
DisplayName: username,
|
|
|
|
Reason: reason,
|
|
|
|
URL: newAccountURIs.UserURL,
|
|
|
|
PrivateKey: key,
|
|
|
|
PublicKey: &key.PublicKey,
|
|
|
|
PublicKeyURI: newAccountURIs.PublicKeyURI,
|
|
|
|
ActorType: gtsmodel.ActivityStreamsPerson,
|
|
|
|
URI: newAccountURIs.UserURI,
|
|
|
|
InboxURI: newAccountURIs.InboxURI,
|
|
|
|
OutboxURI: newAccountURIs.OutboxURI,
|
|
|
|
FollowersURI: newAccountURIs.FollowersURI,
|
|
|
|
FollowingURI: newAccountURIs.FollowingURI,
|
|
|
|
FeaturedCollectionURI: newAccountURIs.CollectionURI,
|
|
|
|
}
|
2021-08-25 14:34:33 +01:00
|
|
|
if _, err = a.conn.
|
|
|
|
NewInsert().
|
|
|
|
Model(acct).
|
|
|
|
Exec(ctx); err != nil {
|
2021-08-29 15:41:41 +01:00
|
|
|
return nil, a.conn.ProcessError(err)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error hashing password: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
newUserID, err := id.NewRandomULID()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
u := >smodel.User{
|
|
|
|
ID: newUserID,
|
|
|
|
AccountID: acct.ID,
|
|
|
|
EncryptedPassword: string(pw),
|
|
|
|
SignUpIP: signUpIP.To4(),
|
|
|
|
Locale: locale,
|
|
|
|
UnconfirmedEmail: email,
|
|
|
|
CreatedByApplicationID: appID,
|
|
|
|
Approved: !requireApproval, // if we don't require moderator approval, just pre-approve the user
|
|
|
|
}
|
|
|
|
|
|
|
|
if emailVerified {
|
|
|
|
u.ConfirmedAt = time.Now()
|
|
|
|
u.Email = email
|
|
|
|
}
|
|
|
|
|
|
|
|
if admin {
|
|
|
|
u.Admin = true
|
|
|
|
u.Moderator = true
|
|
|
|
}
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
if _, err = a.conn.
|
|
|
|
NewInsert().
|
|
|
|
Model(u).
|
|
|
|
Exec(ctx); err != nil {
|
2021-08-29 15:41:41 +01:00
|
|
|
return nil, a.conn.ProcessError(err)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {
|
2021-08-20 11:26:56 +01:00
|
|
|
username := a.config.Host
|
2021-08-25 14:34:33 +01:00
|
|
|
|
|
|
|
// check if instance account already exists
|
|
|
|
existsQ := a.conn.
|
|
|
|
NewSelect().
|
|
|
|
Model(>smodel.Account{}).
|
|
|
|
Where("username = ?", username).
|
2021-08-26 10:28:16 +01:00
|
|
|
WhereGroup(" AND ", whereEmptyOrNull("domain"))
|
2021-08-25 14:34:33 +01:00
|
|
|
count, err := existsQ.Count(ctx)
|
|
|
|
if err != nil && count == 1 {
|
2021-08-29 15:41:41 +01:00
|
|
|
a.conn.log.Infof("instance account %s already exists", username)
|
2021-08-25 14:34:33 +01:00
|
|
|
return nil
|
|
|
|
} else if err != sql.ErrNoRows {
|
2021-08-29 15:41:41 +01:00
|
|
|
return a.conn.ProcessError(err)
|
2021-08-25 14:34:33 +01:00
|
|
|
}
|
|
|
|
|
2021-08-20 11:26:56 +01:00
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
if err != nil {
|
2021-08-29 15:41:41 +01:00
|
|
|
a.conn.log.Errorf("error creating new rsa key: %s", err)
|
2021-08-20 11:26:56 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
aID, err := id.NewRandomULID()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
newAccountURIs := util.GenerateURIsForAccount(username, a.config.Protocol, a.config.Host)
|
|
|
|
acct := >smodel.Account{
|
|
|
|
ID: aID,
|
|
|
|
Username: a.config.Host,
|
|
|
|
DisplayName: username,
|
|
|
|
URL: newAccountURIs.UserURL,
|
|
|
|
PrivateKey: key,
|
|
|
|
PublicKey: &key.PublicKey,
|
|
|
|
PublicKeyURI: newAccountURIs.PublicKeyURI,
|
|
|
|
ActorType: gtsmodel.ActivityStreamsPerson,
|
|
|
|
URI: newAccountURIs.UserURI,
|
|
|
|
InboxURI: newAccountURIs.InboxURI,
|
|
|
|
OutboxURI: newAccountURIs.OutboxURI,
|
|
|
|
FollowersURI: newAccountURIs.FollowersURI,
|
|
|
|
FollowingURI: newAccountURIs.FollowingURI,
|
|
|
|
FeaturedCollectionURI: newAccountURIs.CollectionURI,
|
|
|
|
}
|
2021-08-25 14:34:33 +01:00
|
|
|
|
|
|
|
insertQ := a.conn.
|
|
|
|
NewInsert().
|
|
|
|
Model(acct)
|
|
|
|
|
|
|
|
if _, err := insertQ.Exec(ctx); err != nil {
|
2021-08-29 15:41:41 +01:00
|
|
|
return a.conn.ProcessError(err)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
2021-08-25 14:34:33 +01:00
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
a.conn.log.Infof("instance account %s CREATED with id %s", username, acct.ID)
|
2021-08-20 11:26:56 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-25 14:34:33 +01:00
|
|
|
func (a *adminDB) CreateInstanceInstance(ctx context.Context) db.Error {
|
|
|
|
domain := a.config.Host
|
|
|
|
|
|
|
|
// check if instance entry already exists
|
2021-08-26 16:22:41 +01:00
|
|
|
q := a.conn.
|
2021-08-25 14:34:33 +01:00
|
|
|
NewSelect().
|
|
|
|
Model(>smodel.Instance{}).
|
|
|
|
Where("domain = ?", domain)
|
|
|
|
|
2021-08-29 15:41:41 +01:00
|
|
|
exists, err := a.conn.Exists(ctx, q)
|
2021-08-26 16:22:41 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if exists {
|
2021-08-29 15:41:41 +01:00
|
|
|
a.conn.log.Infof("instance entry already exists")
|
2021-08-26 16:46:24 +01:00
|
|
|
return nil
|
2021-08-25 14:34:33 +01:00
|
|
|
}
|
|
|
|
|
2021-08-20 11:26:56 +01:00
|
|
|
iID, err := id.NewRandomULID()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
i := >smodel.Instance{
|
|
|
|
ID: iID,
|
2021-08-25 14:34:33 +01:00
|
|
|
Domain: domain,
|
|
|
|
Title: domain,
|
2021-08-20 11:26:56 +01:00
|
|
|
URI: fmt.Sprintf("%s://%s", a.config.Protocol, a.config.Host),
|
|
|
|
}
|
2021-08-25 14:34:33 +01:00
|
|
|
|
|
|
|
insertQ := a.conn.
|
|
|
|
NewInsert().
|
|
|
|
Model(i)
|
|
|
|
|
2021-08-26 16:22:41 +01:00
|
|
|
_, err = insertQ.Exec(ctx)
|
2021-08-29 15:41:41 +01:00
|
|
|
if err != nil {
|
|
|
|
return a.conn.ProcessError(err)
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|
2021-08-29 15:41:41 +01:00
|
|
|
|
|
|
|
a.conn.log.Infof("created instance instance %s with id %s", domain, i.ID)
|
|
|
|
return nil
|
2021-08-20 11:26:56 +01:00
|
|
|
}
|