Skip to content

Instantly share code, notes, and snippets.

@Go-Noji
Last active May 21, 2018 03:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Go-Noji/096bca1c663b1fc5e6fad9ea1873bf91 to your computer and use it in GitHub Desktop.
Save Go-Noji/096bca1c663b1fc5e6fad9ea1873bf91 to your computer and use it in GitHub Desktop.
CodeIgniterで時間制限付きメール認証機能を作る ref: https://qiita.com/Go-Noji/items/6a8e09e66b3f4857266e
//~略
$config['salt'] = 'h6F4htejW69moG6gndjplC12';
$config['auth_time'] = 3600;
CREATE TABLE `mailauth`.`user`
(
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'AI' ,
`mail` VARCHAR(255) NOT NULL COMMENT 'メールアドレス' ,
`password` VARCHAR(64) NOT NULL COMMENT 'パスワード(sha256)' ,
`name` VARCHAR(255) NOT NULL COMMENT 'ユーザー名' ,
`status` INT(1) NOT NULL COMMENT '0:仮登録, 1は通常' ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT = 'ユーザー';
<?php
//このように編集しておくと複数の状況で各設定を使いまわせます
$mail = array(
'field' => 'mail',
'label' => 'メールアドレス',
'rules' => 'required|valid_email|is_unique[user.mail]',
'errors' => array(
'required' => '{field} は必須です',
'valid_email' => '{field} がメールアドレスとして正しくありません',
'is_unique' => '{field} は既に存在しています'
)
);
$password = array(
'field' => 'password',
'label' => 'パスワード',
'rules' => array(
'required',
array(
'isPassword',
function ($password)
{
if($password === '')
{
return TRUE;
}
if(preg_match('/\A(?=.*?[a-z])(?=.*?\d)[!-~]{8,100}+\z/i', $password))
{
return TRUE;
}
return FALSE;
}
)
),
'errors' => array(
'required' => '{field} は必須です',
'isPassword' => '{field} は8文字以上100文字以下の英数字記号で入力してください'
)
);
$passconf = array(
'field' => 'passconf',
'label' => 'パスワード(再入力)',
'rules' => 'matches[password]',
'errors' => array(
'matches' => '{field} が一致しません'
)
);
$name = array(
'field' => 'name',
'label' => 'ユーザーネーム',
'rules' => 'required|max_length[128]',
'errors' => array(
'required' => '{field} は必須です',
'max_length' => '{field} は128文字以下で入力してください'
)
);
//この名前で設定しておけばRegisterクラスのindexメソッドで勝手に呼び出されるようになります
$config['register/index'] = array($mail, $password, $passconf, $name);
<?php
//このように編集しておくと複数の状況で各設定を使いまわせます
$mail = array(
'field' => 'mail',
'label' => 'メールアドレス',
'rules' => 'required|valid_email|is_unique[user.mail]',
'errors' => array(
'required' => '{field} は必須です',
'valid_email' => '{field} がメールアドレスとして正しくありません',
'is_unique' => '{field} は既に存在しています'
)
);
$password = array(
'field' => 'password',
'label' => 'パスワード',
'rules' => array(
'required',
array(
'isPassword',
function ($password)
{
if($password === '')
{
return TRUE;
}
if(preg_match('/\A(?=.*?[a-z])(?=.*?\d)[!-~]{8,100}+\z/i', $password))
{
return TRUE;
}
return FALSE;
}
)
),
'errors' => array(
'required' => '{field} は必須です',
'isPassword' => '{field} は8文字以上100文字以下の英数字記号で入力してください'
)
);
$passconf = array(
'field' => 'passconf',
'label' => 'パスワード(再入力)',
'rules' => 'matches[password]',
'errors' => array(
'matches' => '{field} が一致しません'
)
);
$name = array(
'field' => 'name',
'label' => 'ユーザーネーム',
'rules' => 'required|max_length[128]',
'errors' => array(
'required' => '{field} は必須です',
'max_length' => '{field} は128文字以下で入力してください'
)
);
//この名前で設定しておけばRegisterクラスのindexメソッドで勝手に呼び出されるようになります
$config['register/index'] = array($mail, $password, $passconf, $name);
<?php
/**
* Class Register
* @property CI_Loader $load
* @property CI_Input $input
* @property CI_Form_validation $form_validation
* @property CI_Email $email
* @property CI_Config $config
* @property User_model $user_model
*/
class Register extends CI_Controller
{
/**
* Register constructor.
*/
public function __construct()
{
parent::__construct();
//バリデーションライブラリーのロード
$this->load->library('form_validation');
//メールライブラリーのロード
$this->load->library('email');
//ユーザーモデルのロード
$this->load->model('user_model');
//フォームヘルパー・URLヘルパーのロード
$this->load->helper(array('form', 'url'));
}
/**
* メール設定を行い本登録用のメールを送信する
* @param $id
* @return bool
*/
private function _sendMail($id)
{
//現在のタイムスタンプを取得
$time = time();
//モデルからトークンを取得
$token = $this->user_model->get_token($id, $time);
if ($token === '')
{
return FALSE;
}
$this->email->from('example.com', 'hoge form');
$this->email->to((string)$this->input->post('mail'));
$this->email->subject('本登録案内メール');
$this->email->message($this->load->view('mail/register_user', compact('id', 'time', 'token'), TRUE));
return $this->email->send();
}
/**
* ユーザーデータを登録し、メール送信を行う
* どちらも成功した場合は空文字を返し、エラーが発生した場合はエラー文を返す
* @return string
*/
private function _register_temp()
{
//userテーブルへデータをINSERT
$id = $this->user_model->insert($this->input->post());
//情報 or データベースにエラーが発生したら処理中断
if ($id === 0)
{
return 'データベースエラーが発生しました';
}
//メール送信
return $this->_sendMail($id) ? '' : 'メールの送信に失敗しました';
}
/**
* フォームの表示・入力データの検証を行う
* 検証をクリアした場合はDB操作とメール送信担当メソッドを呼び出す
*/
public function index()
{
//エラー文
$error = '';
//もし$_POST['submit']が存在したらデータ入力後のアクセスと判定
//config/form_validation.phpで設定したregisterバリデーション検証
if ($this->input->post('submit') && $this->form_validation->run())
{
$error = $this->_register_temp();
if ($error === '')
{
//エラーが無かった=処理が成功した場合はリダイレクトする
redirect(site_url('register/complete'));
}
}
//view
$this->load->view('front/register_index', compact('error'));
}
/**
* 仮登録完了画面表示
*/
public function complete()
{
$this->load->view('front/register_complete');
}
/**
* URLからトークンを検証
* OKならステータスを本登録に変更して完了画面を表示
* @param int $id
* @param int $time
* @param string $token
*/
public function auth($id, $time, $token)
{
//現在時刻のタイムスタンプを取得
$now_time = time();
//timeがメール送信から一時間以内ではなかったらリダイレクト
//条件の前半はメール送信時間が現在より未来を弾く
//後半はメール送信時間が現在-1時間より過去を弾く
if ($now_time < $time || $time < $now_time - (int)$this->config->item('auth_time'))
{
redirect(site_url());
}
//トークンが不正だったらリダイレクト
if ($token !== $this->user_model->get_token($id, $time))
{
redirect(site_url());
}
//ユーザーステータスをアップデート
$failure = ! $this->user_model->authentication($id);
//成功ページを表示
$this->load->view('front/register_auth', compact('failure'));
}
}
<?php
/**
* @var bool $failure
*/
defined('BASEPATH') OR exit('No direct script access allowed');
?><!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
<meta name="description" content="">
<title>メール認証ユーザー登録フローテスト</title>
</head>
<body>
<h1>ユーザー登録</h1>
<?php if ($failure): ?>
<p>データベースエラーが発生しました。</p>
<p>開発者の次回アップデートにご期待ください。</p>
<?php else: ?>
<p>ユーザー登録が完了しました。</p>
<p>開発者がログイン機能を作るまでお待ちください。</p>
<?php endif; ?>
</body>
</html>
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
?><!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
<meta name="description" content="">
<title>メール認証ユーザー登録フローテスト</title>
</head>
<body>
<h1>ユーザー登録</h1>
<p>ユーザー認証メールを送信しました。</p>
<p>メールに記載されているURLへアクセスして登録を完了させてください。</p>
</body>
</html>
<?php
/**
* @var string $error
*/
defined('BASEPATH') OR exit('No direct script access allowed');
?><!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
<meta name="description" content="">
<title>メール認証ユーザー登録フローテスト</title>
</head>
<body>
<h1>ユーザー登録</h1>
<?php echo validation_errors(); ?>
<?php echo $error; ?>
<?php echo form_open(); ?>
<div>
<label>
メールアドレス<?php echo form_input('mail', set_value('mail')); ?>
</label>
</div>
<div>
<label>
パスワード<?php echo form_password('password', set_value('password')); ?>
</label>
</div>
<div>
<label>
パスワード(再入力)<?php echo form_password('passconf', set_value('passconf')); ?>
</label>
</div>
<div>
<label>
ユーザーネーム<?php echo form_input('name', set_value('name')); ?>
</label>
</div>
<div>
<?php echo form_submit('submit', '認証メールを送信する'); ?>
</div>
<?php echo form_close(); ?>
</body>
</html>
<?php
/**
* @var int $id
* @var int $time
* @var string $token
*/
defined('BASEPATH') OR exit('No direct script access allowed');
?>この度は、ご登録頂きありがとうございます。
まだ登録は完了しておりません。
以下のURLへアクセスして登録を完了させてください。
<?php
echo site_url('register/auth/'.(string)$id.'/'.(string)$time.'/'.$token);
<?php
/**
* Class User_model
* @property CI_Loader $load
* @property CI_DB $db
* @property CI_Config $config
*/
class User_model extends CI_Model
{
public function __construct()
{
parent::__construct();
//データベースのロード
$this->load->database();
}
/**
* idから仮登録ユーザー情報を取得する
* 情報がなかった場合は空配列が返る
* @param int $id
* @return array
*/
private function _get_temp_user($id)
{
//クエリビルダでid指定されたユーザーを取得
$this->db->where('id', $id);
$this->db->where('status', 0);
$query = $this->db->get('user', 1);
$result = $query->row_array();
return $result ? $result : array();
}
/**
* idからuserテーブルを参照してユーザー認証用のtokenを作成する
* $timeはタイムスタンプを設定
* データが存在せず、場合は空文字を返す
* @param int $id
* @param int $time
* @return string
*/
public function get_token($id, $time)
{
$user = $this->_get_temp_user($id);
//ユーザー情報が存在しなかった場合は空文字(エラーの意)を返す
if ( ! isset($user['mail']) || ! isset($user['password']))
{
return '';
}
//メールアドレス、、パスワード、タイムスタンプ、ソルトを組み合わせてハッシュを作成
return hash('sha256', $user['mail'].'|'.$user['password'].'|'.$time.$this->config->item('salt'));
}
/**
* userテーブルに仮登録ステータスで情報をinsert する
* $dataに必要情報が無かった場合 or insertに失敗した場合は0を返す
* @param array $data
* @return int
*/
public function insert($data)
{
//データ内容の検証
//今回はNULLを使わないのでOKだが、
//もしNULLを明示的に入れる場合はissetで未定義判定ができないので注意
$columns = array('mail', 'password', 'name');
foreach ((array)$columns as $column)
{
if ( ! isset($data[$column]))
{
return 0;
}
}
//クエリビルダでINSERT
$result = $this->db->insert('user', array(
'mail' => $data['mail'],
'password' => hash('sha256', $data['password']),
'name' => $data['name'],
'status' => 0
));
return $result ? $this->db->insert_id() : 0;
}
/**
* ユーザーステータスを本登録にする
* エラーはFALSEを返す
* @param int $id
* @return bool
*/
public function authentication($id)
{
$user = $this->_get_temp_user($id);
//ユーザーが存在しない場合はFALSEを返す
if ( ! isset($user['id']))
{
return FALSE;
}
//statusカラムをUPDATE
return $this->db->update('user', array(
'status' => 1
),array(
'id' => $id
));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment