gotosocial/internal/processing/user/emailconfirm.go
kim 43ac0cdb9c
[chore] Global server configuration overhaul (#575)
* move config flag names and usage to config package, rewrite config package to use global Configuration{} struct

Signed-off-by: kim <grufwub@gmail.com>

* improved code comment

Signed-off-by: kim <grufwub@gmail.com>

* linter

Signed-off-by: kim <grufwub@gmail.com>

* fix unmarshaling

Signed-off-by: kim <grufwub@gmail.com>

* remove kim's custom go compiler changes

Signed-off-by: kim <grufwub@gmail.com>

* generate setter and flag-name functions, implement these in codebase

Signed-off-by: kim <grufwub@gmail.com>

* update deps

Signed-off-by: kim <grufwub@gmail.com>

* small change

Signed-off-by: kim <grufwub@gmail.com>

* appease the linter...

Signed-off-by: kim <grufwub@gmail.com>

* move configuration into ConfigState structure, ensure reloading to/from viper settings to keep in sync

Signed-off-by: kim <grufwub@gmail.com>

* lint

Signed-off-by: kim <grufwub@gmail.com>

* update code comments

Signed-off-by: kim <grufwub@gmail.com>

* fix merge issue

Signed-off-by: kim <grufwub@gmail.com>

* fix merge issue

Signed-off-by: kim <grufwub@gmail.com>

* improved version string (removes time + go version)

Signed-off-by: kim <grufwub@gmail.com>

* fix version string build to pass test script + consolidate logic in func

Signed-off-by: kim <grufwub@gmail.com>

* add license text, update config.Defaults comment

Signed-off-by: kim <grufwub@gmail.com>

* add license text to generated config helpers file

Signed-off-by: kim <grufwub@gmail.com>

* defer unlock on config.Set___(), to ensure unlocked on panic

Signed-off-by: kim <grufwub@gmail.com>

* make it more obvious which cmd flags are being attached

Signed-off-by: kim <grufwub@gmail.com>
2022-05-30 14:41:24 +02:00

132 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
GoToSocial
Copyright (C) 2021-2022 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 user
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/email"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/uris"
)
var oneWeek = 168 * time.Hour
func (p *processor) SendConfirmEmail(ctx context.Context, user *gtsmodel.User, username string) error {
if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email {
// user has already confirmed this email address, so there's nothing to do
return nil
}
// We need a token and a link for the user to click on.
// We'll use a uuid as our token since it's basically impossible to guess.
// From the uuid package we use (which uses crypto/rand under the hood):
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
confirmationToken := uuid.NewString()
confirmationLink := uris.GenerateURIForEmailConfirm(confirmationToken)
// pull our instance entry from the database so we can greet the user nicely in the email
instance := &gtsmodel.Instance{}
host := config.GetHost()
if err := p.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: host}}, instance); err != nil {
return fmt.Errorf("SendConfirmEmail: error getting instance: %s", err)
}
// assemble the email contents and send the email
confirmData := email.ConfirmData{
Username: username,
InstanceURL: instance.URI,
InstanceName: instance.Title,
ConfirmLink: confirmationLink,
}
if err := p.emailSender.SendConfirmEmail(user.UnconfirmedEmail, confirmData); err != nil {
return fmt.Errorf("SendConfirmEmail: error sending to email address %s belonging to user %s: %s", user.UnconfirmedEmail, username, err)
}
// email sent, now we need to update the user entry with the token we just sent them
user.ConfirmationSentAt = time.Now()
user.ConfirmationToken = confirmationToken
user.LastEmailedAt = time.Now()
user.UpdatedAt = time.Now()
if err := p.db.UpdateByPrimaryKey(ctx, user); err != nil {
return fmt.Errorf("SendConfirmEmail: error updating user entry after email sent: %s", err)
}
return nil
}
func (p *processor) ConfirmEmail(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) {
if token == "" {
return nil, gtserror.NewErrorNotFound(errors.New("no token provided"))
}
user := &gtsmodel.User{}
if err := p.db.GetWhere(ctx, []db.Where{{Key: "confirmation_token", Value: token}}, user); err != nil {
if err == db.ErrNoEntries {
return nil, gtserror.NewErrorNotFound(err)
}
return nil, gtserror.NewErrorInternalError(err)
}
if user.Account == nil {
a, err := p.db.GetAccountByID(ctx, user.AccountID)
if err != nil {
return nil, gtserror.NewErrorNotFound(err)
}
user.Account = a
}
if !user.Account.SuspendedAt.IsZero() {
return nil, gtserror.NewErrorForbidden(fmt.Errorf("ConfirmEmail: account %s is suspended", user.AccountID))
}
if user.UnconfirmedEmail == "" || user.UnconfirmedEmail == user.Email {
// no pending email confirmations so just return OK
return user, nil
}
if user.ConfirmationSentAt.Before(time.Now().Add(-oneWeek)) {
return nil, gtserror.NewErrorForbidden(errors.New("ConfirmEmail: confirmation token expired"))
}
// mark the user's email address as confirmed + remove the unconfirmed address and the token
user.Email = user.UnconfirmedEmail
user.UnconfirmedEmail = ""
user.ConfirmedAt = time.Now()
user.ConfirmationToken = ""
user.UpdatedAt = time.Now()
if err := p.db.UpdateByPrimaryKey(ctx, user); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
return user, nil
}