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->rollback(__DIR__ . '/database');
foreach ($migrator->getNotes() as $note) {
echo strip_tags($note) . PHP_EOL;

View file

@ -5,11 +5,15 @@ use Aitemu\RouterRequest;
require_once __DIR__ . '/../misuzu.php';
ob_start('ob_gzhandler');
//ob_start('ob_gzhandler');
$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();
echo $app->router->resolve(

View file

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

View file

@ -2,11 +2,13 @@
namespace Misuzu\Controllers;
use Aitemu\RouterResponse;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Misuzu\Application;
use Misuzu\Database;
use Misuzu\Net\IP;
use Misuzu\Users\User;
use Misuzu\Users\Session;
class AuthController extends Controller
{
@ -36,23 +38,48 @@ class AuthController extends Controller
return ['error' => 'Invalid username or password!'];
}
$_SESSION['user_id'] = $user->user_id;
$_SESSION['username'] = $user->username;
$session = new Session;
$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();
setcookie('msz_tmp_id', $_SESSION['user_id'], time() + 604800, '/', '.flashii.net');
setcookie('msz_tmp_key', $_SESSION['chat_key'], time() + 604800, '/', '.flashii.net');
setcookie('msz_tmp_id', $user->user_id, time() + 604800, '/', '.flashii.net');
setcookie('msz_tmp_key', $user->user_chat_key, time() + 604800, '/', '.flashii.net');
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
{
$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;
}
@ -137,7 +164,6 @@ class AuthController extends Controller
$user->register_ip = IP::unpack(IP::remote());
$user->last_ip = IP::unpack(IP::remote());
$user->user_country = get_country_code(IP::remote());
$user->user_registered = time();
$user->save();
return ['error' => 'Welcome to Flashii! You may now log in.', 'next' => '/auth/login'];
@ -145,8 +171,19 @@ class AuthController extends Controller
public function logout()
{
session_destroy();
return 'Logged out.<meta http-equiv="refresh" content="0; url=/">';
$app = Application::getInstance();
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

View file

@ -113,6 +113,16 @@ class TemplateEngine
$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.
* @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
{
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">
<a class="header__entry fa-home" href="/">home</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>
<a class="header__user" href="/auth/{{ tsession is defined and tsession.username is defined ? 'logout' : 'login' }}">
<div class="header__username">{{ tsession is defined and tsession.username is defined ? tsession.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>
<a class="header__user" href="{{ app.session is not null ? '/users/' ~ app.session.user_id : '/auth/login' }}">
<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/{{ app.session is not null ? 'discord-logo' : 'nova-none' }}.png')"></div>
</a>
</div>
</nav>

View file

@ -27,9 +27,9 @@
'text': 'Contributor',
},
'premium': {
'display': false,
'icon': 'fa-heart',
'text': 'Contributor',
'display': app.session is not null and app.session.user_id == profile.user_id,
'icon': 'fa-user',
'text': 'You!',
},
'banned': {
'display': false,
@ -45,7 +45,7 @@
<div class="profile__username">{{ profile.username }}</div>
</div>
<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>
{% endspaceless %}