| @@ -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(); | return $this->failedResponse(); | ||||
| } | } | ||||
| /** | |||||
| * @var Customer | |||||
| */ | |||||
| $customer = $customer->first(); | $customer = $customer->first(); | ||||
| $kintoneId = $customer->getRecordId(); | $kintoneId = $customer->getRecordId(); | ||||
| @@ -66,7 +63,6 @@ class LoginController extends WebController | |||||
| $user->save(); | $user->save(); | ||||
| } | } | ||||
| if (Auth::attempt([ | if (Auth::attempt([ | ||||
| 'email' => $param->email, | 'email' => $param->email, | ||||
| 'password' => $param->password, | 'password' => $param->password, | ||||
| @@ -76,22 +72,7 @@ class LoginController extends WebController | |||||
| return $this->failedResponse(); | return $this->failedResponse(); | ||||
| } | } | ||||
| } else { | } 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 instanceof User) { | ||||
| if ($event->model->isDirty(User::COL_NAME_PASSWORD)) { | 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 = new MigrationHelper($table); | ||||
| $helper->baseColumn(); | $helper->baseColumn(); | ||||
| $table->string('email')->unique()->comment('Email'); | $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(1, ['email']); | ||||
| $helper->index(2, ['kintone_id']); | $helper->index(2, ['kintone_id']); | ||||
| @@ -29,9 +29,9 @@ return new class extends Migration | |||||
| $helper->baseColumn(); | $helper->baseColumn(); | ||||
| $table->string('email')->comment('Email'); | $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(1, ['email']); | ||||
| $helper->index(2, ['kintone_id']); | $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/start', App\Http\Controllers\Web\Customer\ChangeEmailStartController::class); | ||||
| RouteHelper::post('/email/change/verify', App\Http\Controllers\Web\Customer\ChangeEmailVerifyController::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); | |||||