refactor: migrate signup functionality to single usersignup module

This commit is contained in:
Elias Schneider
2026-06-30 20:53:31 +02:00
parent 58fcf7cbe6
commit ecad31cae2
14 changed files with 223 additions and 147 deletions

View File

@@ -143,7 +143,10 @@ func registerRoutes(r *gin.Engine, db *gorm.DB, svc *services) error {
controller.NewCustomClaimController(apiGroup, authMiddleware, svc.customClaimService)
controller.NewVersionController(apiGroup, authMiddleware, svc.versionService)
controller.NewScimController(apiGroup, authMiddleware, svc.scimService)
controller.NewUserSignupController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), svc.userSignUpService, svc.appConfigService)
svc.userSignUpModule.RegisterRoutes(apiGroup,
authMiddleware.Add(),
middleware.NewRateLimitMiddleware().Add(rate.Every(1*time.Minute), 10),
)
optionalBrowserAuth := authMiddleware.WithAdminNotRequired().WithSuccessOptional().WithApiKeyAuthDisabled().Add()
browserAuth := authMiddleware.WithAdminNotRequired().WithApiKeyAuthDisabled().Add()

View File

@@ -13,6 +13,7 @@ import (
"github.com/pocket-id/pocket-id/backend/internal/oidc"
"github.com/pocket-id/pocket-id/backend/internal/service"
"github.com/pocket-id/pocket-id/backend/internal/storage"
"github.com/pocket-id/pocket-id/backend/internal/usersignup"
"github.com/pocket-id/pocket-id/backend/internal/webauthn"
)
@@ -32,12 +33,12 @@ type services struct {
versionService *service.VersionService
fileStorage storage.FileStorage
appLockService *service.AppLockService
userSignUpService *service.UserSignUpService
oneTimeAccessService *service.OneTimeAccessService
apiKeyModule *apikey.Module
oidcModule *oidc.Module
webauthnModule *webauthn.Module
apiKeyModule *apikey.Module
oidcModule *oidc.Module
webauthnModule *webauthn.Module
userSignUpModule *usersignup.Module
}
// Initializes all services
@@ -113,7 +114,13 @@ func initServices(ctx context.Context, db *gorm.DB, httpClient *http.Client, ima
return nil, fmt.Errorf("failed to create API key module: %w", err)
}
svc.userSignUpService = service.NewUserSignupService(db, svc.jwtService, svc.auditLogService, svc.appConfigService, svc.userService)
svc.userSignUpModule = usersignup.New(usersignup.Dependencies{
DB: db,
Signer: svc.jwtService,
AuditLog: svc.auditLogService,
AppConfig: svc.appConfigService,
UserCreator: svc.userService,
})
svc.oneTimeAccessService = service.NewOneTimeAccessService(db, svc.userService, svc.jwtService, svc.auditLogService, svc.emailService, svc.appConfigService)
svc.versionService = service.NewVersionService(httpClient)

View File

@@ -1,9 +0,0 @@
package dto
type SignUpDto struct {
Username string `json:"username" binding:"required,username,min=1,max=50" unorm:"nfc"`
Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"`
FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"`
LastName string `json:"lastName" binding:"max=50" unorm:"nfc"`
Token string `json:"token"`
}

View File

@@ -1,22 +0,0 @@
package dto
import (
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/utils"
)
type SignupTokenCreateDto struct {
TTL utils.JSONDuration `json:"ttl" binding:"required,ttl"`
UsageLimit int `json:"usageLimit" binding:"required,min=1,max=100"`
UserGroupIDs []string `json:"userGroupIds"`
}
type SignupTokenDto struct {
ID string `json:"id"`
Token string `json:"token"`
ExpiresAt datatype.DateTime `json:"expiresAt"`
UsageLimit int `json:"usageLimit"`
UsageCount int `json:"usageCount"`
UserGroups []UserGroupMinimalDto `json:"userGroups"`
CreatedAt datatype.DateTime `json:"createdAt"`
}

View File

@@ -15,6 +15,7 @@ import (
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/oidc"
"github.com/pocket-id/pocket-id/backend/internal/service"
"github.com/pocket-id/pocket-id/backend/internal/usersignup"
"github.com/pocket-id/pocket-id/backend/internal/webauthn"
)
@@ -74,17 +75,14 @@ func (j *DbCleanupJobs) clearOneTimeAccessTokens(ctx context.Context) error {
return nil
}
// ClearSignupTokens deletes signup tokens that have expired
// clearSignupTokens deletes signup tokens that have expired
func (j *DbCleanupJobs) clearSignupTokens(ctx context.Context) error {
// Delete tokens that are expired OR have reached their usage limit
st := j.db.
WithContext(ctx).
Delete(&model.SignupToken{}, "expires_at < ?", datatype.DateTime(time.Now()))
if st.Error != nil {
return fmt.Errorf("failed to clean expired tokens: %w", st.Error)
count, err := usersignup.CleanupExpiredSignupTokens(ctx, j.db)
if err != nil {
return fmt.Errorf("failed to clean expired signup tokens: %w", err)
}
slog.InfoContext(ctx, "Cleaned expired tokens", slog.Int64("count", st.RowsAffected))
slog.InfoContext(ctx, "Cleaned expired signup tokens", slog.Int64("count", count))
return nil
}

View File

@@ -29,6 +29,7 @@ import (
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/oidc"
"github.com/pocket-id/pocket-id/backend/internal/storage"
"github.com/pocket-id/pocket-id/backend/internal/usersignup"
"github.com/pocket-id/pocket-id/backend/internal/utils"
jwkutils "github.com/pocket-id/pocket-id/backend/internal/utils/jwk"
"github.com/pocket-id/pocket-id/backend/internal/webauthn"
@@ -387,7 +388,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
}
}
signupTokens := []model.SignupToken{
signupTokens := []usersignup.SignupToken{
{
Base: model.Base{
ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",

View File

@@ -531,7 +531,7 @@ func (s *LdapService) reconcileUsers(ctx context.Context, tx *gorm.DB, desiredUs
userID := databaseUser.ID
if databaseUser.ID == "" {
createdUser, err := s.userService.createUserInternal(ctx, desiredUser.input, true, tx)
createdUser, err := s.userService.CreateUserInternal(ctx, desiredUser.input, true, tx)
if errors.Is(err, &common.AlreadyInUseError{}) {
slog.Warn("Skipping creating LDAP user", slog.String("username", desiredUser.input.Username), slog.Any("error", err))
continue

View File

@@ -239,7 +239,7 @@ func (s *UserService) CreateUser(ctx context.Context, input dto.UserCreateDto) (
tx.Rollback()
}()
user, err := s.createUserInternal(ctx, input, false, tx)
user, err := s.CreateUserInternal(ctx, input, false, tx)
if err != nil {
return model.User{}, err
}
@@ -252,7 +252,7 @@ func (s *UserService) CreateUser(ctx context.Context, input dto.UserCreateDto) (
return user, nil
}
func (s *UserService) createUserInternal(ctx context.Context, input dto.UserCreateDto, isLdapSync bool, tx *gorm.DB) (model.User, error) {
func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCreateDto, isLdapSync bool, tx *gorm.DB) (model.User, error) {
if s.appConfigService.GetDbConfig().RequireUserEmail.IsTrue() && input.Email == nil {
return model.User{}, &common.UserEmailNotSetError{}
}

View File

@@ -0,0 +1,19 @@
package usersignup
import (
"context"
"time"
"gorm.io/gorm"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
)
// CleanupExpiredSignupTokens deletes signup tokens that have expired
// It returns the number of rows removed
func CleanupExpiredSignupTokens(ctx context.Context, db *gorm.DB) (int64, error) {
st := db.
WithContext(ctx).
Delete(&SignupToken{}, "expires_at < ?", datatype.DateTime(time.Now()))
return st.RowsAffected, st.Error
}

View File

@@ -0,0 +1,31 @@
package usersignup
import (
"github.com/pocket-id/pocket-id/backend/internal/dto"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/utils"
)
type signUpDto struct {
Username string `json:"username" binding:"required,username,min=1,max=50" unorm:"nfc"`
Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"`
FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"`
LastName string `json:"lastName" binding:"max=50" unorm:"nfc"`
Token string `json:"token"`
}
type signupTokenCreateDto struct {
TTL utils.JSONDuration `json:"ttl" binding:"required,ttl"`
UsageLimit int `json:"usageLimit" binding:"required,min=1,max=100"`
UserGroupIDs []string `json:"userGroupIds"`
}
type signupTokenDto struct {
ID string `json:"id"`
Token string `json:"token"`
ExpiresAt datatype.DateTime `json:"expiresAt"`
UsageLimit int `json:"usageLimit"`
UsageCount int `json:"usageCount"`
UserGroups []dto.UserGroupMinimalDto `json:"userGroups"`
CreatedAt datatype.DateTime `json:"createdAt"`
}

View File

@@ -1,48 +1,30 @@
package controller
package usersignup
import (
"net/http"
"time"
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
"github.com/gin-gonic/gin"
"github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/middleware"
"github.com/pocket-id/pocket-id/backend/internal/service"
"github.com/pocket-id/pocket-id/backend/internal/utils"
"golang.org/x/time/rate"
"github.com/pocket-id/pocket-id/backend/internal/utils/cookie"
)
const defaultSignupTokenDuration = time.Hour
// NewUserSignupController creates a new controller for user signup and signup token management
// @Summary User signup and signup token management controller
// @Description Initializes all user signup-related API endpoints
// @Tags Users
func NewUserSignupController(group *gin.RouterGroup, authMiddleware *middleware.AuthMiddleware, rateLimitMiddleware *middleware.RateLimitMiddleware, userSignUpService *service.UserSignUpService, appConfigService *service.AppConfigService) {
usc := UserSignupController{
userSignUpService: userSignUpService,
appConfigService: appConfigService,
}
group.POST("/signup-tokens", authMiddleware.Add(), usc.createSignupTokenHandler)
group.GET("/signup-tokens", authMiddleware.Add(), usc.listSignupTokensHandler)
group.DELETE("/signup-tokens/:id", authMiddleware.Add(), usc.deleteSignupTokenHandler)
group.POST("/signup", rateLimitMiddleware.Add(rate.Every(1*time.Minute), 10), usc.signupHandler)
group.GET("/signup/setup", usc.checkInitialAdminSetupAvailable)
group.POST("/signup/setup", usc.signUpInitialAdmin)
type handler struct {
service *Service
appConfig AppConfigProvider
}
type UserSignupController struct {
userSignUpService *service.UserSignUpService
appConfigService *service.AppConfigService
func newHandler(service *Service, appConfig AppConfigProvider) *handler {
return &handler{service: service, appConfig: appConfig}
}
func (usc *UserSignupController) checkInitialAdminSetupAvailable(c *gin.Context) {
setupCompleted, err := usc.userSignUpService.IsInitialAdminSetupCompleted(c.Request.Context())
func (h *handler) checkInitialAdminSetupAvailable(c *gin.Context) {
setupCompleted, err := h.service.IsInitialAdminSetupCompleted(c.Request.Context())
if err != nil {
_ = c.Error(err)
return
@@ -62,17 +44,17 @@ func (usc *UserSignupController) checkInitialAdminSetupAvailable(c *gin.Context)
// @Tags Users
// @Accept json
// @Produce json
// @Param body body dto.SignUpDto true "User information"
// @Param body body signUpDto true "User information"
// @Success 200 {object} dto.UserDto
// @Router /api/signup/setup [post]
func (usc *UserSignupController) signUpInitialAdmin(c *gin.Context) {
var input dto.SignUpDto
func (h *handler) signUpInitialAdmin(c *gin.Context) {
var input signUpDto
if err := dto.ShouldBindWithNormalizedJSON(c, &input); err != nil {
_ = c.Error(err)
return
}
user, token, err := usc.userSignUpService.SignUpInitialAdmin(c.Request.Context(), input)
user, token, err := h.service.SignUpInitialAdmin(c.Request.Context(), input)
if err != nil {
_ = c.Error(err)
return
@@ -84,7 +66,7 @@ func (usc *UserSignupController) signUpInitialAdmin(c *gin.Context) {
return
}
maxAge := int(usc.appConfigService.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
maxAge := int(h.appConfig.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
cookie.AddAccessTokenCookie(c, maxAge, token)
c.JSON(http.StatusOK, userDto)
@@ -96,11 +78,11 @@ func (usc *UserSignupController) signUpInitialAdmin(c *gin.Context) {
// @Tags Users
// @Accept json
// @Produce json
// @Param token body dto.SignupTokenCreateDto true "Signup token information"
// @Success 201 {object} dto.SignupTokenDto
// @Param token body signupTokenCreateDto true "Signup token information"
// @Success 201 {object} signupTokenDto
// @Router /api/signup-tokens [post]
func (usc *UserSignupController) createSignupTokenHandler(c *gin.Context) {
var input dto.SignupTokenCreateDto
func (h *handler) createSignupToken(c *gin.Context) {
var input signupTokenCreateDto
if err := c.ShouldBindJSON(&input); err != nil {
_ = c.Error(err)
return
@@ -111,13 +93,13 @@ func (usc *UserSignupController) createSignupTokenHandler(c *gin.Context) {
ttl = defaultSignupTokenDuration
}
signupToken, err := usc.userSignUpService.CreateSignupToken(c.Request.Context(), ttl, input.UsageLimit, input.UserGroupIDs)
signupToken, err := h.service.CreateSignupToken(c.Request.Context(), ttl, input.UsageLimit, input.UserGroupIDs)
if err != nil {
_ = c.Error(err)
return
}
var tokenDto dto.SignupTokenDto
var tokenDto signupTokenDto
err = dto.MapStruct(signupToken, &tokenDto)
if err != nil {
_ = c.Error(err)
@@ -135,24 +117,24 @@ func (usc *UserSignupController) createSignupTokenHandler(c *gin.Context) {
// @Param pagination[limit] query int false "Number of items per page" default(20)
// @Param sort[column] query string false "Column to sort by"
// @Param sort[direction] query string false "Sort direction (asc or desc)" default("asc")
// @Success 200 {object} dto.Paginated[dto.SignupTokenDto]
// @Success 200 {object} dto.Paginated[signupTokenDto]
// @Router /api/signup-tokens [get]
func (usc *UserSignupController) listSignupTokensHandler(c *gin.Context) {
func (h *handler) listSignupTokens(c *gin.Context) {
listRequestOptions := utils.ParseListRequestOptions(c)
tokens, pagination, err := usc.userSignUpService.ListSignupTokens(c.Request.Context(), listRequestOptions)
tokens, pagination, err := h.service.ListSignupTokens(c.Request.Context(), listRequestOptions)
if err != nil {
_ = c.Error(err)
return
}
var tokensDto []dto.SignupTokenDto
var tokensDto []signupTokenDto
if err := dto.MapStructList(tokens, &tokensDto); err != nil {
_ = c.Error(err)
return
}
c.JSON(http.StatusOK, dto.Paginated[dto.SignupTokenDto]{
c.JSON(http.StatusOK, dto.Paginated[signupTokenDto]{
Data: tokensDto,
Pagination: pagination,
})
@@ -165,10 +147,10 @@ func (usc *UserSignupController) listSignupTokensHandler(c *gin.Context) {
// @Param id path string true "Token ID"
// @Success 204 "No Content"
// @Router /api/signup-tokens/{id} [delete]
func (usc *UserSignupController) deleteSignupTokenHandler(c *gin.Context) {
func (h *handler) deleteSignupToken(c *gin.Context) {
tokenID := c.Param("id")
err := usc.userSignUpService.DeleteSignupToken(c.Request.Context(), tokenID)
err := h.service.DeleteSignupToken(c.Request.Context(), tokenID)
if err != nil {
_ = c.Error(err)
return
@@ -177,17 +159,17 @@ func (usc *UserSignupController) deleteSignupTokenHandler(c *gin.Context) {
c.Status(http.StatusNoContent)
}
// signupWithTokenHandler godoc
// signupHandler godoc
// @Summary Sign up
// @Description Create a new user account
// @Tags Users
// @Accept json
// @Produce json
// @Param user body dto.SignUpDto true "User information"
// @Success 201 {object} dto.SignUpDto
// @Param user body signUpDto true "User information"
// @Success 201 {object} dto.UserDto
// @Router /api/signup [post]
func (usc *UserSignupController) signupHandler(c *gin.Context) {
var input dto.SignUpDto
func (h *handler) signup(c *gin.Context) {
var input signUpDto
if err := dto.ShouldBindWithNormalizedJSON(c, &input); err != nil {
_ = c.Error(err)
return
@@ -196,13 +178,13 @@ func (usc *UserSignupController) signupHandler(c *gin.Context) {
ipAddress := c.ClientIP()
userAgent := c.GetHeader("User-Agent")
user, accessToken, err := usc.userSignUpService.SignUp(c.Request.Context(), input, ipAddress, userAgent)
user, accessToken, err := h.service.SignUp(c.Request.Context(), input, ipAddress, userAgent)
if err != nil {
_ = c.Error(err)
return
}
maxAge := int(usc.appConfigService.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
maxAge := int(h.appConfig.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
cookie.AddAccessTokenCookie(c, maxAge, accessToken)
var userDto dto.UserDto

View File

@@ -1,19 +1,21 @@
package model
package usersignup
import (
"time"
"github.com/pocket-id/pocket-id/backend/internal/model"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
)
// SignupToken is a single- or limited-use token that grants the ability to self-register
type SignupToken struct {
Base
model.Base
Token string `json:"token"`
ExpiresAt datatype.DateTime `json:"expiresAt" sortable:"true"`
UsageLimit int `json:"usageLimit" sortable:"true"`
UsageCount int `json:"usageCount" sortable:"true"`
UserGroups []UserGroup `gorm:"many2many:signup_tokens_user_groups;"`
UserGroups []model.UserGroup `gorm:"many2many:signup_tokens_user_groups;"`
}
func (st *SignupToken) IsExpired() bool {

View File

@@ -0,0 +1,60 @@
package usersignup
import (
"context"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/model"
)
type TokenService interface {
GenerateAccessToken(user model.User, authenticationMethod string) (string, error)
}
type AuditLogger interface {
Create(ctx context.Context, event model.AuditLogEvent, ipAddress, userAgent, userID string, data model.AuditLogData, tx *gorm.DB) (model.AuditLog, bool)
}
type AppConfigProvider interface {
GetDbConfig() *model.AppConfig
}
type UserCreator interface {
CreateUserInternal(ctx context.Context, input dto.UserCreateDto, isLdapSync bool, tx *gorm.DB) (model.User, error)
}
type Dependencies struct {
DB *gorm.DB
Signer TokenService
AuditLog AuditLogger
AppConfig AppConfigProvider
UserCreator UserCreator
}
type Module struct {
service *Service
handler *handler
}
func New(deps Dependencies) *Module {
service := newService(deps)
return &Module{
service: service,
handler: newHandler(service, deps.AppConfig),
}
}
// RegisterRoutes mounts the signup and signup-token management endpoints
// adminAuth guards the admin token-management routes; signupRateLimit throttles public self-signup
func (m *Module) RegisterRoutes(apiGroup *gin.RouterGroup, adminAuth, signupRateLimit gin.HandlerFunc) {
apiGroup.POST("/signup-tokens", adminAuth, m.handler.createSignupToken)
apiGroup.GET("/signup-tokens", adminAuth, m.handler.listSignupTokens)
apiGroup.DELETE("/signup-tokens/:id", adminAuth, m.handler.deleteSignupToken)
apiGroup.POST("/signup", signupRateLimit, m.handler.signup)
apiGroup.GET("/signup/setup", m.handler.checkInitialAdminSetupAvailable)
apiGroup.POST("/signup/setup", m.handler.signUpInitialAdmin)
}

View File

@@ -1,4 +1,4 @@
package service
package usersignup
import (
"context"
@@ -6,34 +6,39 @@ import (
"strings"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/dto"
"github.com/pocket-id/pocket-id/backend/internal/model"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/utils"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type UserSignUpService struct {
db *gorm.DB
userService *UserService
jwtService *JwtService
auditLogService *AuditLogService
appConfigService *AppConfigService
// authenticationMethodOneTimePassword identifies one-time-password authentication, used for the initial admin setup token
// It must match the value emitted by the JWT service in the access token's "amr" claim
const authenticationMethodOneTimePassword = "otp"
type Service struct {
db *gorm.DB
userCreator UserCreator
signer TokenService
auditLog AuditLogger
appConfig AppConfigProvider
}
func NewUserSignupService(db *gorm.DB, jwtService *JwtService, auditLogService *AuditLogService, appConfigService *AppConfigService, userService *UserService) *UserSignUpService {
return &UserSignUpService{
db: db,
jwtService: jwtService,
auditLogService: auditLogService,
appConfigService: appConfigService,
userService: userService,
func newService(deps Dependencies) *Service {
return &Service{
db: deps.DB,
userCreator: deps.UserCreator,
signer: deps.Signer,
auditLog: deps.AuditLog,
appConfig: deps.AppConfig,
}
}
func (s *UserSignUpService) SignUp(ctx context.Context, signupData dto.SignUpDto, ipAddress, userAgent string) (model.User, string, error) {
func (s *Service) SignUp(ctx context.Context, signupData signUpDto, ipAddress, userAgent string) (model.User, string, error) {
tx := s.db.Begin()
defer func() {
tx.Rollback()
@@ -41,12 +46,12 @@ func (s *UserSignUpService) SignUp(ctx context.Context, signupData dto.SignUpDto
tokenProvided := signupData.Token != ""
config := s.appConfigService.GetDbConfig()
config := s.appConfig.GetDbConfig()
if config.AllowUserSignups.Value != "open" && !tokenProvided {
return model.User{}, "", &common.OpenSignupDisabledError{}
}
var signupToken model.SignupToken
var signupToken SignupToken
var userGroupIDs []string
if tokenProvided {
err := tx.
@@ -79,21 +84,21 @@ func (s *UserSignUpService) SignUp(ctx context.Context, signupData dto.SignUpDto
LastName: signupData.LastName,
DisplayName: strings.TrimSpace(signupData.FirstName + " " + signupData.LastName),
UserGroupIds: userGroupIDs,
EmailVerified: s.appConfigService.GetDbConfig().EmailsVerified.IsTrue(),
EmailVerified: s.appConfig.GetDbConfig().EmailsVerified.IsTrue(),
}
user, err := s.userService.createUserInternal(ctx, userToCreate, false, tx)
user, err := s.userCreator.CreateUserInternal(ctx, userToCreate, false, tx)
if err != nil {
return model.User{}, "", err
}
accessToken, err := s.jwtService.GenerateAccessToken(user, "")
accessToken, err := s.signer.GenerateAccessToken(user, "")
if err != nil {
return model.User{}, "", err
}
if tokenProvided {
s.auditLogService.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{
s.auditLog.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{
"signupToken": signupToken.Token,
}, tx)
@@ -102,10 +107,9 @@ func (s *UserSignUpService) SignUp(ctx context.Context, signupData dto.SignUpDto
err = tx.WithContext(ctx).Save(&signupToken).Error
if err != nil {
return model.User{}, "", err
}
} else {
s.auditLogService.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{
s.auditLog.Create(ctx, model.AuditLogEventAccountCreated, ipAddress, userAgent, user.ID, model.AuditLogData{
"method": "open_signup",
}, tx)
}
@@ -118,7 +122,7 @@ func (s *UserSignUpService) SignUp(ctx context.Context, signupData dto.SignUpDto
return user, accessToken, nil
}
func (s *UserSignUpService) SignUpInitialAdmin(ctx context.Context, signUpData dto.SignUpDto) (model.User, string, error) {
func (s *Service) SignUpInitialAdmin(ctx context.Context, signUpData signUpDto) (model.User, string, error) {
tx := s.db.Begin()
defer func() {
tx.Rollback()
@@ -141,12 +145,12 @@ func (s *UserSignUpService) SignUpInitialAdmin(ctx context.Context, signUpData d
IsAdmin: true,
}
user, err := s.userService.createUserInternal(ctx, userToCreate, false, tx)
user, err := s.userCreator.CreateUserInternal(ctx, userToCreate, false, tx)
if err != nil {
return model.User{}, "", err
}
token, err := s.jwtService.GenerateAccessToken(user, AuthenticationMethodOneTimePassword)
token, err := s.signer.GenerateAccessToken(user, authenticationMethodOneTimePassword)
if err != nil {
return model.User{}, "", err
}
@@ -159,11 +163,11 @@ func (s *UserSignUpService) SignUpInitialAdmin(ctx context.Context, signUpData d
return user, token, nil
}
func (s *UserSignUpService) IsInitialAdminSetupCompleted(ctx context.Context) (bool, error) {
func (s *Service) IsInitialAdminSetupCompleted(ctx context.Context) (bool, error) {
return s.isInitialAdminSetupCompleted(ctx, s.db)
}
func (s *UserSignUpService) isInitialAdminSetupCompleted(ctx context.Context, db *gorm.DB) (bool, error) {
func (s *Service) isInitialAdminSetupCompleted(ctx context.Context, db *gorm.DB) (bool, error) {
var userCount int64
if err := db.WithContext(ctx).Model(&model.User{}).
Where("id != ?", common.StaticApiKeyUserID).
@@ -174,22 +178,22 @@ func (s *UserSignUpService) isInitialAdminSetupCompleted(ctx context.Context, db
return userCount != 0, nil
}
func (s *UserSignUpService) ListSignupTokens(ctx context.Context, listRequestOptions utils.ListRequestOptions) ([]model.SignupToken, utils.PaginationResponse, error) {
var tokens []model.SignupToken
query := s.db.WithContext(ctx).Preload("UserGroups").Model(&model.SignupToken{})
func (s *Service) ListSignupTokens(ctx context.Context, listRequestOptions utils.ListRequestOptions) ([]SignupToken, utils.PaginationResponse, error) {
var tokens []SignupToken
query := s.db.WithContext(ctx).Preload("UserGroups").Model(&SignupToken{})
pagination, err := utils.PaginateFilterAndSort(listRequestOptions, query, &tokens)
return tokens, pagination, err
}
func (s *UserSignUpService) DeleteSignupToken(ctx context.Context, tokenID string) error {
return s.db.WithContext(ctx).Delete(&model.SignupToken{}, "id = ?", tokenID).Error
func (s *Service) DeleteSignupToken(ctx context.Context, tokenID string) error {
return s.db.WithContext(ctx).Delete(&SignupToken{}, "id = ?", tokenID).Error
}
func (s *UserSignUpService) CreateSignupToken(ctx context.Context, ttl time.Duration, usageLimit int, userGroupIDs []string) (model.SignupToken, error) {
signupToken, err := NewSignupToken(ttl, usageLimit)
func (s *Service) CreateSignupToken(ctx context.Context, ttl time.Duration, usageLimit int, userGroupIDs []string) (SignupToken, error) {
signupToken, err := newSignupToken(ttl, usageLimit)
if err != nil {
return model.SignupToken{}, err
return SignupToken{}, err
}
var userGroups []model.UserGroup
@@ -198,19 +202,19 @@ func (s *UserSignUpService) CreateSignupToken(ctx context.Context, ttl time.Dura
Find(&userGroups).
Error
if err != nil {
return model.SignupToken{}, err
return SignupToken{}, err
}
signupToken.UserGroups = userGroups
err = s.db.WithContext(ctx).Create(signupToken).Error
if err != nil {
return model.SignupToken{}, err
return SignupToken{}, err
}
return *signupToken, nil
}
func NewSignupToken(ttl time.Duration, usageLimit int) (*model.SignupToken, error) {
func newSignupToken(ttl time.Duration, usageLimit int) (*SignupToken, error) {
// Generate a random token
randomString, err := utils.GenerateRandomAlphanumericString(16)
if err != nil {
@@ -218,7 +222,7 @@ func NewSignupToken(ttl time.Duration, usageLimit int) (*model.SignupToken, erro
}
now := time.Now().Round(time.Second)
token := &model.SignupToken{
token := &SignupToken{
Token: randomString,
ExpiresAt: datatype.DateTime(now.Add(ttl)),
UsageLimit: usageLimit,