Ajaxログイン認証 公開ソースコード
開発/実行環境
- OS
- Windows 7
- サーバー言語
- PHP
- 言語バージョン
- ローカル 5.38
リモート 5.3.15 - 実行環境
- Xampp 1.7.7
- 開発環境
- Dreamweaver CS5.5
- ローカルサーバー
- Apache
- RDBS
- MySQL 5.5.16
ディレクトリ・ファイル構成
root(https://digick-wiz-code.ssl-lolipop.jp/)
│
│
└─dev
│
├─lib
│ jquery
│ bootstrap
│ underscore
│
├─js
│ FSM-0.6.js
│
└─ajax-login
────────────────────────────────
ajax-login
│
│ .htaccess
│
│ config.php
│ functions.php
│
│ index.php
│ login.php
│ logout.php
│ register.php
│
├─css
│ ajax-login.css
│
├─js
│ ajax-login.js
│
└─tpl
admin-frag.inc.php
ajax-login.inc.php
auth-frag.inc.php
データベースの構成
- データベース名
- ajax_login
- エンコーディング/照合順序
- utf8_general_ci
| カラム名 | 種別 | ヌル(NULL) | デフォルト値 | その他 |
|---|---|---|---|---|
| id | varchar(255) | いいえ | なし | 主キー |
| varchar(255) | はい | NULL | ||
| password | varchar(255) | はい | NULL | |
| type | varchar(255) | はい | NULL | |
| create_time | datetime | はい | NULL | |
| update_time | datetime | はい | NULL | |
| last_pass_change_time | datetime | はい | NULL |
ステートマシン図
php.iniの設定(ローカル)
セッション関係
session.use_cookies = 1
session.use_only_cookies = 1
session.use_trans_sid = 0
session.gc_probability = 1
session.gc_divisor = 100
session.gc_maxlifetime = 3600
エンコーディング関係
mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off
mbstring.detect_order = UTF-8,SJIS,EUC-JP,JIS,ASCII
mbstring.substitute_character = none
mbstring.func_overload = 0
mbstring.strict_detection = Off
ソースコード
- config.php
- functions.php
- index.php
- register.php
- login.php
- logout.php
- ajax-login.inc.php
- auth-frag.inc.php
- admin-frag.inc.php
- ajax-login.js
- ajax-login.css
config.php
<?php
$dbname = 'ajax_login';
$host = '127.0.0.1';
$dsn = "mysql:dbname={$dbname};host={$host};charset=utf8";
$username = 'root';
$password = 'wizard1977';
$driver_options = array(
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
);
?>
functions.php
<?php
/* FIXEDSALTに暗号論的擬似乱数生成器関数を使用 */
//echo bin2hex(openssl_random_pseudo_bytes(20));
define('FIXEDSALT', '5dd7599454e8068c0e740710fa50861711c65c94');
define('STRETCH_COUNT', 1000);
function get_salt($id) {
return $id.pack('H*', FIXEDSALT);
}
function get_password_hash($id, $pwd) {
$salt = get_salt($id);
$hash = '';
for ($i = 0; $i < STRETCH_COUNT; $i++) {
$hash = hash('sha256', $hash.$pwd.$salt);
}
return $hash;
}
function json_safe_encode($val) {
return json_encode($val, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
function h($str) {
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
?>
index.php
<?php
require_once './functions.php';
/* クッキーに保存されるセッションIDの設定 */
session_set_cookie_params(60 * 60, '/', '', TRUE, TRUE); //期間は60分。HttpOnlyかつSecure属性はTRUE
session_start();
if (!isset($_SESSION['user_id'])) {
/* ユーザーのログイン/登録手続き用のCSRF対策トークン */
$token = session_id();
/* ログイン/登録画面のコンポーネントを先に変数に入れる */
ob_start();
include_once 'tpl/auth-frag.inc.php';
$template_frag = ob_get_contents();
ob_end_clean();
/* 後からページのコンテナ部分を読み込む */
include_once 'tpl/ajax-login.inc.php';
} else {
/* 認証済みの場合、セッションIDを振り直す */
session_regenerate_id(TRUE);
$user_id = $_SESSION['user_id'];
$email = $_SESSION['email'];
/* ユーザーの何らかの手続き用のCSRF対策トークン */
$token = session_id();
/* ユーザー管理画面のコンポーネントを変数に入れる */
ob_start();
include_once 'tpl/admin-frag.inc.php';
$template_frag = ob_get_contents();
ob_end_clean();
/* 後からページのコンテナ部分を読み込む */
include_once 'tpl/ajax-login.inc.php';
}
?>
register.php
<?php
require_once './config.php';
require_once './functions.php';
/* クッキーに保存されるセッションIDの設定 */
session_set_cookie_params(60 * 60, '/', '', TRUE, TRUE);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
$response = array();
session_start();
/* セッションがタイムアウトしていたら手続きを終了 */
if (!isset($_COOKIE[session_name()])) {
$response['message'] = 'expired';
echo json_safe_encode($response);
exit;
}
/* Ajax以外のリクエストだったら強制終了 */
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
$response['message'] = 'not XHR';
echo json_safe_encode($response);
exit;
}
$user_id = $_POST['user-id'];
$email = $_POST['email'];
$pass = $_POST['password'];
$regi_token = $_POST['regi-token'];
/* トークンが異なっていたら強制終了 */
if (session_id() !== $regi_token) {
$response['message'] = 'illicit access';
echo json_safe_encode($response);
exit;
}
/* サーバーサイドでも入力値の検証を行う */
if (preg_match('/\A[a-zA-Z0-9_]{4,20}\z/u', $user_id) !== 1) {
$response['message'] = 'invalid userId';
echo json_safe_encode($response);
exit;
}
if (preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/ui', $email) !== 1) {
$response['message'] = 'invalid email';
echo json_safe_encode($response);
exit;
}
if (preg_match('/\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,16}+\z/u', $pass) !== 1) {
$response['message'] = 'invalid password';
echo json_safe_encode($response);
exit;
}
try {
/* データベース接続 */
$pdo = new PDO($dsn, $username, $password, $driver_options);
$sql = "SELECT * FROM account WHERE id=:id";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':id', $user_id, PDO::PARAM_STR);
$stmt->execute();
$user_id_duplicated = $stmt->fetch();
$sql = "SELECT * FROM account WHERE email=:email";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->execute();
$email_duplicated = $stmt->fetch();
if (empty($user_id_duplicated) && empty($email_duplicated)) {
/* 重複登録がないことを確認。登録処理へ */
$time = date('Y-m-d H:i:s');
$hash = get_password_hash($user_id, $pass);
$sql = "INSERT INTO account (id, email, password, type, create_time, update_time, last_pass_change_time) VALUES(:id, :email, :pass, :type, :create_time, :update_time, :last_pass_change_time)";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':id', $user_id, PDO::PARAM_STR);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->bindParam(':pass', $hash, PDO::PARAM_STR);
$stmt->bindValue(':type', 'general', PDO::PARAM_STR);
$stmt->bindParam(':create_time', $time, PDO::PARAM_STR);
$stmt->bindParam(':update_time', $time, PDO::PARAM_STR);
$stmt->bindParam(':last_pass_change_time', $time, PDO::PARAM_STR);
$stmt->execute();
/* 認証状態に移行 */
session_regenerate_id(TRUE);
$_SESSION['user_id'] = $user_id;
$_SESSION['email'] = $email;
/* ユーザーが何らかの手続きを行うときのCSRF対策トークン */
$token = session_id();
/* レスポンスをJSON形式で作成 */
$response['message'] = 'success';
$response['id'] = h($user_id);
/* 認証後のページのHTMLデータを変数に入れる */
ob_start();
include_once 'tpl/admin-frag.inc.php';
$template_frag = ob_get_contents();
ob_end_clean();
$response['html'] = $template_frag;
} else {
if (!empty($user_id_duplicated)) {
$response['message'] = 'username duplicated';
} else {
$response['message'] = 'email duplicated';
}
}
echo json_safe_encode($response);
} catch (PDOException $e) {
$response['message'] = $e->getMessage();
echo json_safe_encode($response);
exit;
}
?>
login.php
<?php
require_once './config.php';
require_once './functions.php';
/* クッキーに保存されるセッションIDの設定 */
session_set_cookie_params(60 * 60, '/', '', TRUE, TRUE);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
$response = array();
session_start();
/* セッションがタイムアウトしていたら手続きを終了 */
if (!isset($_COOKIE[session_name()])) {
$response['message'] = 'expired';
echo json_safe_encode($response);
exit;
}
/* Ajax以外のリクエストだったら強制終了 */
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
$response['message'] = 'not XHR';
echo json_safe_encode($response);
exit;
}
$email = $_POST['email'];
$pass = $_POST['password'];
$login_token = $_POST['login-token'];
/* トークンが異なっていたら強制終了 */
if (session_id() !== $login_token) {
$response['message'] = 'illicit access';
echo json_safe_encode($response);
exit;
}
/* サーバーサイドでも入力値の検証を行う */
if (preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/ui', $email) !== 1) {
$response['message'] = 'invalid email';
echo json_safe_encode($response);
exit;
}
if (preg_match('/\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,16}+\z/u', $pass) !== 1) {
$response['message'] = 'invalid password';
echo json_safe_encode($response);
exit;
}
try {
$pdo = new PDO($dsn, $username, $password, $driver_options);
$sql = "SELECT * FROM account WHERE email=:email";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':email', $email, PDO::PARAM_STR);
$stmt->execute();
$account = $stmt->fetch();
if (!empty($account)) {
$hash = get_password_hash($account['id'], $pass);
if ($account['password'] === $hash) {
$time = date('Y-m-d H:i:s');
$user_id = $account['id'];
$sql = "UPDATE account SET update_time=:update_time WHERE id=:id";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':update_time', $time, PDO::PARAM_STR);
$stmt->bindParam(':id', $user_id, PDO::PARAM_STR);
$stmt->execute();
/* 認証状態に移行 */
session_regenerate_id(TRUE);
$_SESSION['user_id'] = $user_id;
$_SESSION['email'] = $email;
/* ユーザーが何らかの手続きを行うときのCSRF対策トークン */
$token = session_id();
/* レスポンスをJSON形式で作成 */
$response['message'] = 'success';
$response['id'] = h($user_id);
/* 認証後のページのHTMLデータを変数に入れる */
ob_start();
include_once 'tpl/admin-frag.inc.php';
$template_frag = ob_get_contents();
ob_end_clean();
$response['html'] = $template_frag;
} else {
$response['message'] = 'failure';
}
} else {
$response['message'] = 'failure';
}
echo json_safe_encode($response);
} catch (PDOException $e) {
$response['message'] = $e->getMessage();
echo json_safe_encode($response);
exit;
}
?>
logout.php
<?php
require_once './functions.php';
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
$response = array();
if (@$_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
$response['message'] = 'not XHR';
echo json_safe_encode($response);
die();
}
$logout_token = $_POST['logout-token'];
session_start();
/* トークンが異なっていたら強制終了 */
if (session_id() !== $logout_token) {
$response['message'] = 'illicit access';
echo json_safe_encode($response);
die();
}
$response['message'] = 'success';
/* 念のためセッションIDを格納しているCookieを削除 */
setcookie(session_name(), '', time() - 3600, '/');
/* セッションデータを削除し、セッションを終了する */
$_SESSION = array();
session_destroy();
echo json_safe_encode($response);
?>
ajax-login.inc.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="Description" content="JavaScriptやAjaxライブラリを利用したゲームなどを公開しています。">
<meta name="Keywords" content="Ajax,JavaScript,HTML5,jQuery,Bootstrap,ゲーム">
<meta name="author" content="masa">
<meta name="copyright" content="Copyright © 2009 WIZARD-CODE">
<title>Ajaxログイン認証 - ウィザード・コード | WIZARD-CODE</title>
<link rel="shortcut icon" href="//wiz-code.digick.jp/image/icon/favicon.ico">
<link rel="copyright" href="//wiz-code.digick.jp/">
<link href="/lib/bootstrap/3.3.1/css/bootstrap.css" rel="stylesheet">
<link href="/test/ajax-login/css/ajax-login.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div id="ajax-login" class="panel panel-default"><?=$template_frag?></div>
</div>
<script src="/lib/jquery/jquery-1.10.1.js"></script>
<script src="/lib/bootstrap/3.3.1/js/bootstrap.js"></script>
<script src="/lib/underscore/1.7.0/underscore.js"></script>
<script src="/js/FSM-0.6.js"></script>
<script src="/test/ajax-login/js/ajax-login.js"></script>
</body>
</html>
auth-frag.inc.php
<div id="content" class="panel-body" data-authentication-status="unauthenticated">
<h1 id="title" class="text-center">Ajaxログイン認証</h1>
<div id="auth" class="text-center hidden">
<button id="register-button" type="button" class="btn btn-primary">登録する</button>
<button id="login-button" type="button" class="btn btn-default">ログインする</button>
</div>
<div id="return-home-button" class="hidden"><button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-home"></span></button></div>
<div id="validation" class="text-danger bg-danger hidden"></div>
<div id="register" class="hidden">
<form class="form-horizontal">
<fieldset>
<div class="form-group form-group-xs">
<label for="user-id" class="col-xs-offset-1 col-xs-4 control-label">登録ユーザー名</label>
<div class="col-xs-4">
<input type="text" class="form-control user-id" name="user-id" placeholder="登録ユーザー名">
</div>
</div>
<div class="form-group form-group-xs">
<label for="email" class="col-xs-offset-1 col-xs-4 control-label">登録Eメール</label>
<div class="col-xs-4">
<input type="email" class="form-control email" name="email" placeholder="登録Eメール">
</div>
</div>
<div class="form-group form-group-xs">
<label for="password" class="col-xs-offset-1 col-xs-4 control-label">登録パスワード</label>
<div class="col-xs-4">
<input type="password" class="form-control password" name="password" placeholder="登録パスワード">
</div>
</div>
<input id="regi-token" name="regi-token" value="<?=h($token)?>" type="hidden">
<div class="form-group form-group-xs">
<div class="col-xs-offset-5 col-xs-4">
<button type="submit" class="btn btn-primary btn-sm">登録する</button>
</div>
</div>
</fieldset>
</form>
</div>
<div id="login" class="hidden">
<div class="row">
<div class="col-xs-offset-4 col-xs-4">
<form>
<fieldset>
<div class="form-group form-group-xs">
<label for="email">Eメール</label>
<input type="email" class="form-control email" name="email" placeholder="Eメール">
</div>
<div class="form-group form-group-xs">
<label for="password">パスワード</label>
<input type="password" class="form-control password" name="password" placeholder="パスワード">
</div>
<input id="login-token" name="login-token" value="<?=h($token)?>" type="hidden">
<button type="submit" class="btn btn-primary btn-sm">サインイン</button>
</fieldset>
</form>
</div>
</div>
</div>
<div id="credit" class="text-center">
<address>Copyright © WIZARD-CODE 2015</address>
</div>
</div>
admin-frag.inc.php
<div id="content" class="panel-body" data-authentication-status="authenticated">
<h1 id="title" class="text-center">こんにちは! <?=h($user_id)?>さん!</h1>
<div id="validation" class="text-danger bg-danger hidden"></div>
<div id="return-home-button" class="hidden"><button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-home"></span></button></div>
<div id="menu" class="hidden">
<button id="chat" type="button" class="btn btn-default btn-block disabled">チャットする</button>
<button id="find-friends" type="button" class="btn btn-default btn-block disabled">フレンドを見つける</button>
<button id="setting-button" type="button" class="btn btn-default btn-block">設定</button>
<button id="logout-button" type="button" class="btn btn-default btn-block">ログアウトする</button>
</div>
<div id="logout" class="hidden">
<div class="alert alert-warning text-center" role="alert">
<h4 class="text-center">ログアウトの確認</h4>
<p>「ログアウトする」をクリックするとログアウト処理が完了します。</p>
<form>
<input id="logout-token" name="logout-token" value="<?=h($token)?>" type="hidden">
<button id="confirm-logout-button" type="button" class="btn btn-warning">ログアウトする</button>
<button id="return-menu-from-logout" type="button" class="btn btn-default">戻る</button>
</form>
</div>
</div>
<div id="setting" class="hidden">
<form>
<fieldset>
<input id="setting-token" name="setting-token" value="<?=h($token)?>" type="hidden">
<table class="table table-condensed">
<tbody>
<tr>
<th class="col-md-4">ユーザー名</th>
<td id="user-name" colspan="2" class="col-md-8"><?=h($user_id)?></td>
</tr>
<tr>
<th class="col-md-4">Eメールアドレス</th>
<td id="email" class="col-md-6"><?=h($email)?></td>
<td class="col-md-2"><button id="change-email" type="button" class="btn btn-success btn-sm">変更</button></td>
</tr>
<tr class="hidden">
<th>Eメール変更</th>
<td class="text-right">
<label class="sr-only" for="new-email">Eメール</label>
<input type="email" class="form-control input-sm" id="new-email" name="new-email" placeholder="Eメール">
</td>
<td>
<button type="submit" class="btn btn-primary btn-sm">送信</button>
</td>
</tr>
<tr>
<th colspan="2">パスワード変更</th>
<td><button id="change-email" type="button" class="btn btn-success btn-sm">変更</button></td>
</tr>
<tr class="hidden">
<th>パスワード変更</th>
<td>
<label class="sr-only" for="new-password">新しいパスワード</label>
<input type="email" class="form-control input-sm" id="new-password" name="new-password" placeholder="新しいパスワード">
</td>
<td></td>
</tr>
<tr class="hidden">
<th class="delete-border-top"></th>
<td class="delete-border-top">
<label class="sr-only" for="confirm-password">パスワードの再入力</label>
<input type="email" class="form-control input-sm" id="confirm-password" placeholder="パスワードの再入力">
</td>
<td class="delete-border-top">
<button type="submit" class="btn btn-primary btn-sm">送信</button>
</td>
</tr>
<tr>
<th colspan="2" class="insert-border-bottom">アカウント管理</th>
<td class="insert-border-bottom"><button id="delete-account" type="button" class="btn btn-warning btn-sm">削除</button></td>
</tr>
<tr class="danger hidden">
<th>アカウントの削除</th>
<td>アカウントを削除します。よろしいですか?</td>
<td>
<button type="submit" class="btn btn-danger btn-sm">送信</button>
</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</div>
<div id="credit" class="text-center">
<address>Copyright © WIZARD-CODE 2015</address>
</div>
</div>
ajax-login.js
(function () {
'use strict';
var fsm,
state,
transit,
user,
compo,
button,
message,
FORM_FADE_SPEED;
/* ユーザーデータを集約するオブジェクト */
user = {};
/* コンポーネント要素を集約するオブジェクト */
compo = {};
/* ボタン要素を集約するオブジェクト */
button = {};
/* メッセージ表示要素を集約するオブジェクト */
message = {};
/* 画面の表示アニメーション速度 */
FORM_FADE_SPEED = 200;
/* Ajaxログインシステム */
fsm = {};
state = {};
transit = {};
fsm.ajaxLogin = new FSM('ajax-login');
state.branch = new State('branch');
fsm.ajaxLogin.addStateAsChoicePseudo(state.branch, function () {
/* アプリケーション画面のフレーム */
compo.ajaxLogin = $('#ajax-login');
user.authentication = $('#content').attr('data-authentication-status');
if (user.authentication !== 'authenticated') {
return 'auth-init';
} else {
return 'admin-init';
}
});
transit.firstTransit = new Transition('first-transit', null, 'branch');
fsm.ajaxLogin.addTransition(transit.firstTransit);
/* ログイン/登録に関するステート群 */
state.authInit = new State('auth-init', {
autoTransition: true,
entryAction: function () {
/* コンポーネント要素 */
compo.auth = $('#auth');
compo.register = $('#register');
compo.login = $('#login');
/* バリデーションのテキスト表示用要素 */
message.validation = $('#validation');
/* 最初の画面に戻るためのボタン */
button.returnHome = $('#return-home-button');
/* いったんボタンやコンポーネントを非表示に */
compo.auth.hide().removeClass('hidden');
compo.register.hide().removeClass('hidden');
compo.login.hide().removeClass('hidden');
message.validation.hide().removeClass('hidden');
button.returnHome.hide().removeClass('hidden');
}
});
fsm.ajaxLogin.addState(state.authInit);
/* 認証画面の設定 */
state.auth = new State('auth', {
entryAction: function () {
/* 認証画面を表示 */
compo.auth.show();
/* ボタンごとに遷移先を指定する */
compo.auth.find('button')
.on('click', function (e) {
var target;
e.preventDefault();
e.stopPropagation();
$(this).blur();
target = e.target;
if (target.id === 'login-button') {
transit.authToLogin.trigger();
} else if (target.id === 'register-button') {
transit.authToRegister.trigger();
}
});
},
exitAction: function () {
/* 画面遷移の際にイベントリスナーを削除 */
compo.auth.find('button').off('click').end().hide();
}
});
fsm.ajaxLogin.addState(state.auth);
transit.authInitToAuth = new Transition('auth-init-to-auth', 'auth-init', 'auth');
fsm.ajaxLogin.addTransition(transit.authInitToAuth);
/* 登録画面の設定 */
state.register = new State('register', {
entryAction: function () {
/* 登録画面を表示 */
compo.register.show();
/* 最初の画面へ戻るボタンを表示 */
button.returnHome.show()
.on('click', function (e) {
/* 登録画面から最初の画面へ戻る */
transit.registerToAuth.trigger();
});
/* 「登録する」ボタンを押した際の処理 */
compo.register.find('button').on('click', function (e) {
var form, userId, email, password, query;
e.preventDefault();
e.stopPropagation();
$(this).blur();
form = compo.register.find('form');
userId = form.find('.user-id');
email = form.find('.email');
password = form.find('.password');
message.validation.text('').hide();
/* 入力値をトリミングする */
userId.val($.trim(userId.val()));
email.val($.trim(email.val()));
password.val($.trim(password.val()));
/* ユーザー名のバリデーション */
if (!/^[a-zA-Z0-9_]{4,20}$/.test(userId.val())) {
userId.val('');
message.validation.text('ユーザー名は半角英数字かアンダースコア(_)のみ使用できます。4~20文字の範囲で入力してください。').show(FORM_FADE_SPEED);
return;
}
/* メールアドレスのバリデーション */
if (!/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/i.test(email.val())) {
email.val('');
message.validation.text('メールアドレスが正しくありません。').show(FORM_FADE_SPEED);
return;
}
/* パスワードのバリデーション */
if (!/^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,16}$/.test(password.val())) {
password.val('');
message.validation.text('パスワードは半角英数字(大文字/小文字/数字)をそれぞれ1文字以上使用し、8~16文字の範囲で入力してください。').show(FORM_FADE_SPEED);
return;
}
/* フォームが空でないか */
if (userId.val() === '' || email.val() === '' || password.val() === '') {
message.validation.text('フォームが空です。').show(FORM_FADE_SPEED);
return;
}
/* 入力内容をシリアライズ */
query = form.serialize();
/* パスワード入力文字をクリア */
password.val('');
/* 誤動作防止のため、送信ボタンを無効化 */
form.find('fieldset').prop('disabled', true);
/* 入力内容をAjax送信 */
$.post(
'./register.php',
query,
function (json) {
var html;
if (json && json.message) {
/* レスポンスを受け取ったら、フォームの無効化を解除 */
form.find('fieldset').prop('disabled', false);
switch (json.message) {
/* 登録処理が成功した時 */
case 'success':
user.id = json.id;
html = $.parseHTML(json.html, false);
userId.val('');
email.val('');
/* ユーザー画面に切り替え */
compo.ajaxLogin.empty().append(html);
/* マイページへ遷移 */
transit.registerToAdminInit.trigger();
break;
/* 不正なユーザー名 */
case 'invalid userId':
message.validation.text('ユーザー名は半角英数字かアンダースコア(_)のみ使用できます。4~20文字の範囲で入力してください。').show(FORM_FADE_SPEED);
break;
/* 不正なメールアドレス */
case 'invalid email':
message.validation.text('メールアドレスが正しくありません。').show(FORM_FADE_SPEED);
break;
/* 不正なパスワード */
case 'invalid password':
message.validation.text('パスワードは半角英数字(大文字/小文字/数字)をそれぞれ1文字以上使用し、8~16文字の範囲で入力してください。').show(FORM_FADE_SPEED);
break;
/* 登録ユーザー名の重複 */
case 'userId duplicated':
message.validation.text('登録済みのユーザー名です。').show(FORM_FADE_SPEED);
break;
/* 登録メールアドレスの重複 */
case 'email duplicated':
message.validation.text('登録済みのメールアドレスです。').show(FORM_FADE_SPEED);
break;
/* 入力時間がタイムオーバー */
case 'expired':
message.validation.text('登録期限が過ぎています。リロードを行ってください。').show(FORM_FADE_SPEED);
break;
/* 不正なアクセス、その他のエラー */
case 'illicit access':
case 'not XHR':
case 'db error':
message.validation.text('不明なエラーが発生しました。リロードを行ってください。').show(FORM_FADE_SPEED);
break;
}
} else {
message.validation.text('ユーザー登録に失敗しました。リロードを行ってください。').show(FORM_FADE_SPEED);
}
},
'json'
);
});
},
exitAction: function () {
/* 「ホーム」ボタンと登録画面を隠す */
button.returnHome.off('click').hide();
compo.register.find('button').off('click').end().hide();
/* バリデーション用テキストを空に */
message.validation.text('').hide();
}
});
fsm.ajaxLogin.addState(state.register);
transit.authToRegister = new Transition('auth-to-register', 'auth', 'register');
transit.registerToAuth = new Transition('register-to-auth', 'register', 'auth');
transit.registerToAdminInit = new Transition('register-to-admin-init', 'register', 'admin-init');
fsm.ajaxLogin.addTransition(transit.authToRegister);
fsm.ajaxLogin.addTransition(transit.registerToAuth);
fsm.ajaxLogin.addTransition(transit.registerToAdminInit);
/* ログイン画面の設定 */
state.login = new State('login', {
entryAction: function () {
/* ログイン画面を表示 */
compo.login.show();
/* 最初の画面へ戻るボタンを表示 */
button.returnHome.show()
.on('click', function (e) {
/* ログイン画面から最初の画面へ戻る */
transit.loginToAuth.trigger();
});
/* 「サインイン」ボタンを押した際の処理 */
compo.login.find('button').on('click', function (e) {
var form, email, password, query, sha, hash;
e.preventDefault();
e.stopPropagation();
$(this).blur();
form = compo.login.find('form');
email = form.find('.email');
password = form.find('.password');
message.validation.text('').hide();
/* 入力値をトリミングする */
email.val($.trim(email.val()));
password.val($.trim(password.val()));
/* メールアドレスのバリデーション */
if (!/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/i.test($.trim(email.val()))) {
email.val('');
message.validation.text('メールアドレスが正しくありません。').show(FORM_FADE_SPEED);
return;
}
/* パスワードのバリデーション */
if (!/^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,16}$/.test(password.val())) {
password.val('');
message.validation.text('パスワードは半角英数字(大文字/小文字/数字)をそれぞれ1文字以上使用し、8~16文字の範囲で入力してください。').show(FORM_FADE_SPEED);
return;
}
/* フォームが空でないか */
if (email.val() === '' || password.val() === '') {
message.validation.text('フォームが空です。').show(FORM_FADE_SPEED);
return;
}
query = form.serialize();
password.val('');
form.find('fieldset').prop('disabled', true);
/* ログイン処理をするPHPファイルにXHRリクエスト */
$.post(
'./login.php',
query,
function (json) {
var html;
if (json && json.message) {
form.find('fieldset').prop('disabled', false);
switch (json.message) {
/* 登録処理が成功した時 */
case 'success':
user.id = json.id;
html = $.parseHTML(json.html, false);
email.val('');
/* ユーザー画面に切り替え */
compo.ajaxLogin.empty().append(html);
/* マイページへ遷移 */
transit.loginToAdminInit.trigger();
break;
/* 不正なメールアドレス */
case 'invalid email':
message.validation.text('メールアドレスが正しくありません。').show(FORM_FADE_SPEED);
break;
/* 不正なパスワード */
case 'invalid password':
message.validation.text('パスワードは半角英数字(大文字/小文字/数字)をそれぞれ1文字以上使用し、8~16文字の範囲で入力してください。').show(FORM_FADE_SPEED);
break;
/* メールアドレスかパスワードが不正 */
case 'failure':
message.validation.text('メールアドレスまたはパスワードが正しくありません。').show(FORM_FADE_SPEED);
break;
/* 入力時間がタイムオーバー */
case 'expired':
message.validation.text('ログイン期限が過ぎています。リロードを行ってください。').show(FORM_FADE_SPEED);
break;
/* 不正なアクセスやその他のエラー */
case 'illicit access':
case 'not XHR':
case 'db error':
message.validation.text('不明なエラーが発生しました。リロードを行ってください。').show(FORM_FADE_SPEED);
break
}
} else {
message.validation.text('ログイン認証に失敗しました。リロードを行ってください。').show(FORM_FADE_SPEED);
}
},
'json'
);
});
},
exitAction: function () {
/* 「ホーム」ボタンと登録画面を隠す */
button.returnHome.off('click').hide();
compo.login.find('button').off('click').end().hide();
/* バリデーション用テキストを空に */
message.validation.text('').hide();
}
});
fsm.ajaxLogin.addState(state.login);
transit.authToLogin = new Transition('auth-to-login', 'auth', 'login');
transit.loginToAuth = new Transition('login-to-auth', 'login', 'auth');
transit.loginToAdminInit = new Transition('login-to-admin-init', 'login', 'admin-init');
fsm.ajaxLogin.addTransition(transit.authToLogin);
fsm.ajaxLogin.addTransition(transit.loginToAuth);
fsm.ajaxLogin.addTransition(transit.loginToAdminInit);
/* 認証後の機能ステート群 */
state.adminInit = new State('admin-init', {
autoTransition: true,
entryAction: function () {
/* ボタンやコンポーネント要素 */
compo.menu = $('#menu');
compo.setting = $('#setting');
compo.logout = $('#logout');
/* バリデーションのテキスト表示用要素 */
message.validation = $('#validation');
/* 最初の画面に戻るためのボタン */
button.returnHome = $('#return-home-button');
button.setting = $('#setting-button');
button.logout = $('#logout-button');
/* いったんコンポーネントを非表示に */
message.validation.hide().removeClass('hidden');
button.returnHome.hide().removeClass('hidden');
compo.menu.hide().removeClass('hidden');
compo.setting.hide().removeClass('hidden');
compo.logout.hide().removeClass('hidden');
}
});
fsm.ajaxLogin.addState(state.adminInit);
state.menu = new State('menu', {
entryAction: function () {
compo.menu.show();
compo.menu.find('button')
.on('click', function (e) {
var target;
e.preventDefault();
e.stopPropagation();
target = e.target;
if (target.id === 'setting-button') {
transit.menuToSetting.trigger();
} else if (target.id === 'logout-button') {
transit.menuToLogout.trigger();
}
});
},
exitAction: function () {
/* 画面遷移の際にイベントリスナーを削除 */
compo.menu.find('button').off('click').end().hide();
}
});
fsm.ajaxLogin.addState(state.menu);
transit.adminInitToMenu = new Transition('admin-init-to-menu', 'admin-init', 'menu');
fsm.ajaxLogin.addTransition(transit.adminInitToMenu);
state.logout = new State('logout', {
entryAction: function () {
var form, buttons;
/* ログアウト画面を表示 */
compo.logout.show();
form = compo.logout.find('form');
buttons = form.find('button');
buttons.on('click', function (e) {
var target, query;
e.preventDefault();
e.stopPropagation();
buttons.blur();
target = e.target;
if (target.id === 'confirm-logout-button') {
query = form.serialize();
buttons.addClass('disabled');
$.post(
'./logout.php',
query,
function (json) {
if (json && json.message) {
switch (json.message) {
/* ログアウト処理が成功した時 */
case 'success':
window.location.href = './';
break;
/* 処理が失敗した時 */
case 'illicit access':
case 'not XHR':
buttons.removeClass('disabled');
message.validation.text('不正なアクセスを検知しました。操作をやり直してください。').show(FORM_FADE_SPEED);
break;
}
}
},
'json'
);
} else if (target.id === 'return-menu-from-logout') {
transit.logoutToMenu.trigger();
}
});
},
exitAction: function () {
compo.logout.find('button').off('click').end().hide();
/* バリデーション用テキストを空に */
message.validation.text('').hide();
}
});
fsm.ajaxLogin.addState(state.logout);
transit.menuToLogout = new Transition('menu-to-logout', 'menu', 'logout');
transit.logoutToMenu = new Transition('logout-to-menu', 'logout', 'menu');
fsm.ajaxLogin.addTransition(transit.menuToLogout);
fsm.ajaxLogin.addTransition(transit.logoutToMenu);
state.setting = new State('setting', {
entryAction: function () {
/* 設定画面を表示 */
compo.setting.show();
button.returnHome.show();
},
exitAction: function () {
}
});
fsm.ajaxLogin.addState(state.setting);
transit.menuToSetting = new Transition('menu-to-setting', 'menu', 'setting');
transit.settingToMenu = new Transition('setting-to-menu', 'setting', 'menu');
fsm.ajaxLogin.addTransition(transit.menuToSetting);
fsm.ajaxLogin.addTransition(transit.settingToMenu);
/* ステートマシンを起動する */
fsm.ajaxLogin.start();
}());
ajax-login.css
@charset "UTF-8";
.container-fluid {
padding: 15px;
max-width: 768px;
max-height: 576px;
height: 100vh;
min-width: 480px;
min-height: 360px;
}
.container-fluid > #ajax-login {
margin-bottom: 0;
height: 100%;
}
#content {
height: 100%;
position: relative;
}
#title {
margin: auto;
color: #333px;
font-size: 60px;
position: absolute;
top: 25%;
left: 0;
right: 0;
text-shadow: 4px 2px 2px #05F;
}
#return-home-button {
position: absolute;
right: 10px;
top: 10px;
}
#return-home-button > button {
padding-top: 4px;
}
#return-home-button span {
font-size: 18px;
color: #777;
}
#auth {
margin: auto;
position: absolute;
top: 50%;
left: 0;
right: 0;
}
#auth > button {
margin-right: 10px;
}
#register {
margin: auto;
position: absolute;
top: 45%;
left: 0;
right: 0;
}
#login {
margin: auto;
position: absolute;
top: 45%;
left: 0;
right: 0;
}
#validation {
margin: auto;
padding: 0 10px;
width: 360px;
position: absolute;
top: 15%;
left: 0;
right: 0;
}
#validation p {
margin-top: 2px;
padding: 2px;
}
#menu {
margin: auto;
width: 300px;
position: absolute;
top: 50%;
left: 0;
right: 0;
}
#menu > button {
width: 100%;
white-space: normal;
}
#setting {
margin: auto;
width: 65%;
position: absolute;
top: 50%;
left: 0;
right: 0;
}
.delete-border-top {
border-top: none !important;
}
.insert-border-bottom {
border-bottom: 1px solid #ddd !important;
}
#logout {
margin: auto;
width: 70%;
position: absolute;
top: 55%;
left: 0;
right: 0;
}
#logout form {
margin-top: 10px;
}
#credit {
margin: auto;
font-size: 12px;
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
#credit address {
margin-bottom: 4px;
}
@media screen and (max-width: 768px) {
#title {
font-size: 50px;
}
}
@media screen and (max-height: 576px) {
#title {
top: 15%;
}
#register {
top: 35%;
}
#login {
top: 35%;
}
#menu {
top: 40%;
}
#setting {
top: 35%;
}
}