Use custom session system.

This commit is contained in:
flash 2018-02-11 00:18:49 +01:00
parent e6eb44303b
commit 3862a66807
10 changed files with 175 additions and 25 deletions

View file

@ -0,0 +1,65 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Misuzu\Database;
// phpcs:disable
class CreateSessionsTable extends Migration
{
/**
* @SuppressWarnings(PHPMD)
*/
public function up()
{
$schema = Database::connection()->getSchemaBuilder();
$schema->create('sessions', function (Blueprint $table) {
$table->increments('session_id');
$table->integer('user_id')
->unsigned();
$table->string('session_key', 255);
$table->binary('session_ip');
$table->string('user_agent', 255)
->nullable()
->default(null);
$table->timestamp('expires_on')
->nullable();
$table->timestamps();
$table->foreign('user_id')
->references('user_id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
});
$schema->table('users', function (Blueprint $table) {
$table->dropColumn('user_registered');
$table->timestamps();
$table->softDeletes();
});
}
/**
* @SuppressWarnings(PHPMD)
*/
public function down()
{
$schema = Database::connection()->getSchemaBuilder();
$schema->drop('sessions');
$schema->table('users', function (Blueprint $table) {
$table->integer('user_registered')
->unsigned()
->default(0);
$table->dropSoftDeletes();
$table->dropTimestamps();
});
}
}

View file

@ -23,6 +23,7 @@ if (!$migrator->repositoryExists()) {
} }
$migrator->run(__DIR__ . '/database'); $migrator->run(__DIR__ . '/database');
//$migrator->rollback(__DIR__ . '/database');
foreach ($migrator->getNotes() as $note) { foreach ($migrator->getNotes() as $note) {
echo strip_tags($note) . PHP_EOL; echo strip_tags($note) . PHP_EOL;

View file

@ -5,11 +5,15 @@ use Aitemu\RouterRequest;
require_once __DIR__ . '/../misuzu.php'; require_once __DIR__ . '/../misuzu.php';
ob_start('ob_gzhandler'); //ob_start('ob_gzhandler');
$app = Application::getInstance(); $app = Application::getInstance();
$app->startRouter(include_once __DIR__ . '/../routes.php'); if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) {
$app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']);
}
$app->startRouter(include __DIR__ . '/../routes.php');
$app->startTemplating(); $app->startTemplating();
echo $app->router->resolve( echo $app->router->resolve(

View file

@ -3,6 +3,7 @@ namespace Misuzu;
use Aitemu\RouteCollection; use Aitemu\RouteCollection;
use Misuzu\Config\ConfigManager; use Misuzu\Config\ConfigManager;
use Misuzu\Users\Session;
use UnexpectedValueException; use UnexpectedValueException;
use InvalidArgumentException; use InvalidArgumentException;
@ -22,9 +23,14 @@ class Application extends ApplicationBase
*/ */
private const DATABASE_CONNECTIONS = [ private const DATABASE_CONNECTIONS = [
'mysql-main', 'mysql-main',
//'mysql-ayase',
]; ];
/**
* Session instance.
* @var \Misuzu\Users\Session
*/
public $session = null;
/** /**
* Constructor, called by ApplicationBase::start() which also passes the arguments through. * Constructor, called by ApplicationBase::start() which also passes the arguments through.
* @param ?string $configFile * @param ?string $configFile
@ -36,9 +42,6 @@ class Application extends ApplicationBase
ExceptionHandler::register(); ExceptionHandler::register();
ExceptionHandler::debug($this->debugMode); ExceptionHandler::debug($this->debugMode);
$this->addModule('config', new ConfigManager($configFile)); $this->addModule('config', new ConfigManager($configFile));
// temporary session system
session_start();
} }
public function __destruct() public function __destruct()
@ -46,6 +49,15 @@ class Application extends ApplicationBase
ExceptionHandler::unregister(); ExceptionHandler::unregister();
} }
public function startSession(int $user_id, string $session_key): void
{
$session = Session::where('session_key', $session_key)->where('user_id', $user_id)->first();
if ($session !== null) {
$this->session = $session;
}
}
/** /**
* Sets up the database module. * Sets up the database module.
*/ */
@ -68,7 +80,7 @@ class Application extends ApplicationBase
$database = $this->database; $database = $this->database;
foreach (self::DATABASE_CONNECTIONS as $name) { foreach (self::DATABASE_CONNECTIONS as $name) {
$section = 'Database.' . $name; $section = "Database.{$name}";
if (!$config->contains($section)) { if (!$config->contains($section)) {
throw new InvalidArgumentException("Database {$name} is not configured."); throw new InvalidArgumentException("Database {$name} is not configured.");
@ -100,7 +112,7 @@ class Application extends ApplicationBase
$twig->addFunction('git_hash', [Application::class, 'gitCommitHash']); $twig->addFunction('git_hash', [Application::class, 'gitCommitHash']);
$twig->addFunction('git_branch', [Application::class, 'gitBranch']); $twig->addFunction('git_branch', [Application::class, 'gitBranch']);
$twig->vars(['app' => $this, 'tsession' => $_SESSION]); $twig->var('app', $this);
$twig->addPath('nova', __DIR__ . '/../views/nova'); $twig->addPath('nova', __DIR__ . '/../views/nova');
} }

View file

@ -2,11 +2,13 @@
namespace Misuzu\Controllers; namespace Misuzu\Controllers;
use Aitemu\RouterResponse; use Aitemu\RouterResponse;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Misuzu\Application; use Misuzu\Application;
use Misuzu\Database; use Misuzu\Database;
use Misuzu\Net\IP; use Misuzu\Net\IP;
use Misuzu\Users\User; use Misuzu\Users\User;
use Misuzu\Users\Session;
class AuthController extends Controller class AuthController extends Controller
{ {
@ -36,23 +38,48 @@ class AuthController extends Controller
return ['error' => 'Invalid username or password!']; return ['error' => 'Invalid username or password!'];
} }
$_SESSION['user_id'] = $user->user_id; $session = new Session;
$_SESSION['username'] = $user->username; $session->user_id = $user->user_id;
$session->session_ip = IP::unpack(IP::remote());
$session->user_agent = 'Misuzu Testing 1';
$session->expires_on = Carbon::now()->addMonth();
$session->session_key = bin2hex(random_bytes(32));
$session->save();
$user->user_chat_key = $_SESSION['chat_key'] = bin2hex(random_bytes(16)); Application::getInstance()->session = $session;
$this->setCookie('uid', $session->user_id, 604800);
$this->setCookie('sid', $session->session_key, 604800);
// Temporary key generation for chat login.
// Should eventually be replaced with a callback login system.
// Also uses different cookies since $httponly is required to be false for these.
$user->user_chat_key = bin2hex(random_bytes(16));
$user->save(); $user->save();
setcookie('msz_tmp_id', $_SESSION['user_id'], time() + 604800, '/', '.flashii.net'); setcookie('msz_tmp_id', $user->user_id, time() + 604800, '/', '.flashii.net');
setcookie('msz_tmp_key', $_SESSION['chat_key'], time() + 604800, '/', '.flashii.net'); setcookie('msz_tmp_key', $user->user_chat_key, time() + 604800, '/', '.flashii.net');
return ['error' => 'You are now logged in!', 'next' => '/']; return ['error' => 'You are now logged in!', 'next' => '/'];
} }
private function setCookie(string $name, string $value, int $expires): void
{
setcookie(
"msz_{$name}",
$value,
time() + $expires,
'/',
'',
!empty($_SERVER['HTTPS']),
true
);
}
private function hasRegistrations(?string $ipAddr = null): bool private function hasRegistrations(?string $ipAddr = null): bool
{ {
$ipAddr = IP::unpack($ipAddr ?? IP::remote()); $ipAddr = IP::unpack($ipAddr ?? IP::remote());
if (User::where('register_ip', $ipAddr)->orWhere('last_ip', $ipAddr)->count()) { if (User::withTrashed()->where('register_ip', $ipAddr)->orWhere('last_ip', $ipAddr)->count()) {
return true; return true;
} }
@ -137,7 +164,6 @@ class AuthController extends Controller
$user->register_ip = IP::unpack(IP::remote()); $user->register_ip = IP::unpack(IP::remote());
$user->last_ip = IP::unpack(IP::remote()); $user->last_ip = IP::unpack(IP::remote());
$user->user_country = get_country_code(IP::remote()); $user->user_country = get_country_code(IP::remote());
$user->user_registered = time();
$user->save(); $user->save();
return ['error' => 'Welcome to Flashii! You may now log in.', 'next' => '/auth/login']; return ['error' => 'Welcome to Flashii! You may now log in.', 'next' => '/auth/login'];
@ -145,8 +171,19 @@ class AuthController extends Controller
public function logout() public function logout()
{ {
session_destroy(); $app = Application::getInstance();
return 'Logged out.<meta http-equiv="refresh" content="0; url=/">';
if ($app->session === null) {
echo "You aren't logged in.";
} else {
echo "You've been logged out.";
$this->setCookie('uid', '', -3600);
$this->setCookie('sid', '', -3600);
$app->session->delete();
$app->session = null;
}
return '<meta http-equiv="refresh" content="1; url=/">';
} }
private function validateUsername(string $username): string private function validateUsername(string $username): string

View file

@ -113,6 +113,16 @@ class TemplateEngine
$this->loader->addPath($path, $name); $this->loader->addPath($path, $name);
} }
/**
* Sets a render var.
* @param string $name
* @param mixed $value
*/
public function var(string $name, $value): void
{
$this->vars[$name] = $value;
}
/** /**
* Sets render vars. * Sets render vars.
* @param array $vars * @param array $vars

14
src/Users/Session.php Normal file
View file

@ -0,0 +1,14 @@
<?php
namespace Misuzu\Users;
use Misuzu\Model;
class Session extends Model
{
protected $primaryKey = 'session_id';
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
}

View file

@ -6,5 +6,9 @@ use Misuzu\Model;
class User extends Model class User extends Model
{ {
protected $primaryKey = 'user_id'; protected $primaryKey = 'user_id';
public $timestamps = false;
public function sessions()
{
return $this->hasMany(Session::class, 'user_id');
}
} }

View file

@ -16,10 +16,13 @@
<div class="header__navigation"> <div class="header__navigation">
<a class="header__entry fa-home" href="/">home</a> <a class="header__entry fa-home" href="/">home</a>
<a class="header__entry fa-comments" href="https://chat.flashii.net">chat</a> <a class="header__entry fa-comments" href="https://chat.flashii.net">chat</a>
{% if app.session is not null %}
<a class="header__entry fa-sign-out" href="/auth/logout">logout</a>
{% endif %}
</div> </div>
<a class="header__user" href="/auth/{{ tsession is defined and tsession.username is defined ? 'logout' : 'login' }}"> <a class="header__user" href="{{ app.session is not null ? '/users/' ~ app.session.user_id : '/auth/login' }}">
<div class="header__username">{{ tsession is defined and tsession.username is defined ? tsession.username : 'login' }}</div> <div class="header__username">{{ app.session is not null ? app.session.user.first.username : 'login' }}</div>
<div class="header__avatar" style="background-image: url('https://static.flash.moe/images/{{ tsession is defined and tsession.username is defined ? 'discord-logo' : 'nova-none' }}.png')"></div> <div class="header__avatar" style="background-image: url('https://static.flash.moe/images/{{ app.session is not null ? 'discord-logo' : 'nova-none' }}.png')"></div>
</a> </a>
</div> </div>
</nav> </nav>

View file

@ -27,9 +27,9 @@
'text': 'Contributor', 'text': 'Contributor',
}, },
'premium': { 'premium': {
'display': false, 'display': app.session is not null and app.session.user_id == profile.user_id,
'icon': 'fa-heart', 'icon': 'fa-user',
'text': 'Contributor', 'text': 'You!',
}, },
'banned': { 'banned': {
'display': false, 'display': false,
@ -45,7 +45,7 @@
<div class="profile__username">{{ profile.username }}</div> <div class="profile__username">{{ profile.username }}</div>
</div> </div>
<div class="profile__header-sub profile__dates"> <div class="profile__header-sub profile__dates">
<div class="profile__date--joined">Joined {{ profile.user_registered|date('r') }}</div> <div class="profile__date--joined">Joined {{ profile.created_at.format('r') }}</div>
</div> </div>
</div> </div>
{% endspaceless %} {% endspaceless %}