ユーザー
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
data/*.pem
|
data/*.pem
|
||||||
data/*.txt
|
data/*.txt
|
||||||
|
data/user/*.json
|
||||||
log/*.txt
|
log/*.txt
|
||||||
config/config.php
|
config/config.php
|
||||||
public/static/*.pem
|
public/static/*.pem
|
||||||
|
|||||||
@@ -37,4 +37,6 @@ define('RSS_ENABLED', false);
|
|||||||
define('ACTIVITYPUB_ENABLED', false);
|
define('ACTIVITYPUB_ENABLED', false);
|
||||||
define('MYSQL_ENABLED', false);
|
define('MYSQL_ENABLED', false);
|
||||||
define('CSV_ENABLED', false);
|
define('CSV_ENABLED', false);
|
||||||
|
define('AUTH_ENABLED', false);
|
||||||
|
define('AUTH_REGISTER_ENABLED', false);
|
||||||
define('COPYRIGHT_YEAR', '2018-'.date('Y'));
|
define('COPYRIGHT_YEAR', '2018-'.date('Y'));
|
||||||
|
|||||||
0
data/user/.kara
Normal file
0
data/user/.kara
Normal file
@@ -55,6 +55,14 @@ header, main, footer {
|
|||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.errormes {
|
||||||
|
background: #ee4030;
|
||||||
|
color: #fcfcfc;
|
||||||
|
border: 4px inset #b61729;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
h1.paragraph, p.paragraph {
|
h1.paragraph, p.paragraph {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Site\Controller\Fediverse;
|
|||||||
use Site\Controller\Home;
|
use Site\Controller\Home;
|
||||||
use Site\Controller\Notfound;
|
use Site\Controller\Notfound;
|
||||||
use Site\Controller\Page;
|
use Site\Controller\Page;
|
||||||
|
use Site\Controller\User;
|
||||||
|
|
||||||
define('ROOT', realpath(__DIR__));
|
define('ROOT', realpath(__DIR__));
|
||||||
|
|
||||||
@@ -35,6 +36,12 @@ if (ACTIVITYPUB_ENABLED) {
|
|||||||
$routes[] = Route::add('GET', 'ap/actor', Fediverse::class.'@apactor');
|
$routes[] = Route::add('GET', 'ap/actor', Fediverse::class.'@apactor');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AUTH_ENABLED) {
|
||||||
|
$routes[] = Route::add('POST', 'login', User::class.'@login');
|
||||||
|
$routes[] = Route::add('GET', 'login', User::class.'@login');
|
||||||
|
$routes[] = Route::add('GET', 'logout', User::class.'@logout');
|
||||||
|
}
|
||||||
|
|
||||||
/* if (RSS_ENABLED) {} */
|
/* if (RSS_ENABLED) {} */
|
||||||
|
|
||||||
if (ATOM_ENABLED) {
|
if (ATOM_ENABLED) {
|
||||||
|
|||||||
@@ -61,5 +61,4 @@ class Page {
|
|||||||
throw new \Exception($e->getMessage());
|
throw new \Exception($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
76
src/Site/Controller/User.php
Normal file
76
src/Site/Controller/User.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Controller;
|
||||||
|
|
||||||
|
use Site\Controller\Mods;
|
||||||
|
use Site\Lib\Auth;
|
||||||
|
use Site\Lib\Template;
|
||||||
|
|
||||||
|
class User {
|
||||||
|
use Mods;
|
||||||
|
|
||||||
|
public function login(array $params): void {
|
||||||
|
try {
|
||||||
|
$auth = new Auth;
|
||||||
|
$user = $auth->getLoggedInUser();
|
||||||
|
if ($user) {
|
||||||
|
header('Location: /');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$doLogin = count($_POST) > 0;
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($doLogin) {
|
||||||
|
$a = [];
|
||||||
|
if (count($_POST) === 2) {
|
||||||
|
$i = 0;
|
||||||
|
foreach ($_POST as $p) {
|
||||||
|
$a[(int)$i] = $p;
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$auth = new Auth($a[0]);
|
||||||
|
$res = $auth->isUserExist($a[0]);
|
||||||
|
kys($res);
|
||||||
|
if (!$res->isSuccess) {
|
||||||
|
$error = $res->message;
|
||||||
|
} else {
|
||||||
|
$auth->setToken($a[0], $a[1]);
|
||||||
|
header('Location: /');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tmpl = new Template('/');
|
||||||
|
$pagetit = 'サインイン';
|
||||||
|
$description = 'サイトにサインイン';
|
||||||
|
|
||||||
|
$tmpl->assign('pagetit', $pagetit);
|
||||||
|
$tmpl->assign('curPage', 'about');
|
||||||
|
$tmpl->assign('custCss', false);
|
||||||
|
$tmpl->assign('menu', $this->getMenu());
|
||||||
|
$tmpl->assign('description', $description);
|
||||||
|
$tmpl->assign('error', $error);
|
||||||
|
|
||||||
|
$tmpl->render('login');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(array $params): void {
|
||||||
|
try {
|
||||||
|
$auth = new Auth();
|
||||||
|
$user = $auth->getLoggedInUser();
|
||||||
|
if (!$user) {
|
||||||
|
header('Location: /');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth->logout();
|
||||||
|
header('Location: /');
|
||||||
|
exit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
280
src/Site/Lib/Auth.php
Normal file
280
src/Site/Lib/Auth.php
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
<?php
|
||||||
|
namespace Site\Lib;
|
||||||
|
|
||||||
|
class Auth {
|
||||||
|
private int $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性別: -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 int $minPassLen = 20;
|
||||||
|
protected int $tokenDuration = 31536000; // 1年間
|
||||||
|
protected string|int|null $algo = PASSWORD_ARGON2ID;
|
||||||
|
|
||||||
|
public function __construct(?string $username = null) {
|
||||||
|
if (!AUTH_ENABLED) return;
|
||||||
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
|
$this->token = \getcookie('kerozen');
|
||||||
|
$this->id = $this->getUserId($username, $this->token);
|
||||||
|
if ($this->id > 0) $this->user = $this->getUserData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLoggedInUser(): \stdClass|null {
|
||||||
|
if ($this->id === 0) return null;
|
||||||
|
$this->pubUser = $this->user;
|
||||||
|
unset($this->pubUser->password);
|
||||||
|
unset($this->pubUser->tokens);
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
$verify = $this->verifyLogin($username, $password);
|
||||||
|
if (!$verify->isSuccess) {
|
||||||
|
return \Result::error($verify->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'];
|
||||||
|
if (!empty($_SERVER['HTTP_X_FORMARDED_FOR'])) {
|
||||||
|
$ipList = explode(',', $_SERVER['HTTP_X_FORMARDED_FOR']);
|
||||||
|
$ip = trim($ipList[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userData = $this->purgeOldTokens($userData);
|
||||||
|
$expire = 0;
|
||||||
|
$tokenExist = false;
|
||||||
|
|
||||||
|
foreach ($userData->tokens as $t) {
|
||||||
|
if ($t->ip === $ip) {
|
||||||
|
$tokenExist = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$tokenExist) {
|
||||||
|
$token = bin2hex(random_bytes(64));
|
||||||
|
$expire = time() + $this->tokenDuration;
|
||||||
|
$newToken = [
|
||||||
|
'token' => password_hash($token, $algo),
|
||||||
|
'ip' => $ip,
|
||||||
|
'createDate' => time(),
|
||||||
|
'expDate' => $expire,
|
||||||
|
'lastDate' => time(),
|
||||||
|
'userAgent' => $_SERVER['HTTP_USER_AGENT'] ?? '不明'
|
||||||
|
];
|
||||||
|
|
||||||
|
$userData->tokens[] = $newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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('エラー:ユーザーデータの保存に失敗。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$tokenExist) {
|
||||||
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
|
$domain = $_SERVER['SERVER_NAME'];
|
||||||
|
|
||||||
|
if (!setcookie('kerozen', $token, [
|
||||||
|
'expires' => $expire,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => $domain,
|
||||||
|
'secure' => $secure,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict'
|
||||||
|
])) {
|
||||||
|
return \Result::error('エラー:クッキーを設定に失敗。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Result::success('ログイン成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(): string {
|
||||||
|
$userData = $this->getUserData();
|
||||||
|
if (!isset($userData->tokens) || !is_array($userData->tokens)) $userData->tokens = [];
|
||||||
|
$userData = $this->purgeOldTokens($userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(?string $token = null): \Result {
|
||||||
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
|
$domain = $_SERVER['SERVER_NAME'];
|
||||||
|
$userData = $this->getUserData();
|
||||||
|
|
||||||
|
if (!$token) {
|
||||||
|
$userData->tokens = array_filter($userData->tokens, function($t): bool {
|
||||||
|
return ($t->expires ?? 0) > time();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setcookie('kerozen', null, [
|
||||||
|
'expires' => 0,
|
||||||
|
'path' => '/',
|
||||||
|
'domain' => $domain,
|
||||||
|
'secure' => $secure,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'Strict'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return \Result::success();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUserExist(?string $username): \Result {
|
||||||
|
if (null === $username) return \Result::Error('エラー:ユーザー名をご入力下さい。');
|
||||||
|
$userList = scandir($this->dataDir);
|
||||||
|
|
||||||
|
foreach ($userList as $list) {
|
||||||
|
if ($list === '.' || $list === '..') continue;
|
||||||
|
$file = str_replace('.json', '', $list);
|
||||||
|
$user = explode('.', $file)[1];
|
||||||
|
if ($username === $user) return \Result::Success("ユーザー「{$username}」は既に存在します。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Result::Error("エラー:ユーザー「{$username}」は存在しません。");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
private function purgeOldTokens(\stdClass $userData): \stdClass {
|
||||||
|
// 有効期限切れたトークンの削除
|
||||||
|
$out = $userData;
|
||||||
|
$out->tokens = array_filter($userData->tokens, function($t): bool {
|
||||||
|
return ($t->expDate ?? 0) > time();
|
||||||
|
});
|
||||||
|
|
||||||
|
$path = $this->dataDir.$userData->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 $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUserId(?string $username = null, ?string $token): int {
|
||||||
|
if (!$username && !$token) return 0;
|
||||||
|
$file = scandir($this->dataDir);
|
||||||
|
$matches = [];
|
||||||
|
$id = 0;
|
||||||
|
|
||||||
|
foreach ($file as $f) {
|
||||||
|
if ($f === '.' || $f === '..') continue;
|
||||||
|
|
||||||
|
$path = "{$this->dataDir}{$f}";
|
||||||
|
if (!is_file($path)) continue;
|
||||||
|
$content = file_get_contents($path);
|
||||||
|
if (str_contains($content, $token)) {
|
||||||
|
$matches[] = $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$id = (int)preg_replace('/^(.*?)\.(.*?)$/', "$1", str_replace($this->dataDir, '', $matches[0]));
|
||||||
|
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUserData(): \stdClass {
|
||||||
|
$file = scandir($this->dataDir);
|
||||||
|
$userFile = "";
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
foreach ($file as $f) {
|
||||||
|
if ($f === '.' || $f === '..') continue;
|
||||||
|
if (preg_match('/^'.$this->id.'\.(.*?)\.json$/', $f, $matches)) {
|
||||||
|
$userFile = $matches[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$path = $this->dataDir.$userFile;
|
||||||
|
unset($file, $userFile);
|
||||||
|
|
||||||
|
if (!file_exists($path) || !is_readable($path)) {
|
||||||
|
kys('エラー');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fp = fopen($path, 'r');
|
||||||
|
assert_not_null($fp);
|
||||||
|
if (!$fp) kys('ユーザーが存在しない。');
|
||||||
|
$lines = "";
|
||||||
|
while (($buf = fgets($fp, 4096)) !== false) $lines .= $buf.PHP_EOL;
|
||||||
|
if (!feof($fp)) kys("エラー:不明なエラー");
|
||||||
|
fclose($fp);
|
||||||
|
unset($path);
|
||||||
|
return json_decode($lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isEmailExist(?string $email): \Result {
|
||||||
|
if (null === $password) return \Result::Error('エラー:パスワードをご入力下さい。');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyUsername(?string $username): \Result {
|
||||||
|
if (null === $username) return \Result::Error('エラー:ユーザー名をご入力下さい。');
|
||||||
|
if (strlen($username) < 6) return \Result::Error('エラー:ユーザー名は6文字以上をご入力下さい。');
|
||||||
|
|
||||||
|
$accepted = 'A-Za-z0-9';
|
||||||
|
|
||||||
|
if (preg_match('/[^'.preg_quote($accepted, '/').']/', $password)) {
|
||||||
|
return \Result::Error('エラー:ユーザー名に不正な文字が含みます。');
|
||||||
|
}
|
||||||
|
return \Result::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyPassword(?string $password): \Result {
|
||||||
|
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}以上をご入力下さい。");
|
||||||
|
|
||||||
|
$specChar = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~]/';
|
||||||
|
$accepted = 'A-Za-z0-9'.$specChar;
|
||||||
|
|
||||||
|
if (preg_match('/[^'.preg_quote($accepted, '/').']/', $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;
|
||||||
|
|
||||||
|
if ($countUpper || $countLower || $countDigit || $countSymbol) {
|
||||||
|
return \Result::Error('エラー:パスワードは2つ以上の大文字、2つ以上の小文字、2つ以上の数字、及び2つ以上の特別文字を含む事が必須です。');
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Result::Success();
|
||||||
|
}
|
||||||
|
};
|
||||||
52
util.php
52
util.php
@@ -6,6 +6,24 @@ enum LogType {
|
|||||||
case Csv;
|
case Csv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Result {
|
||||||
|
public bool $isSuccess;
|
||||||
|
public ?string $message;
|
||||||
|
|
||||||
|
public function __construct(bool $isSuccess, ?string $message = null) {
|
||||||
|
$this->isSuccess = $isSuccess;
|
||||||
|
$this->message = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function Success(?string $message = null): self {
|
||||||
|
return new self(true, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function Error(string $message): self {
|
||||||
|
return new self(false, $message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function uuid(): string {
|
function uuid(): string {
|
||||||
$data = random_bytes(16);
|
$data = random_bytes(16);
|
||||||
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
||||||
@@ -90,4 +108,38 @@ function to_money($amount, $lang) {
|
|||||||
default:
|
default:
|
||||||
return '¥ '.number_format($amount, 0);
|
return '¥ '.number_format($amount, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randstr(): string {
|
||||||
|
srand(floor(time() / (60*60*24)));
|
||||||
|
$len = rand() % 20;
|
||||||
|
return bin2hex(random_bytes($len / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert_null(mixed $assertion, Throwable|string|null $description = null): bool {
|
||||||
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
||||||
|
if (is_null($assertion)) return true;
|
||||||
|
assert(false, $description ?? 'Assertion failed: value is not null');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert_not_null(mixed $assertion, Throwable|string|null $description = null): bool {
|
||||||
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
||||||
|
if (!is_null($assertion)) return true;
|
||||||
|
assert(false, $description ?? 'Assertion failed: value is null');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert_unless_success(Result $assertion, Throwable|string|null $description = null): bool {
|
||||||
|
if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
|
||||||
|
if ($assertion->isSuccess) return true;
|
||||||
|
assert(false, $description ?? $assertion->message ?? 'Assertion failed: Result is not successful');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AUTH_ENABLED) {
|
||||||
|
function getcookie(string $name): string|null {
|
||||||
|
if (!$_COOKIE[$name]) return null;
|
||||||
|
return $_COOKIE[$name];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,18 @@
|
|||||||
<a class="{{ $m['class'] }}{{ $curPage == $m['page'] ? ' active' : '' }}" href="{{ $m['href'] }}">{{ $m['text'] }}</a>
|
<a class="{{ $m['class'] }}{{ $curPage == $m['page'] ? ' active' : '' }}" href="{{ $m['href'] }}">{{ $m['text'] }}</a>
|
||||||
{@ endif @}
|
{@ endif @}
|
||||||
{@ endforeach @}
|
{@ endforeach @}
|
||||||
|
{@ if (AUTH_ENABLED) @}
|
||||||
|
<div>
|
||||||
|
{@ if (isset($user) && isset($user->id) && $user->id > 0) @}
|
||||||
|
お帰りなしゃ~い、{{ $user->displayname ?? $user->username }}さん! (<a href="/logout">ログアウト</a>)
|
||||||
|
{@ else @}
|
||||||
|
<a href="/login">ログイン</a>
|
||||||
|
{@ if (AUTH_REGISTER_ENABLED) @}
|
||||||
|
| <a href="/register">登録</a>
|
||||||
|
{@ endif @}
|
||||||
|
{@ endif @}
|
||||||
|
</div>
|
||||||
|
{@ endif @}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
37
view/login.maron
Normal file
37
view/login.maron
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{@ include(common/header) @}
|
||||||
|
<h1 class="paragraph">ログイン</h1>
|
||||||
|
<p class="paragraph">
|
||||||
|
ログインして下さい。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{@ if ($error) @}
|
||||||
|
<p>
|
||||||
|
<div class="errormes">
|
||||||
|
{{ $error }}
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
{@ endif @}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<form action="/login" method="POST">
|
||||||
|
{$ $username = randstr() $}
|
||||||
|
{$ $password = randstr() $}
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><label for="{{ $username }}">ユーザー名:</label></td>
|
||||||
|
<td><input type="text" id="{{ $username }}" name="{{ $username }}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="{{ $password }}">パスワード:</label></td>
|
||||||
|
<td><input type="password" id="{{ $password }}" name="{{ $password }}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><button>サインイン</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
{@ include(common/footer) @}
|
||||||
Reference in New Issue
Block a user