ユーザーの申し込み

This commit is contained in:
2025-12-08 00:56:06 +09:00
parent f6389eedc9
commit c7537ab36f
7 changed files with 306 additions and 43 deletions

View File

@@ -7,15 +7,13 @@ class Auth {
/**
* 性別: -1 = 不明, 0 = 男性, 1 = 女性
* ロール: -1 = BAN, 0 = ユーザー, 1 = スタッフ
* トークン: string token, int expDate
*
* パスワードとメールアドレスはArgon2IDでハッシュする
*/
private \stdClass $user;
private \stdClass $pubUser;
private ?string $token;
protected string $dataDir = ROOT."/data/user/";
protected string $assDir = ROOT."/public/static/user/";
protected int $minPassLen = 20;
protected int $tokenDuration = 31536000; // 1年間
protected string|int|null $algo = PASSWORD_ARGON2ID;
@@ -36,25 +34,6 @@ class Auth {
return $this->pubUser;
}
private function verifyLogin(string $username, string $password): \Result {
$userData = $this->getUserData();
if ($username !== $userData->username || !password_verify($password, $userData->password)) {
return \Result::error('エラー:ユーザー名又はパスワードが一致していません。');
}
if (password_needs_rehash($userData->password, $algo)) {
$userData->password = password_hash($password, $algo);
$path = $this->dataDir.$this->id.'.'.$userData->username.'.json';
$json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
return \Result::error('エラー:ユーザーデータの保存に失敗。');
}
}
return \Result::success();
}
public function setToken(string $username, string $password): \Result {
$userData = $this->getUserData();
if (!isset($userData->tokens) || !is_array($userData->tokens)) $userData->tokens = [];
@@ -156,7 +135,7 @@ class Auth {
$userList = scandir($this->dataDir);
foreach ($userList as $list) {
if ($list === '.' || $list === '..') continue;
if ($list === '.kara' || $list === '.' || $list === '..') continue;
$file = str_replace('.json', '', $list);
$user = explode('.', $file)[1];
if ($username === $user) return \Result::Success("ユーザー「{$username}」は既に存在します。");
@@ -165,8 +144,86 @@ class Auth {
return \Result::Error("エラー:ユーザー「{$username}」は存在しません。");
}
public function mkUser(?string $username = null, ?string $password = null, ?string $passwordVerify = null, ?string $email = null): \Result {
if (!AUTH_REGISTER_ENABLED) return \Result::Error('ユーザー登録は無効です。');
$resUsr = $this->verifyUsername($username);
$resPwd = $this->verifyPassword($password, $passwordVerify);
$resEml = $this->verifyEmail($email);
$err = '';
if (!$resUsr->isSuccess) $err .= $resUsr->message."<br />";
if (!$resPwd->isSuccess) $err .= $resPwd->message."<br />";
if (!$resEml->isSuccess) $err .= $resEml->message."<br />";
if ($err != '') return \Result::Error($err);
$err = '';
if (!mkdir($this->assDir.$username, 0755)) {
return \Result::Error('エラー:ユーザーのアイコンディレクトリの作成に失敗。');
}
$file = scandir($this->dataDir);
$lastId = 0;
if ($file) {
foreach ($file as $f) {
if ($f === '.kara' || $f === '.' || $f === '..') continue;
$base = substr($f, 0, -5); // .jsonの削除
$dot = strpos($base, '.');
if (!$dot) continue;
$id = substr($base, 0, $dot);
if (ctype_digit($id)) {
if ((int)$id > $lastId) $lastId = $id;
}
}
$lastId++;
}
$user = new \stdClass;
$user->id = $lastId;
$user->username = $username;
$user->password = password_hash($password, $algo);
$user->avatar = '';
$user->email = $email;
$user->regDate = time();
$user->namecolor = '';
$user->displayname = '';
$user->gender = -1;
$user->role = 0;
$user->tokens = [];
$path = "{$this->dataDir}{$lastId}.{$username}.json";
$json = json_encode($user, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
rmdir($this->assDir.$username);
return \Result::Error('エラー:ユーザーデータの保存に失敗。');
}
return \Result::Success();
}
////////////////////
private function verifyLogin(string $username, string $password): \Result {
$userData = $this->getUserData();
if ($username !== $userData->username || !password_verify($password, $userData->password)) {
return \Result::Error('エラー:ユーザー名又はパスワードが一致していません。');
}
if (password_needs_rehash($userData->password, $algo)) {
$userData->password = password_hash($password, $algo);
$path = $this->dataDir.$this->id.'.'.$userData->username.'.json';
$json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
return \Result::Error('エラー:ユーザーデータの保存に失敗。');
}
}
return \Result::Success();
}
private function purgeOldTokens(\stdClass $userData): \stdClass|\Result {
// 有効期限切れたトークンの削除
$out = $userData;
@@ -178,7 +235,7 @@ class Auth {
$json = json_encode($userData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json) === false) {
return \Result::error('エラー:ユーザーデータの保存に失敗。');
return \Result::Error('エラー:ユーザーデータの保存に失敗。');
}
return $out;
@@ -191,7 +248,7 @@ class Auth {
$id = 0;
foreach ($file as $f) {
if ($f === '.' || $f === '..') continue;
if ($f === '.kara' || $f === '.' || $f === '..') continue;
$path = "{$this->dataDir}{$f}";
if (!is_file($path)) continue;
@@ -215,7 +272,7 @@ class Auth {
$matches = [];
foreach ($file as $f) {
if ($f === '.' || $f === '..') continue;
if ($f === '.kara' || $f === '.' || $f === '..') continue;
if (preg_match('/^'.$this->id.'\.(.*?)\.json$/', $f, $matches)) {
$userFile = $matches[0];
break;
@@ -239,41 +296,82 @@ class Auth {
return json_decode($lines);
}
private function isEmailExist(?string $email): \Result {
if (null === $password) return \Result::Error('エラー:パスワードをご入力下さい。');
private function isEmailExist(?string $email = null): \Result {
$userList = scandir($this->dataDir);
$matches = [];
foreach ($userList as $f) {
if ($f === '.kara' || $f === '.' || $f === '..') continue;
$path = "{$this->dataDir}{$f}";
if (!is_file($path)) continue;
$content = file_get_contents($path);
$file = str_replace('.json', '', $f);
if (str_contains($content, '"email": "'.$email.'",')) {
$matches[] = $email;
}
if (count($matches) > 0) return \Result::Error("ユーザー「{$email}」は既に存在します。");
}
return \Result::Success('エラー:メールアドレスが存在しません。');
}
private function verifyEmail(?string $email = null): \Result {
if (null === $email || '' === $email) return \Result::Error('エラー:メールアドレスをご入力下さい。');
if (strpos($email, '@') === false) return \Result::Error('エラー:メールアドレスは不正です。');
$domain = explode('@', $email)[1];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return \Result::Error('エラー:メールアドレスは不正です。');
}
if (!checkdnsrr($domain, 'MX')) {
return \Result::Error('エラー:メールアドレスは不正です。');
}
$res = $this->isEmailExist($email);
if (!$res->isSuccess) return \Result::Error($res->message);
return \Result::Success();
}
private function verifyUsername(?string $username): \Result {
if (null === $username) return \Result::Error('エラー:ユーザー名をご入力下さい。');
if (null === $username || '' === $username) return \Result::Error('エラー:ユーザー名をご入力下さい。');
if (strlen($username) < 6) return \Result::Error('エラーユーザー名は6文字以上をご入力下さい。');
$accepted = 'A-Za-z0-9';
$res = $this->isUserExist($username);
if ($res->isSuccess) return \Result::Error($res->message);
if (preg_match('/[^'.preg_quote($accepted, '/').']/', $password)) {
$accepted = 'A-Za-z0-9';
if (preg_match("/[^{$accepted}]/", $username)) {
return \Result::Error('エラー:ユーザー名に不正な文字が含みます。');
}
return \Result::Success();
}
private function verifyPassword(?string $password): \Result {
private function verifyPassword(?string $password = null, ?string $passwordVerify = null): \Result {
if (null === $password || '' === $password) return \Result::Error('エラー:パスワードをご入力下さい。');
if ($password !== $passwordVerify) return \Result::Error('エラー:パスワードは一致していません。');
$res = $this->checkPasswordStandards($password);
if (!$res->isSuccess) {
return \Result::Error($res->message);
}
return \Result::Success();
}
private function checkPasswordStandards(?string $password): \Result {
if (null === $password) return \Result::Error('エラー:パスワードをご入力下さい。');
if (strlen($password) < $this->minPassLen) return \Result::Error("エラー:パスワード数は{$this->minPassLen}以上をご入力下さい。");
private function checkPasswordStandards(?string $password = null): \Result {
if (strlen($password) < $this->minPassLen) return \Result::Error("エラー:パスワード{$this->minPassLen}以上をご入力下さい。");
$specChar = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~]/';
$accepted = 'A-Za-z0-9'.$specChar;
if (preg_match('/[^'.preg_quote($accepted, '/').']/', $password)) {
if (!\countmatch($password)) {
return \Result::Error('エラー:パスワードに不正な文字が含みます。');
}
$countUpper = preg_match_all('/[A-Z]/', $password) < 2;
$countLower = preg_match_all('/[a-z]/', $password) < 2;
$countDigit = preg_match_all('/[0-9]/', $password) < 2;
$countSymbol = preg_match_all('/['.$specChar.']/', $password) < 2;
$countSymbol = \count_special_chars($password) < 2;
if ($countUpper || $countLower || $countDigit || $countSymbol) {
return \Result::Error('エラーパスワードは2つ以上の大文字、2つ以上の小文字、2つ以上の数字、及び2つ以上の特別文字を含む事が必須です。');