| @@ -0,0 +1,35 @@ | |||
| <?php | |||
| namespace App\Email\Guests; | |||
| use App\Models\PasswordSettingToken; | |||
| class PasswordSettingStart extends Guest | |||
| { | |||
| public function __construct(private PasswordSettingToken $token) | |||
| { | |||
| } | |||
| public function getTemplateName(): string | |||
| { | |||
| return 'emails.guests.password_setting_start'; | |||
| } | |||
| public function getSubject(): string | |||
| { | |||
| return "ログインパスワード変更手続きのお知らせ"; | |||
| } | |||
| public function getGuestParams(): array | |||
| { | |||
| return [ | |||
| 'url' => $this->getVerifyUrl(), | |||
| ]; | |||
| } | |||
| private function getVerifyUrl(): string | |||
| { | |||
| return $this->getAppUrl(['setting', 'password', 'verify', $this->token->token]); | |||
| } | |||
| } | |||
| @@ -43,9 +43,6 @@ class LoginController extends WebController | |||
| return $this->failedResponse(); | |||
| } | |||
| /** | |||
| * @var Customer | |||
| */ | |||
| $customer = $customer->first(); | |||
| $kintoneId = $customer->getRecordId(); | |||
| @@ -66,7 +63,6 @@ class LoginController extends WebController | |||
| $user->save(); | |||
| } | |||
| if (Auth::attempt([ | |||
| 'email' => $param->email, | |||
| 'password' => $param->password, | |||
| @@ -76,22 +72,7 @@ class LoginController extends WebController | |||
| return $this->failedResponse(); | |||
| } | |||
| } else { | |||
| // 初回ログインのケース | |||
| $password = "testtest"; | |||
| $user = new User(); | |||
| $user->email = $param->email; | |||
| $user->kintone_id = $customer->getRecordId(); | |||
| $user->password = $password; | |||
| $user->kintone_customer_code = $customer->getNumber(Customer::FIELD_CUSTOMER_CODE); | |||
| $user->save(); | |||
| if (Auth::attempt([ | |||
| 'email' => $param->email, | |||
| 'password' => $password, | |||
| ])) { | |||
| return $this->successResponse($customer->toArray()); | |||
| } else { | |||
| return $this->failedResponse(); | |||
| } | |||
| return $this->failedResponse(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\WebController; | |||
| use App\Kintone\Models\Customer; | |||
| use App\Logic\PasswordSettingManager; | |||
| use App\Models\User; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| class PasswordSettingStartController extends WebController | |||
| { | |||
| public function name(): string | |||
| { | |||
| return "パスワード設定開始"; | |||
| } | |||
| public function description(): string | |||
| { | |||
| return "パスワード設定手続きを開始する"; | |||
| } | |||
| public function __construct(protected PasswordSettingStartParam $param, private PasswordSettingManager $manager) | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| protected function run(Request $request): JsonResponse | |||
| { | |||
| $param = $this->param; | |||
| $access = Customer::getAccess(); | |||
| $query = Customer::getQuery()->where(Customer::FIELD_EMAIL, $param->email); | |||
| $customer = $access->some($query); | |||
| if ($customer->count() !== 1) { | |||
| // 無効なユーザだが、セキュリティ対策として成功と見せかける | |||
| return $this->successResponse(); | |||
| } | |||
| $customer = $customer->first(); | |||
| $kintoneId = $customer->getRecordId(); | |||
| $user = User::whereKintoneId($kintoneId) | |||
| ->first(); | |||
| if ($user instanceof User) { | |||
| //データ同期 | |||
| if ($user->email !== $param->email) { | |||
| $user->email = $param->email; | |||
| $user->save(); | |||
| } | |||
| if ($user->kintone_customer_code !== $customer->getNumber(Customer::FIELD_CUSTOMER_CODE)) { | |||
| $user->kintone_customer_code = $customer->getNumber(Customer::FIELD_CUSTOMER_CODE); | |||
| $user->save(); | |||
| } | |||
| } else { | |||
| // 新規の場合はユーザーを追加する | |||
| $user = new User(); | |||
| $user->email = $param->email; | |||
| $user->kintone_id = $customer->getRecordId(); | |||
| $user->kintone_customer_code = $customer->getNumber(Customer::FIELD_CUSTOMER_CODE); | |||
| $user->save(); | |||
| } | |||
| // トークン生成 | |||
| $this->manager->generate($user); | |||
| return $this->successResponse(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\BaseParam; | |||
| use App\Http\Controllers\Web\Rule; | |||
| /** | |||
| * @property string $email | |||
| */ | |||
| class PasswordSettingStartParam extends BaseParam | |||
| { | |||
| public function rules(): array | |||
| { | |||
| return [ | |||
| 'email' => $this->str([...Rule::email()]), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\WebController; | |||
| use App\Kintone\Models\Customer; | |||
| use App\Logic\PasswordSettingManager; | |||
| use App\Models\User; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| class PasswordSettingVerifyController extends WebController | |||
| { | |||
| public function name(): string | |||
| { | |||
| return "パスワード設定認証"; | |||
| } | |||
| public function description(): string | |||
| { | |||
| return "パスワード設定手続きを認証する"; | |||
| } | |||
| public function __construct(protected PasswordSettingVerifyParam $param, private PasswordSettingManager $manager) | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| protected function run(Request $request): JsonResponse | |||
| { | |||
| $param = $this->param; | |||
| $user = $this->manager->verify($param->token); | |||
| if ($user === null) { | |||
| return $this->failedResponse(); | |||
| } | |||
| $user->password = $param->password; | |||
| $user->save(); | |||
| return $this->successResponse(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\BaseParam; | |||
| use App\Rules\LoginPassword; | |||
| /** | |||
| * @property string $token | |||
| * @property string $password | |||
| */ | |||
| class PasswordSettingVerifyParam extends BaseParam | |||
| { | |||
| public function rules(): array | |||
| { | |||
| return [ | |||
| 'token' => $this->str(), | |||
| 'password' => $this->str([new LoginPassword()]), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -25,7 +25,9 @@ class UpdatingListener extends ModelListener | |||
| // ログインパスワードのハッシュ化 | |||
| if ($event->model instanceof User) { | |||
| if ($event->model->isDirty(User::COL_NAME_PASSWORD)) { | |||
| $event->model->password = Hash::make($event->model->password); | |||
| if ($event->model->password !== null) { | |||
| $event->model->password = Hash::make($event->model->password); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,50 @@ | |||
| <?php | |||
| namespace App\Logic; | |||
| use App\Email\Guests\PasswordSettingStart; | |||
| use App\Models\PasswordSettingToken; | |||
| use App\Models\User; | |||
| use App\Util\DateUtil; | |||
| use Illuminate\Support\Str; | |||
| class PasswordSettingManager | |||
| { | |||
| public function __construct() | |||
| { | |||
| } | |||
| public function generate(User $user) | |||
| { | |||
| // トークン生成 | |||
| $model = new PasswordSettingToken(); | |||
| $model->user_id = $user->id; | |||
| $model->token = Str::uuid(); | |||
| $model->expires_at = DateUtil::now()->addHours(24); | |||
| $model->save(); | |||
| // メール送信 | |||
| $email = (new PasswordSettingStart($model)) | |||
| ->setEmail($user->email); | |||
| $emailManager = new EmailManager($email); | |||
| $emailManager->confirm(); | |||
| } | |||
| public function verify(string $token): User|null | |||
| { | |||
| $model = PasswordSettingToken::whereToken($token) | |||
| ->expiresIn() | |||
| ->first(); | |||
| if ($model === null) { | |||
| return null; | |||
| } | |||
| $user = User::whereId($model->user_id)->firstOrFail(); | |||
| $model->delete(); | |||
| return $user; | |||
| } | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| <?php | |||
| namespace App\Models; | |||
| use App\Models\Feature\UserId; | |||
| use App\Util\DateUtil; | |||
| use Illuminate\Database\Eloquent\Builder; | |||
| class PasswordSettingToken extends AppModel | |||
| { | |||
| use UserId; | |||
| const COL_NAME_USER_ID = ColumnName::USER_ID; | |||
| const COL_NAME_TOKEN = 'token'; | |||
| const COL_NAME_EXPIRES_AT = 'expires_at'; | |||
| protected $casts = [ | |||
| self::COL_NAME_EXPIRES_AT => 'datetime', | |||
| ]; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getModelName(): string | |||
| { | |||
| return "ログインパスワード設定トークン"; | |||
| } | |||
| public function scopeExpiresIn(Builder $query) | |||
| { | |||
| return $query->where(self::COL_NAME_EXPIRES_AT, '>', DateUtil::now()); | |||
| } | |||
| } | |||
| @@ -16,9 +16,9 @@ return new class extends Migration | |||
| $helper = new MigrationHelper($table); | |||
| $helper->baseColumn(); | |||
| $table->string('email')->unique()->comment('Email'); | |||
| $table->string('password')->comment('ログインパスワード'); | |||
| $table->string('kintone_id')->comment('KintoneID'); | |||
| $table->string('kintone_customer_code')->comment('顧客コード'); | |||
| $table->string('password')->nullable()->comment('ログインパスワード'); | |||
| $table->string('kintone_id')->nullable()->comment('KintoneID'); | |||
| $table->string('kintone_customer_code')->nullable()->comment('顧客コード'); | |||
| $helper->index(1, ['email']); | |||
| $helper->index(2, ['kintone_id']); | |||
| @@ -29,9 +29,9 @@ return new class extends Migration | |||
| $helper->baseColumn(); | |||
| $table->string('email')->comment('Email'); | |||
| $table->string('password')->comment('ログインパスワード'); | |||
| $table->string('kintone_id')->comment('KintoneID'); | |||
| $table->string('kintone_customer_code')->comment('顧客コード'); | |||
| $table->string('password')->nullable()->comment('ログインパスワード'); | |||
| $table->string('kintone_id')->nullable()->comment('KintoneID'); | |||
| $table->string('kintone_customer_code')->nullable()->comment('顧客コード'); | |||
| $helper->index(1, ['email']); | |||
| $helper->index(2, ['kintone_id']); | |||
| @@ -0,0 +1,37 @@ | |||
| <?php | |||
| use App\Models\ColumnName; | |||
| use App\Util\MigrationHelper; | |||
| use Illuminate\Database\Migrations\Migration; | |||
| use Illuminate\Database\Schema\Blueprint; | |||
| use Illuminate\Support\Facades\Schema; | |||
| return new class extends Migration | |||
| { | |||
| /** | |||
| * Run the migrations. | |||
| */ | |||
| public function up(): void | |||
| { | |||
| Schema::create('password_setting_tokens', function (Blueprint $table) { | |||
| $helper = new MigrationHelper($table); | |||
| $helper->baseColumn(); | |||
| $table->uuid(ColumnName::USER_ID)->unique()->comment('ユーザーID'); | |||
| $table->uuid('token')->unique()->comment('トークン'); | |||
| $table->string('expires_at')->comment('有効期限'); | |||
| $helper->index(1, [ColumnName::USER_ID]); | |||
| $helper->index(3, ['token']); | |||
| $helper->index(4, ['expires_at']); | |||
| }); | |||
| } | |||
| /** | |||
| * Reverse the migrations. | |||
| */ | |||
| public function down(): void | |||
| { | |||
| Schema::dropIfExists('email_change_tokens'); | |||
| } | |||
| }; | |||
| @@ -0,0 +1,6 @@ | |||
| @extends('emails.layouts.guest') | |||
| @section('contents') | |||
| こちらのURLにアクセスし、ログインパスワードの変更手続きを進めてください。 | |||
| {{ $url }} | |||
| @endsection | |||
| @@ -34,3 +34,6 @@ RouteHelper::post('/ask', App\Http\Controllers\Web\FAQ\AskController::class); | |||
| RouteHelper::post('/email/change/start', App\Http\Controllers\Web\Customer\ChangeEmailStartController::class); | |||
| RouteHelper::post('/email/change/verify', App\Http\Controllers\Web\Customer\ChangeEmailVerifyController::class); | |||
| RouteHelper::post('/password/setting/start', App\Http\Controllers\Web\Auth\PasswordSettingStartController::class); | |||
| RouteHelper::post('/password/setting/verify', App\Http\Controllers\Web\Auth\PasswordSettingVerifyController::class); | |||