| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // 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-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 15:43:25 +02:00
										 |  |  | package user | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-10-31 15:46:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 
					
						
							| 
									
										
										
										
											2021-12-07 13:31:39 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2021-10-31 15:46:23 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | 
					
						
							| 
									
										
										
										
											2021-07-26 20:25:54 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/text" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	"github.com/superseriousbusiness/oauth2/v4" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 15:43:25 +02:00
										 |  |  | // Create processes the given form for creating a new user+account. | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2024-06-06 15:43:25 +02:00
										 |  |  | // App should be the app used to create the user+account. | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | // If nil, the instance app will be used. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Precondition: the form's fields should have already been | 
					
						
							|  |  |  | // validated and normalized by the caller. | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | func (p *Processor) Create( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	app *gtsmodel.Application, | 
					
						
							|  |  |  | 	form *apimodel.AccountCreateRequest, | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | ) (*gtsmodel.User, gtserror.WithCode) { | 
					
						
							|  |  |  | 	const ( | 
					
						
							|  |  |  | 		usersPerDay = 10 | 
					
						
							|  |  |  | 		regBacklog  = 20 | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure no more than usersPerDay | 
					
						
							|  |  |  | 	// have registered in the last 24h. | 
					
						
							|  |  |  | 	newUsersCount, err := p.state.DB.CountApprovedSignupsSince(ctx, time.Now().Add(-24*time.Hour)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := fmt.Errorf("db error counting new users: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if newUsersCount >= usersPerDay { | 
					
						
							|  |  |  | 		err := fmt.Errorf("this instance has hit its limit of new sign-ups for today; you can try again tomorrow") | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure the new users backlog isn't full. | 
					
						
							|  |  |  | 	backlogLen, err := p.state.DB.CountUnhandledSignups(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := fmt.Errorf("db error counting registration backlog length: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if backlogLen >= regBacklog { | 
					
						
							|  |  |  | 		err := fmt.Errorf("this instance's sign-up backlog is currently full; you must wait until pending sign-ups are handled by the admin(s)") | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-01 18:26:53 +00:00
										 |  |  | 	emailAvailable, err := p.state.DB.IsEmailAvailable(ctx, form.Email) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 		err := fmt.Errorf("db error checking email availability: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if !emailAvailable { | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 		err := fmt.Errorf("email address %s is not available", form.Email) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorConflict(err, err.Error()) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-01 18:26:53 +00:00
										 |  |  | 	usernameAvailable, err := p.state.DB.IsUsernameAvailable(ctx, form.Username) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 		err := fmt.Errorf("db error checking username availability: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if !usernameAvailable { | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 		err := fmt.Errorf("username %s is not available", form.Username) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorConflict(err, err.Error()) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 	// Only store reason if one is required. | 
					
						
							|  |  |  | 	var reason string | 
					
						
							|  |  |  | 	if config.GetAccountsReasonRequired() { | 
					
						
							|  |  |  | 		reason = form.Reason | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | 	// Use instance app if no app provided. | 
					
						
							|  |  |  | 	if app == nil { | 
					
						
							|  |  |  | 		app, err = p.state.DB.GetInstanceApplication(ctx) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			err := fmt.Errorf("db error getting instance app: %w", err) | 
					
						
							|  |  |  | 			return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 	user, err := p.state.DB.NewSignup(ctx, gtsmodel.NewSignup{ | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | 		Username: form.Username, | 
					
						
							|  |  |  | 		Email:    form.Email, | 
					
						
							|  |  |  | 		Password: form.Password, | 
					
						
							|  |  |  | 		Reason:   text.SanitizeToPlaintext(reason), | 
					
						
							|  |  |  | 		SignUpIP: form.IP, | 
					
						
							|  |  |  | 		Locale:   form.Locale, | 
					
						
							|  |  |  | 		AppID:    app.ID, | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 		err := fmt.Errorf("db error creating new signup: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 15:43:25 +02:00
										 |  |  | 	// There are side effects for creating a new user+account | 
					
						
							| 
									
										
										
										
											2023-07-23 12:33:17 +02:00
										 |  |  | 	// (confirmation emails etc), perform these async. | 
					
						
							| 
									
										
										
										
											2024-04-26 13:50:46 +01:00
										 |  |  | 	p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ | 
					
						
							| 
									
										
										
										
											2024-06-06 15:43:25 +02:00
										 |  |  | 		// Use ap.ObjectProfile here to | 
					
						
							|  |  |  | 		// distinguish this message (user model) | 
					
						
							|  |  |  | 		// from ap.ActorPerson (account model). | 
					
						
							| 
									
										
										
										
											2021-10-31 15:46:23 +01:00
										 |  |  | 		APObjectType:   ap.ObjectProfile, | 
					
						
							|  |  |  | 		APActivityType: ap.ActivityCreate, | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | 		GTSModel:       user, | 
					
						
							| 
									
										
										
										
											2024-04-26 13:50:46 +01:00
										 |  |  | 		Origin:         user.Account, | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-10-31 15:46:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-11 11:45:53 +02:00
										 |  |  | 	return user, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TokenForNewUser generates an OAuth Bearer token | 
					
						
							|  |  |  | // for a new user (with account) created by Create(). | 
					
						
							|  |  |  | func (p *Processor) TokenForNewUser( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	appToken oauth2.TokenInfo, | 
					
						
							|  |  |  | 	app *gtsmodel.Application, | 
					
						
							|  |  |  | 	user *gtsmodel.User, | 
					
						
							|  |  |  | ) (*apimodel.Token, gtserror.WithCode) { | 
					
						
							|  |  |  | 	// Generate access token. | 
					
						
							|  |  |  | 	accessToken, err := p.oauthServer.GenerateUserAccessToken( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		appToken, | 
					
						
							|  |  |  | 		app.ClientSecret, | 
					
						
							|  |  |  | 		user.ID, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := fmt.Errorf("error creating new access token for user %s: %w", user.ID, err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	return &apimodel.Token{ | 
					
						
							|  |  |  | 		AccessToken: accessToken.GetAccess(), | 
					
						
							|  |  |  | 		TokenType:   "Bearer", | 
					
						
							|  |  |  | 		Scope:       accessToken.GetScope(), | 
					
						
							|  |  |  | 		CreatedAt:   accessToken.GetAccessCreateAt().Unix(), | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } |