Use some of Eloquent's features to make life easier.

This commit is contained in:
flash 2018-02-11 13:57:01 +01:00
parent de56e9ede8
commit 8d90699f40
7 changed files with 128 additions and 30 deletions

View file

@ -29,7 +29,7 @@ class Application extends ApplicationBase
* Session instance. * Session instance.
* @var \Misuzu\Users\Session * @var \Misuzu\Users\Session
*/ */
public $session = null; private $session = null;
/** /**
* Constructor, called by ApplicationBase::start() which also passes the arguments through. * Constructor, called by ApplicationBase::start() which also passes the arguments through.
@ -51,12 +51,28 @@ class Application extends ApplicationBase
public function startSession(int $user_id, string $session_key): void public function startSession(int $user_id, string $session_key): void
{ {
$session = Session::where('session_key', $session_key)->where('user_id', $user_id)->first(); $session = Session::where('session_key', $session_key)
->where('user_id', $user_id)
->first();
if ($session !== null) { if ($session !== null) {
$this->session = $session; if ($session->hasExpired()) {
$session->delete();
} else {
$this->setSession($session);
} }
} }
}
public function getSession(): Session
{
return $this->session;
}
public function setSession(Session $session): void
{
$this->session = $session;
}
/** /**
* Sets up the database module. * Sets up the database module.

View file

@ -29,30 +29,31 @@ class AuthController extends Controller
$password = $_POST['password'] ?? ''; $password = $_POST['password'] ?? '';
try { try {
$user = User::where('username', $username)->firstOrFail(); $user = User::where('username', $username)->orWhere('email', $username)->firstOrFail();
} catch (ModelNotFoundException $e) { } catch (ModelNotFoundException $e) {
return ['error' => 'Invalid username or password!']; return ['error' => 'Invalid username or password!'];
} }
if (!password_verify($password, $user->password)) { if (!$user->validatePassword($password)) {
return ['error' => 'Invalid username or password!']; return ['error' => 'Invalid username or password!'];
} }
$session = new Session; $session = new Session;
$session->user_id = $user->user_id; $session->user_id = $user->user_id;
$session->session_ip = IP::unpack(IP::remote()); $session->session_ip = IP::remote();
$session->user_agent = 'Misuzu Testing 1'; $session->user_agent = 'Misuzu Testing 1';
$session->expires_on = Carbon::now()->addMonth(); $session->expires_on = Carbon::now()->addMonth();
$session->session_key = bin2hex(random_bytes(32)); $session->session_key = bin2hex(random_bytes(32));
$session->save(); $session->save();
Application::getInstance()->session = $session; Application::getInstance()->setSession($session);
$this->setCookie('uid', $session->user_id, 604800); $this->setCookie('uid', $session->user_id, 604800);
$this->setCookie('sid', $session->session_key, 604800); $this->setCookie('sid', $session->session_key, 604800);
// Temporary key generation for chat login. // Temporary key generation for chat login.
// Should eventually be replaced with a callback login system. // Should eventually be replaced with a callback login system.
// Also uses different cookies since $httponly is required to be false for these. // Also uses different cookies since $httponly is required to be false for these.
$user->last_ip = IP::remote();
$user->user_chat_key = bin2hex(random_bytes(16)); $user->user_chat_key = bin2hex(random_bytes(16));
$user->save(); $user->save();
@ -79,6 +80,10 @@ class AuthController extends Controller
{ {
$ipAddr = IP::unpack($ipAddr ?? IP::remote()); $ipAddr = IP::unpack($ipAddr ?? IP::remote());
if ($ipAddr === IP::unpack('127.0.0.1') || $ipAddr === IP::unpack('::1')) {
return false;
}
if (User::withTrashed()->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;
} }
@ -157,14 +162,7 @@ class AuthController extends Controller
return ['error' => 'Your password is considered too weak!']; return ['error' => 'Your password is considered too weak!'];
} }
$user = new User; User::createUser($username, $password, $email);
$user->username = $username;
$user->password = password_hash($password, PASSWORD_ARGON2I);
$user->email = $email;
$user->register_ip = IP::unpack(IP::remote());
$user->last_ip = IP::unpack(IP::remote());
$user->user_country = get_country_code(IP::remote());
$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'];
} }

View file

@ -2,10 +2,27 @@
namespace Misuzu\Users; namespace Misuzu\Users;
use Misuzu\Model; use Misuzu\Model;
use Misuzu\Net\IP;
class Session extends Model class Session extends Model
{ {
protected $primaryKey = 'session_id'; protected $primaryKey = 'session_id';
protected $dates = ['expires_on'];
public function getSessionIpAttribute(string $ipAddress): string
{
return IP::pack($ipAddress);
}
public function setSessionIpAttribute(string $ipAddress): void
{
$this->attributes['session_ip'] = IP::unpack($ipAddress);
}
public function hasExpired(): bool
{
return $this->expires_on->isPast();
}
public function user() public function user()
{ {

View file

@ -3,13 +3,71 @@ namespace Misuzu\Users;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Misuzu\Model; use Misuzu\Model;
use Misuzu\Net\IP;
class User extends Model class User extends Model
{ {
use SoftDeletes; use SoftDeletes;
private const PASSWORD_HASH_ALGO = PASSWORD_ARGON2I;
protected $primaryKey = 'user_id'; protected $primaryKey = 'user_id';
public static function createUser(
string $username,
string $password,
string $email,
?string $ipAddress = null
): User {
$ipAddress = $ipAddress ?? IP::remote();
$user = new User;
$user->username = $username;
$user->password = $password;
$user->email = $email;
$user->register_ip = $ipAddress;
$user->last_ip = $ipAddress;
$user->user_country = get_country_code($ipAddress);
$user->save();
return $user;
}
public function getRegisterIpAttribute(string $ipAddress): string
{
return IP::pack($ipAddress);
}
public function setRegisterIpAttribute(string $ipAddress): void
{
$this->attributes['register_ip'] = IP::unpack($ipAddress);
}
public function getLastIpAttribute(string $ipAddress): string
{
return IP::pack($ipAddress);
}
public function setLastIpAttribute(string $ipAddress): void
{
$this->attributes['last_ip'] = IP::unpack($ipAddress);
}
public function setPasswordAttribute(string $password): void
{
$this->attributes['password'] = password_hash($password, self::PASSWORD_HASH_ALGO);
}
public function validatePassword(string $password): bool
{
if (password_needs_rehash($this->password, self::PASSWORD_HASH_ALGO)) {
$this->password = $password;
$this->save();
}
return password_verify($password, $this->password);
}
public function sessions() public function sessions()
{ {
return $this->hasMany(Session::class, 'user_id'); return $this->hasMany(Session::class, 'user_id');

View file

@ -3,12 +3,14 @@
{% set banner_classes = 'banner--insane landing__banner' %} {% set banner_classes = 'banner--insane landing__banner' %}
{% block banner_content %} {% block banner_content %}
<h1 style="align-self: center; text-align: left; flex-grow: 1; padding-left: 2em">Well, this is embarrassing...</h1> <div class="landing__inner">
{% endblock %} <div class="landing__buttons">
<a href="/auth/register" class="landing__button">register</a>
{% block content %} <a href="/auth/login" class="landing__button">login</a>
<div class="platform" style="text-align: left;"> </div>
<p>Long story short, almost nothing is ready and going live now (or within the next week) will just lead to greater disappointment. I set up a <a href="https://twitter.com/flashiinet" class="container__footer-link" target="_blank" rel="noreferrer noopener">Twitter</a> again on which I'll eventually inform when the site goes in public beta.</p> <div class="landing__text">
<p>I offer my sincerest apologies for not being able to live up to the hype I've created (twice, even) but understand that this decision will make for a less rushed end product and less coping with hurried code in the future.</p> <p>Registration soon, but not now.</p>
<p>Keep an eye on <a href="https://twitter.com/flashiinet" class="container__footer-link" target="_blank" rel="noreferrer noopener">Twitter</a>!</p>
</div>
</div> </div>
{% endblock %} {% endblock %}

5
views/nova/macros.twig Normal file
View file

@ -0,0 +1,5 @@
{% macro link(url, content, class) %}
{% spaceless %}
<a href="{{ url }}" {% if '://' in url %} target="_blank" rel="noreferrer noopener"{% endif %} {% if class is defined %}class="{{ class }}"{% endif %}>{{ content|raw }}</a>
{% endspaceless %}
{% endmacro %}

View file

@ -1,3 +1,5 @@
{% from '@nova/macros.twig' import link %}
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
@ -21,7 +23,7 @@
{% endif %} {% endif %}
</div> </div>
<a class="header__user" href="{{ app.session is not null ? '/users/' ~ app.session.user_id : '/auth/login' }}"> <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__username">{{ app.session is not null ? app.session.user.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> <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>
@ -41,15 +43,15 @@
<div class="container__footer"> <div class="container__footer">
<div class="container__footer-copyright"> <div class="container__footer-copyright">
<a href="https://flash.moe" class="container__footer-link">flash.moe 2013-{{ ''|date('Y') }}</a> | {{ link('https://flash.moe', 'flash.moe 2013-' ~ ''|date('Y'), 'container__footer-link') }} |
<a href="https://github.com/flashwave/misuzu/tree/{{ git_branch() }}" class="container__footer-link">{{ git_branch() }}</a>#<a href="https://github.com/flashwave/misuzu/commit/{{ git_hash(true) }}" class="container__footer-link">{{ git_hash() }}</a> {{ link('https://github.com/flashwave/misuzu/tree/' ~ git_branch(), git_branch(), 'container__footer-link') }}#{{ link('https://github.com/flashwave/misuzu/commit/' ~ git_hash(true), git_hash(), 'container__footer-link') }}
</div> </div>
<div class="container__footer-links"> <div class="container__footer-links">
<a href="#" class="container__footer-link">Terms of Service</a> {{ link('#', 'Terms of Service', 'container__footer-link') }}
<a href="#" class="container__footer-link">Rules</a> {{ link('#', 'Rules', 'container__footer-link') }}
<a href="#" class="container__footer-link">Contact</a> {{ link('#', 'Contact', 'container__footer-link') }}
<a href="#" class="container__footer-link">Status</a> {{ link('#', 'Status', 'container__footer-link') }}
<a href="https://twitter.com/flashiinet" class="container__footer-link" target="_blank" rel="noreferrer noopener">@flashiinet</a> {{ link('https://twitter.com/flashiinet', '@flashiinet', 'container__footer-link') }}
</div> </div>
</div> </div>
</div> </div>