Add profile fields to database and add changing logic.

This commit is contained in:
flash 2018-03-23 01:01:42 +01:00
parent ecd0d6f9c9
commit 8a6a36d6bf
21 changed files with 824 additions and 43 deletions

View file

@ -26,8 +26,10 @@
border-top-width: 1px;
border-bottom-width: 0;
&--selected {
top: 1px;
@media (min-width: @mio-navigation-mobile) {
&--selected {
top: 1px;
}
}
}
}

View file

@ -0,0 +1,64 @@
@mio-settings-account-mobile: 800px;
.mio__settings__account {
display: flex;
flex-direction: column;
&__row {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-evenly;
@media (max-width: @mio-settings-account-mobile) {
flex-direction: column;
}
&--buttons {
display: block;
text-align: center;
margin: 2px 0;
}
}
&__column {
flex-grow: 1;
&:not(.mio__settings__account__column--no-margin) {
margin: 1px;
border: 1px solid #9475b2;
border-top-width: 0;
}
}
&__title {
background-color: #9475b2;
color: #306;
font-size: 1.1em;
font-weight: 700;
padding: 3px;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
&__input {
display: flex;
margin: 2px;
@media (max-width: @mio-settings-account-mobile) {
flex-direction: column;
}
&__name {
margin: 0 2px;
min-width: 140px;
}
&__value {
flex-grow: 1;
&__text {
width: 100%;
}
}
}
}

View file

@ -0,0 +1,5 @@
.mio__settings__content {
&--account {
margin: 1px;
}
}

View file

@ -0,0 +1,4 @@
.mio__settings__errors {
margin-left: 1.2em;
list-style: square;
}

View file

@ -48,5 +48,10 @@ body {
@import "classes/navigation";
@import "classes/profile";
// Settings
@import "classes/settings/content";
@import "classes/settings/errors";
@import "classes/settings/account";
// Forums
@import "classes/forum/listing";

View file

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Misuzu\Database;
// phpcs:disable
class AddSessionCountryAndLoginUserAgent extends Migration
{
/**
* @SuppressWarnings(PHPMD)
*/
public function up()
{
$schema = Database::connection()->getSchemaBuilder();
$schema->table('sessions', function (Blueprint $table) {
$table->char('session_country', 2)
->default('XX');
});
$schema->table('login_attempts', function (Blueprint $table) {
$table->string('user_agent', 255)
->default('');
});
}
/**
* @SuppressWarnings(PHPMD)
*/
public function down()
{
$schema = Database::connection()->getSchemaBuilder();
$schema->table('sessions', function (Blueprint $table) {
$table->dropColumn('session_country');
});
$schema->table('login_attempts', function (Blueprint $table) {
$table->dropColumn('user_agent');
});
}
}

View file

@ -0,0 +1,69 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Misuzu\Database;
// phpcs:disable
class AddProfileFieldsToUsers extends Migration
{
/**
* @SuppressWarnings(PHPMD)
*/
public function up()
{
$schema = Database::connection()->getSchemaBuilder();
$schema->table('users', function (Blueprint $table) {
$table->string('user_website', 255)
->default('');
$table->string('user_twitter', 20)
->default('');
$table->string('user_github', 40)
->default('');
$table->string('user_skype', 60)
->default('');
$table->string('user_discord', 40)
->default('');
$table->string('user_youtube', 255)
->default('');
$table->string('user_steam', 255)
->default('');
$table->string('user_twitchtv', 30)
->default('');
$table->string('user_osu', 20)
->default('');
$table->string('user_lastfm', 20)
->default('');
});
}
/**
* @SuppressWarnings(PHPMD)
*/
public function down()
{
$schema = Database::connection()->getSchemaBuilder();
$schema->table('users', function (Blueprint $table) {
$table->dropColumn([
'user_website',
'user_twitter',
'user_github',
'user_skype',
'user_discord',
'user_youtube',
'user_steam',
'user_twitchtv',
'user_osu',
'user_lastfm',
]);
});
}
}

View file

@ -17,6 +17,7 @@ $username_validation_errors = [
'double-spaces' => "Your username can't contain double spaces.",
'invalid' => 'Your username contains invalid characters.',
'spacing' => 'Please use either underscores or spaces, not both!',
'in-use' => 'This username is already taken!',
];
$mode = $_GET['m'] ?? 'login';
@ -59,6 +60,7 @@ switch ($mode) {
break;
}
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$auth_login_error = '';
while ($_SERVER['REQUEST_METHOD'] === 'POST') {
@ -85,20 +87,20 @@ switch ($mode) {
try {
$user = User::where('username', $username)->orWhere('email', $username)->firstOrFail();
} catch (ModelNotFoundException $e) {
LoginAttempt::recordFail($ipAddress);
LoginAttempt::recordFail($ipAddress, null, $user_agent);
$auth_login_error = 'Invalid username or password!';
break;
}
if (!$user->validatePassword($password)) {
LoginAttempt::recordFail($ipAddress, $user);
if (!$user->verifyPassword($password)) {
LoginAttempt::recordFail($ipAddress, $user, $user_agent);
$auth_login_error = 'Invalid username or password!';
break;
}
LoginAttempt::recordSuccess($ipAddress, $user);
LoginAttempt::recordSuccess($ipAddress, $user, $user_agent);
$session = Session::createSession($user, 'Misuzu T2', null, $ipAddress);
$session = Session::createSession($user, $user_agent, null, $ipAddress);
$app->setSession($session);
set_cookie_m('uid', $session->user_id, 604800);
set_cookie_m('sid', $session->session_key, 604800);
@ -142,41 +144,24 @@ switch ($mode) {
}
$username = $_POST['username'] ?? '';
$username_validate = User::validateUsername($username);
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
$username_validate = User::validateUsername($username, true);
if ($username_validate !== '') {
$auth_register_error = $username_validation_errors[$username_validate];
break;
}
try {
$existing = User::where('username', $username)->firstOrFail();
if ($existing->user_id > 0) {
$auth_register_error = 'This username is already taken!';
break;
}
} catch (ModelNotFoundException $e) {
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || !check_mx_record($email)) {
$auth_register_error = 'The e-mail address you entered is invalid!';
$email_validate = User::validateEmail($email, true);
if ($email_validate !== '') {
$auth_register_error = $email_validate === 'in-use'
? 'This e-mail address has already been used!'
: 'The e-mail address you entered is invalid!';
break;
}
try {
$existing = User::where('email', $email)->firstOrFail();
if ($existing->user_id > 0) {
$auth_register_error = 'This e-mail address has already been used!';
break;
}
} catch (ModelNotFoundException $e) {
}
if (password_entropy($password) < 32) {
if (User::validatePassword($password) !== '') {
$auth_register_error = 'Your password is too weak!';
break;
}

342
public/settings.php Normal file
View file

@ -0,0 +1,342 @@
<?php
use Misuzu\Application;
use Misuzu\Users\User;
use Misuzu\Users\Session;
require_once __DIR__ . '/../misuzu.php';
$settings_session = Application::getInstance()->getSession();
if ($settings_session === null) {
header('Location: /');
return;
}
$settings_user = $settings_session->user;
$settings_mode = $_GET['m'] ?? null;
$settings_modes = [
'account' => 'Account',
'avatar' => 'Avatar',
'sessions' => 'Sessions',
'login-history' => 'Login History',
];
// if no mode is explicitly set just go to the index
if ($settings_mode === null) {
$settings_mode = key($settings_modes);
}
$app->templating->vars(compact('settings_mode', 'settings_modes', 'settings_user'));
if (!array_key_exists($settings_mode, $settings_modes)) {
$app->templating->var('settings_title', 'Not Found');
echo $app->templating->render("settings.notfound");
return;
}
$settings_errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
switch ($settings_mode) {
case 'account':
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
$settings_errors[] = "Couldn't verify you, please refresh the page and retry.";
break;
}
if (isset($_POST['profile']) && is_array($_POST['profile'])) {
if (isset($_POST['profile']['twitter'])) {
$user_twitter = '';
if (!empty($_POST['profile']['twitter'])) {
$twitter_regex = preg_match(
'#^(?:https?://(?:www\.)?twitter.com/(?:\#!\/)?)?@?([A-Za-z0-9_]{1,20})/?$#u',
$_POST['profile']['twitter'],
$twitter_matches
);
if ($twitter_regex !== 1) {
$settings_errors[] = "Invalid Twitter field.";
break;
}
$user_twitter = $twitter_matches[1];
}
$settings_user->user_twitter = $user_twitter;
}
if (isset($_POST['profile']['osu'])) {
$user_osu = '';
if (!empty($_POST['profile']['osu'])) {
$osu_regex = preg_match(
'#^(?:https?://osu.ppy.sh/u(?:sers)?/)?([a-zA-Z0-9-\[\]_ ]{1,20})/?$#u',
$_POST['profile']['osu'],
$osu_matches
);
if ($osu_regex !== 1) {
$settings_errors[] = "Invalid osu! field.";
break;
}
$user_osu = $osu_matches[1];
}
$settings_user->user_osu = $user_osu;
}
if (isset($_POST['profile']['website'])) {
$user_website = '';
if (!empty($_POST['profile']['website'])) {
$website_regex = preg_match(
'#^(?:https?)://(.{1,240})$#u',
$_POST['profile']['website'],
$website_matches
);
if ($website_regex !== 1) {
$settings_errors[] = "Invalid website field.";
break;
}
$user_website = $website_matches[0];
}
$settings_user->user_website = $user_website;
}
if (isset($_POST['profile']['youtube'])) {
$user_youtube = '';
if (!empty($_POST['profile']['youtube'])) {
$youtube_regex = preg_match(
'#^(?:https?://(?:www.)?youtube.com/(?:(?:user|c|channel)/)?)?'
. '(UC[a-zA-Z0-9-_]{1,50}|[a-zA-Z0-9-_%]{1,100})/?$#u',
$_POST['profile']['youtube'],
$youtube_matches
);
if ($youtube_regex !== 1) {
$settings_errors[] = "Invalid Youtube field.";
break;
}
$user_youtube = $youtube_matches[1];
}
$settings_user->user_youtube = $user_youtube;
}
if (isset($_POST['profile']['steam'])) {
$user_steam = '';
if (!empty($_POST['profile']['steam'])) {
$steam_regex = preg_match(
'#^(?:https?://(?:www.)?steamcommunity.com/(?:id|profiles)/)?([a-zA-Z0-9_-]{2,100})/?$#u',
$_POST['profile']['steam'],
$steam_matches
);
if ($steam_regex !== 1) {
$settings_errors[] = "Invalid Steam field.";
break;
}
$user_steam = $steam_matches[1];
}
$settings_user->user_steam = $user_steam;
}
if (isset($_POST['profile']['twitchtv'])) {
$user_twitchtv = '';
if (!empty($_POST['profile']['twitchtv'])) {
$twitchtv_regex = preg_match(
'#^(?:https?://(?:www.)?twitch.tv/)?([0-9A-Za-z_]{3,25})/?$#u',
$_POST['profile']['twitchtv'],
$twitchtv_matches
);
if ($twitchtv_regex !== 1) {
$settings_errors[] = "Invalid Twitch.TV field.";
break;
}
$user_twitchtv = $twitchtv_matches[1];
}
$settings_user->user_twitchtv = $user_twitchtv;
}
if (isset($_POST['profile']['lastfm'])) {
$user_lastfm = '';
if (!empty($_POST['profile']['lastfm'])) {
$lastfm_regex = preg_match(
'#^(?:https?://(?:www.)?last.fm/user/)?([a-zA-Z]{1}[a-zA-Z0-9_-]{1,14})/?$#u',
$_POST['profile']['lastfm'],
$lastfm_matches
);
if ($lastfm_regex !== 1) {
$settings_errors[] = "Invalid Last.fm field.";
break;
}
$user_lastfm = $lastfm_matches[1];
}
$settings_user->user_lastfm = $user_lastfm;
}
if (isset($_POST['profile']['github'])) {
$user_github = '';
if (!empty($_POST['profile']['github'])) {
$github_regex = preg_match(
'#^(?:https?://(?:www.)?github.com/?)?'
. '([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/?$#u',
$_POST['profile']['github'],
$github_matches
);
if ($github_regex !== 1) {
$settings_errors[] = "Invalid Github field.";
break;
}
$user_github = $github_matches[1];
}
$settings_user->user_github = $user_github;
}
if (isset($_POST['profile']['skype'])) {
$user_skype = '';
if (!empty($_POST['profile']['skype'])) {
$skype_regex = preg_match(
'#^((?:live:)?[a-zA-Z][\w\.,\-_@]{1,100})$#u',
$_POST['profile']['skype'],
$skype_matches
);
if ($skype_regex !== 1) {
$settings_errors[] = "Invalid Skype field.";
break;
}
$user_skype = $skype_matches[1];
}
$settings_user->user_skype = $user_skype;
}
if (isset($_POST['profile']['discord'])) {
$user_discord = '';
if (!empty($_POST['profile']['discord'])) {
$discord_regex = preg_match(
'#^(.{1,32}\#[0-9]{4})$#u',
$_POST['profile']['discord'],
$discord_matches
);
if ($discord_regex !== 1) {
$settings_errors[] = "Invalid Discord field.";
break;
}
$user_discord = $discord_matches[1];
}
$settings_user->user_discord = $user_discord;
}
}
if (!empty($_POST['current_password']) && (isset($_POST['password']) || isset($_OST['email']))) {
if (!$settings_user->verifyPassword($_POST['current_password'])) {
$settings_errors[] = "Your current password was incorrect.";
break;
}
if (!empty($_POST['email']['new'])) {
if (empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) {
$settings_errors[] = "The given e-mail addresses did not match.";
break;
}
if ($_POST['email']['new'] === $settings_user->email) {
$settings_errors[] = "This is your e-mail address already!";
break;
}
$email_validate = User::validateEmail($_POST['email']['new'], true);
if ($email_validate !== '') {
switch ($email_validate) {
case 'dns':
$settings_errors[] = "No valid MX record exists for this domain.";
break;
case 'format':
$settings_errors[] = "The given e-mail address was incorrectly formatted.";
break;
case 'in-use':
$settings_errors[] = "This e-mail address has already been used by another user.";
break;
default:
$settings_errors[] = "Unknown e-mail validation error.";
}
break;
}
$settings_user->email = $_POST['email']['new'];
}
if (!empty($_POST['password']['new'])) {
if (empty($_POST['password']['confirm'])
|| $_POST['password']['new'] !== $_POST['password']['confirm']) {
$settings_errors[] = "The given passwords did not match.";
break;
}
$password_validate = User::validatePassword($_POST['password']['new'], true);
if ($password_validate !== '') {
$settings_errors[] = "The given passwords was too weak.";
break;
}
$settings_user->password = $_POST['password']['new'];
}
}
if (count($settings_errors) < 1 && $settings_user->isDirty()) {
$settings_user->save();
}
break;
}
}
$app->templating->vars(compact('settings_errors'));
$app->templating->var('settings_title', $settings_modes[$settings_mode]);
switch ($settings_mode) {
case 'sessions':
$app->templating->var('user_sessions', $settings_user->sessions->reverse());
break;
case 'login-history':
$app->templating->var('user_login_attempts', $settings_user->loginAttempts->reverse());
break;
}
echo $app->templating->render("settings.{$settings_mode}");

View file

@ -120,7 +120,7 @@ class Application extends ApplicationBase
$twig->addFilter('json_decode');
$twig->addFilter('byte_symbol');
$twig->addFilter('country_name', 'get_country_name');
$twig->addFilter('md5'); // using this for logout CSRF for now, remove this when proper CSRF is in place
$twig->addFilter('flip', 'array_flip');
$twig->addFunction('byte_symbol');
$twig->addFunction('session_id');

View file

@ -9,21 +9,26 @@ class LoginAttempt extends Model
{
protected $primaryKey = 'attempt_id';
public static function recordSuccess(IPAddress $ipAddress, User $user): LoginAttempt
public static function recordSuccess(IPAddress $ipAddress, User $user, ?string $userAgent = null): LoginAttempt
{
return static::recordAttempt(true, $ipAddress, $user);
return static::recordAttempt(true, $ipAddress, $user, $userAgent);
}
public static function recordFail(IPAddress $ipAddress, ?User $user = null): LoginAttempt
public static function recordFail(IPAddress $ipAddress, ?User $user = null, ?string $userAgent = null): LoginAttempt
{
return static::recordAttempt(false, $ipAddress, $user);
return static::recordAttempt(false, $ipAddress, $user, $userAgent);
}
public static function recordAttempt(bool $success, IPAddress $ipAddress, ?User $user = null): LoginAttempt
{
public static function recordAttempt(
bool $success,
IPAddress $ipAddress,
?User $user = null,
?string $userAgent = null
): LoginAttempt {
$attempt = new static;
$attempt->was_successful = $success;
$attempt->attempt_ip = $ipAddress;
$attempt->user_agent = $userAgent ?? '';
if ($user !== null) {
$attempt->user_id = $user;

View file

@ -49,6 +49,7 @@ class Session extends Model
public function setSessionIpAttribute(IPAddress $ipAddress): void
{
$this->attributes['session_ip'] = $ipAddress->getRaw();
$this->attributes['session_country'] = $ipAddress->getCountryCode();
}
public function user()

View file

@ -40,7 +40,7 @@ class User extends Model
return $user;
}
public static function validateUsername(string $username): string
public static function validateUsername(string $username, bool $checkInUse = false): string
{
$username_length = strlen($username);
@ -68,6 +68,36 @@ class User extends Model
return 'spacing';
}
if ($checkInUse && static::where('username', $username)->count() > 0) {
return 'in-use';
}
return '';
}
public static function validateEmail(string $email, bool $checkInUse = false): string
{
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
return 'format';
}
if (!check_mx_record($email)) {
return 'dns';
}
if ($checkInUse && static::where('email', $email)->count() > 0) {
return 'in-use';
}
return '';
}
public static function validatePassword(string $password): string
{
if (password_entropy($password) < 32) {
return 'weak';
}
return '';
}
@ -97,7 +127,7 @@ class User extends Model
->count() > 0;
}
public function validatePassword(string $password): bool
public function verifyPassword(string $password): bool
{
if (password_verify($password, $this->password) !== true) {
return false;

View file

@ -4,13 +4,14 @@
{% endspaceless %}
{% endmacro %}
{% macro navigation(links, current, top) %}
{% macro navigation(links, current, top, fmt) %}
{% set top = top|default(false) == true %}
{% set current = current|default(null) %}
{% set fmt = fmt|default('%s') %}
<ul class="mio__navigation{% if top %} mio__navigation--top{% endif %}">
{% for name, url in links %}
<li class="mio__navigation__option{% if url == current or name == current %} mio__navigation__option--selected{% endif %}"><a href="{{ url }}" class="mio__navigation__link">{{ name }}</a></li>
<li class="mio__navigation__option{% if url == current or name == current %} mio__navigation__option--selected{% endif %}"><a href="{{ fmt|format(url) }}" class="mio__navigation__link">{{ name }}</a></li>
{% endfor %}
</ul>
{% endmacro %}

View file

@ -32,7 +32,8 @@
<div class="mio__header__user__links__container">
<ul class="mio__header__user__links">
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/profile.php?u={{ app.session.user.user_id }}">Profile</a></li>
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/auth.php?m=logout&amp;s={{ app.session.session_key|md5 }}">Logout</a></li>
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/settings.php">Settings</a></li>
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/auth.php?m=logout&amp;s={{ csrf_token() }}">Logout</a></li>
</ul>
</div>
</div>

View file

@ -0,0 +1,171 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
<form method="post" action="?m=account" class="mio__settings__account">
<div class="mio__settings__account__row">
<div class="mio__settings__account__column">
<div class="mio__settings__account__title">Profile</div>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Twitter
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[twitter]" value="{{ settings_user.user_twitter }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
osu!
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[osu]" value="{{ settings_user.user_osu }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Website
</div>
<div class="mio__settings__account__input__value">
<input type="url" name="profile[website]" value="{{ settings_user.user_website }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Youtube
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[youtube]" value="{{ settings_user.user_youtube }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Steam
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[steam]" value="{{ settings_user.user_steam }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Twitch.TV
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[twitchtv]" value="{{ settings_user.user_twitchtv }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Last.fm
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[lastfm]" value="{{ settings_user.user_lastfm }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Github
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[github]" value="{{ settings_user.user_github }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Skype
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[skype]" value="{{ settings_user.user_skype }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Discord
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="profile[discord]" value="{{ settings_user.user_discord }}" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
</div>
<div class="mio__settings__account__column mio__settings__account__column--no-margin">
<div class="mio__settings__account__row">
<div class="mio__settings__account__column">
<div class="mio__settings__account__title">E-mail</div>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
New E-mail Address
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="email[new]" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Confirmation
</div>
<div class="mio__settings__account__input__value">
<input type="text" name="email[confirm]" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
</div>
</div>
<div class="mio__settings__account__row">
<div class="mio__settings__account__column">
<div class="mio__settings__account__title">Password</div>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
New Password
</div>
<div class="mio__settings__account__input__value">
<input type="password" name="password[new]" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Confirmation
</div>
<div class="mio__settings__account__input__value">
<input type="password" name="password[confirm]" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
</div>
</div>
<div class="mio__settings__account__row">
<div class="mio__settings__account__column">
<div class="mio__settings__account__title">Confirmation</div>
<label class="mio__settings__account__input">
<div class="mio__settings__account__input__name">
Current Password
</div>
<div class="mio__settings__account__input__value">
<input type="password" name="current_password" placeholder="only needed for e-mail and password updating" class="mio__input__text mio__settings__account__input__value__text">
</div>
</label>
</div>
</div>
</div>
</div>
<div class="mio__settings__account__row mio__settings__account__row--buttons">
<button class="mio__input__button" name="csrf" value="{{ csrf_token() }}">Update</button>
<button class="mio__input__button" type="reset">Reset</button>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,5 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
<p>Sorry for getting your hopes up with the button, but not yet! Read the front page while logged in.</p>
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
{% for login_attempt in user_login_attempts %}
<p>{{ login_attempt.attempt_id }}, {{ login_attempt.was_successful }}, {{ login_attempt.attempt_ip.string }}, {{ login_attempt.user_agent }}, {{ login_attempt.attempt_country }}, {{ login_attempt.created_at }}</p>
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,34 @@
{% extends '@mio/master.twig' %}
{% from '@mio/macros.twig' import navigation %}
{% set title = 'Settings » ' ~ settings_title %}
{% block content %}
{{ navigation(settings_modes|flip, settings_mode, true, '?m=%s') }}
{% block settings_container %}
{% if settings_errors is defined and settings_errors|length > 0 %}
<div class="mio__container">
<div class="mio__container__title">Information</div>
<div class="mio__container__content">
<ul class="mio__settings__errors">
{% for error in settings_errors %}
<li class="mio__settings__errors__entry">{{ error }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="mio__container mio__settings mio__settings--{{ settings_mode }}">
<div class="mio__container__title mio__settings__title mio__settings__title--{{ settings_mode }}">{{ title }}</div>
<div class="mio__container__content mio__settings__content mio__settings__content--{{ settings_mode }}">
{% block settings_content %}
This is a blank settings page.
{% endblock %}
</div>
</div>
{% endblock %}
{{ navigation(mio_navigation) }}
{% endblock %}

View file

@ -0,0 +1,5 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
<p>Could not find what you were looking for.</p>
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
{% for session in user_sessions %}
<p>{{ session.session_id }}, {{ session.session_ip.string }}, {{ session.user_agent|default('-') }}, {{ session.session_country }}, {{ session.created_at }}, {{ session.expires_on }}</p>
{% endfor %}
{% endblock %}