mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-05-12 07:59:52 +00:00
feat: return not found. on /setup if already completed
This commit is contained in:
@@ -28,10 +28,10 @@ func (e *AlreadyInUseError) Is(target error) bool {
|
||||
return errors.As(target, &x)
|
||||
}
|
||||
|
||||
type SetupAlreadyCompletedError struct{}
|
||||
type SetupNotAvailableError struct{}
|
||||
|
||||
func (e *SetupAlreadyCompletedError) Error() string { return "setup already completed" }
|
||||
func (e *SetupAlreadyCompletedError) HttpStatusCode() int { return http.StatusConflict }
|
||||
func (e *SetupNotAvailableError) Error() string { return "not found" }
|
||||
func (e *SetupNotAvailableError) HttpStatusCode() int { return http.StatusNotFound }
|
||||
|
||||
type TokenInvalidOrExpiredError struct{}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"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"
|
||||
@@ -30,6 +31,7 @@ func NewUserSignupController(group *gin.RouterGroup, authMiddleware *middleware.
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -39,6 +41,21 @@ type UserSignupController struct {
|
||||
appConfigService *service.AppConfigService
|
||||
}
|
||||
|
||||
func (usc *UserSignupController) checkInitialAdminSetupAvailable(c *gin.Context) {
|
||||
setupCompleted, err := usc.userSignUpService.IsInitialAdminSetupCompleted(c.Request.Context())
|
||||
if err != nil {
|
||||
_ = c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if setupCompleted {
|
||||
_ = c.Error(&common.SetupNotAvailableError{})
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// signUpInitialAdmin godoc
|
||||
// @Summary Sign up initial admin user
|
||||
// @Description Sign up and generate setup access token for initial admin user
|
||||
|
||||
@@ -124,14 +124,12 @@ func (s *UserSignUpService) SignUpInitialAdmin(ctx context.Context, signUpData d
|
||||
tx.Rollback()
|
||||
}()
|
||||
|
||||
var userCount int64
|
||||
if err := tx.WithContext(ctx).Model(&model.User{}).
|
||||
Where("id != ?", staticApiKeyUserID).
|
||||
Count(&userCount).Error; err != nil {
|
||||
setupCompleted, err := s.isInitialAdminSetupCompleted(ctx, tx)
|
||||
if err != nil {
|
||||
return model.User{}, "", err
|
||||
}
|
||||
if userCount != 0 {
|
||||
return model.User{}, "", &common.SetupAlreadyCompletedError{}
|
||||
if setupCompleted {
|
||||
return model.User{}, "", &common.SetupNotAvailableError{}
|
||||
}
|
||||
|
||||
userToCreate := dto.UserCreateDto{
|
||||
@@ -161,6 +159,21 @@ func (s *UserSignUpService) SignUpInitialAdmin(ctx context.Context, signUpData d
|
||||
return user, token, nil
|
||||
}
|
||||
|
||||
func (s *UserSignUpService) IsInitialAdminSetupCompleted(ctx context.Context) (bool, error) {
|
||||
return s.isInitialAdminSetupCompleted(ctx, s.db)
|
||||
}
|
||||
|
||||
func (s *UserSignUpService) isInitialAdminSetupCompleted(ctx context.Context, db *gorm.DB) (bool, error) {
|
||||
var userCount int64
|
||||
if err := db.WithContext(ctx).Model(&model.User{}).
|
||||
Where("id != ?", staticApiKeyUserID).
|
||||
Count(&userCount).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
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{})
|
||||
|
||||
@@ -123,6 +123,10 @@ export default class UserService extends APIService {
|
||||
return res.data as User;
|
||||
};
|
||||
|
||||
checkInitialUserSetupAvailable = async () => {
|
||||
await this.api.get('/signup/setup');
|
||||
};
|
||||
|
||||
listSignupTokens = async (options?: ListRequestOptions) => {
|
||||
const res = await this.api.get('/signup-tokens', { params: options });
|
||||
return res.data as Paginated<SignupToken>;
|
||||
|
||||
18
frontend/src/routes/signup/setup/+page.ts
Normal file
18
frontend/src/routes/signup/setup/+page.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import { AxiosError } from 'axios';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const userService = new UserService();
|
||||
|
||||
try {
|
||||
await userService.checkInitialUserSetupAvailable();
|
||||
} catch (e) {
|
||||
if (e instanceof AxiosError && e.response?.status === 404) {
|
||||
error(404, 'Not found');
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -81,15 +81,11 @@ test.describe('Initial User Signup', () => {
|
||||
await expect(page.getByText('Set up your passkey')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Initial Signup - setup already completed', async ({ page }) => {
|
||||
test('Initial Signup - setup route unavailable after completion', async ({ page }) => {
|
||||
await cleanupBackend();
|
||||
await page.goto('/setup');
|
||||
await page.getByLabel('First name').fill('Test');
|
||||
await page.getByLabel('Last name').fill('User');
|
||||
await page.getByLabel('Username').fill('testuser123');
|
||||
await page.getByLabel('Email').fill(users.tim.email);
|
||||
await page.getByRole('button', { name: 'Sign Up' }).click();
|
||||
await expect(page.getByText('Setup already completed')).toBeVisible();
|
||||
await expect(page.getByText('Not found')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Sign Up' })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user