diff options
author | 2021-08-25 15:34:33 +0200 | |
---|---|---|
committer | 2021-08-25 15:34:33 +0200 | |
commit | 2dc9fc1626507bb54417fc4a1920b847cafb27a2 (patch) | |
tree | 4ddeac479b923db38090aac8bd9209f3646851c1 /internal/db/bundb/admin.go | |
parent | Manually approves followers (#146) (diff) | |
download | gotosocial-2dc9fc1626507bb54417fc4a1920b847cafb27a2.tar.xz |
Pg to bun (#148)
* start moving to bun
* changing more stuff
* more
* and yet more
* tests passing
* seems stable now
* more big changes
* small fix
* little fixes
Diffstat (limited to 'internal/db/bundb/admin.go')
-rw-r--r-- | internal/db/bundb/admin.go | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go new file mode 100644 index 000000000..67a1e8a0d --- /dev/null +++ b/internal/db/bundb/admin.go @@ -0,0 +1,272 @@ +/* + 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/>. +*/ + +package bundb + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "database/sql" + "fmt" + "net" + "net/mail" + "strings" + "time" + + "github.com/sirupsen/logrus" + "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" + "github.com/uptrace/bun" + "golang.org/x/crypto/bcrypt" +) + +type adminDB struct { + config *config.Config + conn *bun.DB + log *logrus.Logger +} + +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) + + return notExists(ctx, q) +} + +func (a *adminDB) IsEmailAvailable(ctx context.Context, email string) (bool, db.Error) { + // parse the domain from the email + m, err := mail.ParseAddress(email) + if err != nil { + return false, fmt.Errorf("error parsing email address %s: %s", email, err) + } + domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @ + + // check if the email domain is blocked + if err := a.conn. + NewSelect(). + Model(>smodel.EmailDomainBlock{}). + Where("domain = ?", domain). + Scan(ctx); err == nil { + // fail because we found something + return false, fmt.Errorf("email domain %s is blocked", domain) + } else if err != sql.ErrNoRows { + return false, processErrorResponse(err) + } + + // check if this email is associated with a user already + q := a.conn. + NewSelect(). + Model(>smodel.User{}). + Where("email = ?", email). + WhereOr("unconfirmed_email = ?", email) + + return notExists(ctx, q) +} + +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) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + a.log.Errorf("error creating new rsa key: %s", err) + return nil, err + } + + // if something went wrong while creating a user, we might already have an account, so check here first... + acct := >smodel.Account{} + err = a.conn.NewSelect(). + Model(acct). + Where("username = ?", username). + Where("? IS NULL", bun.Ident("domain")). + Scan(ctx) + 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, + } + if _, err = a.conn. + NewInsert(). + Model(acct). + Exec(ctx); err != nil { + return nil, err + } + } + + 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 + } + + if _, err = a.conn. + NewInsert(). + Model(u). + Exec(ctx); err != nil { + return nil, err + } + + return u, nil +} + +func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error { + username := a.config.Host + + // check if instance account already exists + existsQ := a.conn. + NewSelect(). + Model(>smodel.Account{}). + Where("username = ?", username). + Where("? IS NULL", bun.Ident("domain")) + count, err := existsQ.Count(ctx) + if err != nil && count == 1 { + a.log.Infof("instance account %s already exists", username) + return nil + } else if err != sql.ErrNoRows { + return processErrorResponse(err) + } + + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + a.log.Errorf("error creating new rsa key: %s", err) + 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, + } + + insertQ := a.conn. + NewInsert(). + Model(acct) + + if _, err := insertQ.Exec(ctx); err != nil { + return err + } + + a.log.Infof("instance account %s CREATED with id %s", username, acct.ID) + return nil +} + +func (a *adminDB) CreateInstanceInstance(ctx context.Context) db.Error { + domain := a.config.Host + + // check if instance entry already exists + existsQ := a.conn. + NewSelect(). + Model(>smodel.Instance{}). + Where("domain = ?", domain) + + count, err := existsQ.Count(ctx) + if err != nil && count == 1 { + a.log.Infof("instance instance %s already exists", domain) + return nil + } else if err != sql.ErrNoRows { + return processErrorResponse(err) + } + + iID, err := id.NewRandomULID() + if err != nil { + return err + } + + i := >smodel.Instance{ + ID: iID, + Domain: domain, + Title: domain, + URI: fmt.Sprintf("%s://%s", a.config.Protocol, a.config.Host), + } + + insertQ := a.conn. + NewInsert(). + Model(i) + + if _, err := insertQ.Exec(ctx); err != nil { + return err + } + a.log.Infof("created instance instance %s with id %s", domain, i.ID) + return nil +} |