diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -230,16 +230,20 @@ */ public function signupValidate(Request $request) { + $rules = [ + 'login' => 'required|min:2', + 'password' => ['required', 'confirmed', new Password()], + 'domain' => 'required', + 'voucher' => 'max:32', + ]; + + // Direct signup by token + if ($request->token) { + $rules['token'] = ['required', 'string', new SignupToken()]; + } + // Validate input - $v = Validator::make( - $request->all(), - [ - 'login' => 'required|min:2', - 'password' => ['required', 'confirmed', new Password()], - 'domain' => 'required', - 'voucher' => 'max:32', - ] - ); + $v = Validator::make($request->all(), $rules); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); @@ -247,8 +251,16 @@ $settings = []; - // Plan parameter is required/allowed in mandate mode - if (!empty($request->plan) && empty($request->code) && empty($request->invitation)) { + if (!empty($request->token)) { + // Token mode, check the plan + $plan = $request->plan ? Plan::withEnvTenantContext()->where('title', $request->plan)->first() : null; + + if (!$plan || $plan->mode != Plan::MODE_TOKEN) { + $msg = self::trans('validation.exists', ['attribute' => 'plan']); + return response()->json(['status' => 'error', 'errors' => ['plan' => $msg]], 422); + } + } elseif (!empty($request->plan) && empty($request->code) && empty($request->invitation)) { + // Plan parameter is required/allowed in mandate mode $plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first(); if (!$plan || $plan->mode != Plan::MODE_MANDATE) { diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php --- a/src/tests/Feature/Controller/SignupTest.php +++ b/src/tests/Feature/Controller/SignupTest.php @@ -64,6 +64,8 @@ IP4Net::where('net_number', inet_pton('127.0.0.0'))->delete(); VatRate::query()->delete(); + @unlink(storage_path('signup-tokens.txt')); + parent::tearDown(); } @@ -910,6 +912,68 @@ // TODO: Test POST params validation } + /** + * Test signup via token + */ + public function testSignupToken(): void + { + Queue::fake(); + + $plan = Plan::create([ + 'title' => 'test', + 'name' => 'Test Account', + 'description' => 'Test', + 'free_months' => 1, + 'discount_qty' => 0, + 'discount_rate' => 0, + 'mode' => Plan::MODE_TOKEN, + ]); + + $post = [ + 'plan' => $plan->title, + 'token' => 'abc', + 'login' => 'test-inv', + 'domain' => 'kolabnow.com', + 'password' => 'testtest', + 'password_confirmation' => 'testtest', + ]; + + // Test invalid token + $response = $this->post('/api/auth/signup', $post); + $response->assertStatus(422); + $json = $response->json(); + + $this->assertSame('error', $json['status']); + $this->assertSame(['token' => ["The signup token is invalid."]], $json['errors']); + + file_put_contents(storage_path('signup-tokens.txt'), "abc\n"); + + // Test invalid plan (existing plan with another mode) + $post['plan'] = 'individual'; + $response = $this->post('/api/auth/signup', $post); + $response->assertStatus(422); + $json = $response->json(); + + $this->assertSame('error', $json['status']); + $this->assertSame(['plan' => "The selected plan is invalid."], $json['errors']); + + // Test valid input + $post['plan'] = $plan->title; + $response = $this->post('/api/auth/signup', $post); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame('test-inv@kolabnow.com', $json['email']); + + // Check if the user has been created + $user = User::where('email', 'test-inv@kolabnow.com')->first(); + + $this->assertNotEmpty($user); + $this->assertSame($plan->id, $user->getSetting('plan_id')); + } + /** * Test signup validation (POST /signup/validate) */