...
  View open merge request
Commits (17)
flexisip-account-manager/
rpmbuild/
.env.*
flexiapi/node_modules
flexiapi/public/hot
flexiapi/public/storage
flexiapi/public/css/*.style.css
flexiapi/storage/*.key
flexiapi/vendor
flexiapi/.env
......
......@@ -3,6 +3,11 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
APP_SIP_DOMAIN=
INSTANCE_COPYRIGHT=
INSTANCE_INTRO_REGISTRATION=
INSTANCE_CUSTOM_THEME=false
LOG_CHANNEL=stack
......@@ -20,3 +25,19 @@ CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
MAIL_DRIVER=
MAIL_HOST=
MAIL_PORT=2525
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME=
OVH_APP_KEY=
OVH_APP_SECRET=
OVH_APP_ENDPOINT=ovh-eu
OVH_APP_CONSUMER_KEY=
NOCAPTCHA_SECRET=secret-key
NOCAPTCHA_SITEKEY=site-key
\ No newline at end of file
This diff is collapsed.
......@@ -19,6 +19,73 @@ You can also run the test suit using `phpunit`.
To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/6.x).
### Configure the .env file
Complete all the other variables in the `.env` file:
- The OVH SMS connector
- SMTP configuration
- App name, SIP domain…
### Multi instances environement
FlexiAPI can also handle multi domains setup.
To do so, configure several web servers virtualhosts and set a specific `APP_ENV` environement variable in each of them.
With Apache, use the [mod_env](https://httpd.apache.org/docs/2.4/mod/mod_env.html) module.
SetEnv APP_ENV foobar
On nginx use `fastcgi_param` to pass the parameter directly to PHP.
location ~ [^/]\.php(/|$) {
include /etc/nginx/fastcgi_params;
fastcgi_param APP_ENV foobar;
}
Note that if `APP_ENV` is not set FlexiAPI will directly use the default `.env` file.
FlexiAPI will then try to load a custom configuration file with the following name `.env.$APP_ENV`. So for the previous example `.env.foobar`.
You can then configure your instances with specific values.
INSTANCE_COPYRIGHT="FooBar - Since 1997"
INSTANCE_INTRO_REGISTRATION="Welcome on the FooBar Server"
INSTANCE_CUSTOM_THEME=true
#### Custom theme
If you set `INSTANCE_CUSTOM_THEME` to true, FlexiAPI will try to load a CSS file located in `public/css/$APP_ENV.style.css`. If the file doesn't exists it will fallback to `public/css/style.css`.
We advise you to copy the `style.css` file and rename it to make your custom CSS configurations for your instance.
### systemd restrictions
To retrieve the devices configuration, FlexiAPI connects to the UNIX socket opened by FlexiSIP. The socket is located in the `/tmp` directory.
If you have issues connecting to that socket, please ensure that your PHP process have access to it (user, rights).
The systemd service [PrivateTmp](https://access.redhat.com/blogs/766093/posts/1976243) setting might restrict that access.
### SELinux restrictions
If you are running on a CentOS/RedHat machine, please ensure that SELinux is correctly configured.
Allow the webserver user to write in the `storage/` directory:
chcon -R -t httpd_sys_rw_content_t storage/
If you have your SQLite DB setup in another directory don't forget to allow write rights as well
chcon -R -t httpd_sys_rw_content_t db.sqlite
If your external database is locate on a remote machine, you should also allow your webserver user to connect to remote hosts:
semanage port -a -t http_port_t -p tcp 3306 // Open remote connections on the MySQL port for example
setsebool httpd_can_network_connect 1 // Allow remote network connected
setsebool httpd_can_network_connect_db 1 // Allow remote database connection
### CRON job
The DIGEST authentication method is saving some temporary information (nonces) in the database.
......
......@@ -25,20 +25,44 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
class Account extends Authenticatable
{
protected $connection = 'external';
protected $with = ['passwords'];
protected $with = ['passwords', 'admin'];
protected $dates = ['creation_time'];
public $timestamps = false;
protected static function booted()
{
static::addGlobalScope('domain', function (Builder $builder) {
$builder->where('domain', config('app.sip_domain'));
});
}
public function passwords()
{
return $this->hasMany('App\Password');
}
public function alias()
{
return $this->hasOne('App\Alias');
}
public function nonces()
{
return $this->hasMany('App\DigestNonce');
}
public function admin()
{
return $this->hasOne('App\Admin');
}
public function getIdentifierAttribute()
{
return $this->attributes['username'].'@'.$this->attributes['domain'];
}
public function isAdmin()
{
return ($this->admin);
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Admin extends Model
{
protected $connection = 'local';
protected $table = 'admins';
public function account()
{
return $this->belongsTo('App\Account');
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Alias extends Model
{
protected $table = 'aliases';
protected $connection = 'external';
public $timestamps = false;
public function account()
{
return $this->belongsTo('App\Account');
}
}
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Carbon\Carbon;
use App\Account;
class RemoveUnconfirmedAccounts extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'accounts:clear-unconfirmed {days} {--apply}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Clear unconfirmed accounts after n days';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$accounts = Account::where('activated', false)->where('creation_time', '<',
Carbon::now()->subDays($this->argument('days'))->toDateTimeString()
)->get();
if ($this->option('apply')) {
$this->info($accounts->count() . ' accounts deleted');
$accounts->delete();
} else {
$this->info($accounts->count() . ' accounts to delete');
}
}
}
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Account;
use App\Admin;
class SetAccountAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'accounts:set-admin {id}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Give the admin role to an account';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$account = Account::where('id', $this->argument('id'))->first();
if (!$account) $this->error('Account not found, please use an existing ID');
$admin = new Admin;
$admin->account_id = $account->id;
$admin->save();
}
}
......@@ -40,4 +40,16 @@ class Utils
return $nonce->nonce;
}
public static function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5')
{
$algos = ['MD5' => 'md5', 'SHA-256' => 'sha256'];
return hash($algos[$algorithm], $username.':'.$domain.':'.$password);
}
public static function generatePin()
{
return mt_rand(1000, 9999);
}
}
<?php
namespace App\Http\Controllers\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Libraries\FlexisipConnector;
class DeviceController extends Controller
{
public function index(Request $request)
{
$connector = new FlexisipConnector;
return view('account.devices.index',
['devices' => $connector->getDevices($request->user()->identifier)
->keyBy('uuid')
]);
}
public function delete(Request $request, string $uuid)
{
$connector = new FlexisipConnector;
return view('account.devices.delete',
['device' => $connector->getDevices($request->user()->identifier)
->keyBy('uuid')
->where('uuid', $uuid)
]);
}
public function destroy(string $uuid)
{
$connector = new FlexisipConnector;
$connector->deleteDevice($request->user()->identifier, $uuid);
return redirect()->route('account.device.index');
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;
use App\Account;
use App\Alias;
use App\Rules\SIP;
use App\Helpers\Utils;
use App\Libraries\OvhSMS;
use App\Mail\PasswordAuthentication;
use App\Mail\RegisterConfirmation;
class AccountController extends Controller
{
private $emailCodeSize = 14;
public function index(Request $request)
{
return view('account.index', [
'account' => $request->user()
]);
}
public function terms(Request $request)
{
return view('account.terms');
}
public function login(Request $request)
{
return view('account.login');
}
public function register(Request $request)
{
return view('account.register', [
'domain' => '@' . config('app.sip_domain')
]);
}
public function store(Request $request)
{
$request->validate([
'terms' =>'accepted',
'username' => 'required|unique:external.accounts,username|min:6',
'phone' => 'required_without:email|nullable|unique:external.aliases,alias|unique:external.accounts,username|starts_with:+|phone:AUTO',
'g-recaptcha-response' => 'required|captcha',
'email' => 'required_without:phone|nullable|email|confirmed'
]);
$account = new Account;
$account->username = $request->get('username');
$account->email = $request->get('email');
$account->activated = false;
$account->domain = config('app.sip_domain');
$account->ip_address = $request->ip();
$account->creation_time = Carbon::now();
$account->user_agent = config('app.name');
$account->save();
if ($request->filled('phone')) {
$alias = new Alias;
$alias->alias = $request->get('phone');
$alias->domain = config('app.sip_domain');
$alias->account_id = $account->id;
$alias->save();
$account->confirmation_key = Utils::generatePin();
$account->save();
$ovhSMS = new OvhSMS;
$ovhSMS->send($request->get('phone'), 'Your '.config('app.name').' validation code is '.$account->confirmation_key);
return view('account.authenticate_phone', [
'account' => $account
]);
}
$account->confirmation_key = Str::random($this->emailCodeSize);
$account->save();
Mail::to($account)->send(new RegisterConfirmation($account));
return view('account.authenticate_email', [
'account' => $account
]);
}
public function delete(Request $request)
{
return view('account.delete', [
'account' => $request->user()
]);
}
public function destroy(Request $request)
{
$request->validate(['identifier' => 'required|same:identifier_confirm']);
Auth::logout();
$request->user()->delete();
return redirect()->route('account.login');
}
public function authenticate(Request $request)
{
$request->validate([
'username' => 'required',
'password' => 'required'
]);
$account = Account::where('username', $request->get('username'))
->firstOrFail();
// Try out the passwords
foreach ($account->passwords as $password) {
if (hash_equals(
$password->password,
Utils::bchash($request->get('username'), config('app.sip_domain'), $request->get('password'), $password->algorithm)
)) {
Auth::login($account);
return redirect()->route('account.index');
}
}
return redirect()->back();
}
public function loginEmail(Request $request)
{
return view('account.login_email');
}
public function authenticateEmail(Request $request)
{
$request->validate(['email' => 'required|email|exists:external.accounts,email']);
$account = Account::where('email', $request->get('email'))->first();
$account->confirmation_key = Str::random($this->emailCodeSize);
$account->save();
Mail::to($account)->send(new PasswordAuthentication($account));
return view('account.authenticate_email', [
'account' => $account
]);
}
public function authenticateEmailConfirm(Request $request, string $code)
{
$request->merge(['code' => $code]);
$request->validate(['code' => 'required|size:'.$this->emailCodeSize]);
$account = Account::where('confirmation_key', $code)->firstOrFail();
$account->confirmation_key = null;
$account->save();
Auth::login($account);
// Ask the user to set a password
if (!$account->activated) {
return redirect()->route('account.password');
}
return redirect()->route('account.index');
}
public function loginPhone(Request $request)
{
return view('account.login_phone');
}
public function authenticatePhone(Request $request)
{
$request->validate(['phone' => 'required|starts_with:+|phone:AUTO']);
$account = Account::where('username', $request->get('phone'))->first();
// Try alias
if (!$account) {
$alias = Alias::where('alias', $request->get('phone'))->first();
if ($alias) {
$account = $alias->account;
}
}
if (!$account) {
return view('account.login_phone')->withErrors([
'phone' => 'Phone number not found'
]);
}
$account->confirmation_key = Utils::generatePin();
$account->save();
$ovhSMS = new OvhSMS;
$ovhSMS->send($request->get('phone'), 'Your '.config('app.name').' validation code is '.$account->confirmation_key);
// Ask the user to set a password
if (!$account->activated) {
return redirect()->route('account.password');
}
return view('account.authenticate_phone', [
'account' => $account
]);
}
public function authenticatePhoneConfirm(Request $request)
{
$request->validate([
'account_id' => 'required',
'code' => 'required|digits:4'
]);
$account = Account::where('id', $request->get('account_id'))->firstOrFail();
if ($account->confirmation_key != $request->get('code')) {
return view('account.login_phone')->withErrors([
'code' => 'Wrong code'
]);
}
$account->confirmation_key = null;
$account->save();
Auth::login($account);
return redirect()->route('account.index');
}
public function logout(Request $request)
{
Auth::logout();
return redirect()->route('account.login');
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Mail\ChangedEmail;
class AccountEmailController extends Controller
{
public function show(Request $request)
{
return view('account.email', [
'account' => $request->user()
]);
}
public function update(Request $request)
{
$request->validate([
'email' => 'required|confirmed|email',
]);
$account = $request->user();
$account->email = $request->get('email');
$account->save();
Mail::to($account)->send(new ChangedEmail());
return redirect()->route('account.index');
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Account;
use App\Password;
use App\Helpers\Utils;
class AccountPasswordController extends Controller
{
public function show(Request $request)
{
return view('account.password', [
'account' => $request->user()
]);
}
public function update(Request $request)
{
$request->validate([
'password' => 'required|confirmed|min:6',
]);
$account = $request->user();
$account->activated = true;
$account->save();
$algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
if ($account->passwords()->count() > 0) {
$request->validate(['old_password' => 'required']);
foreach ($account->passwords as $password) {
// If one of the password stored equals the one entered
if (hash_equals(
$password->password,
Utils::bchash($account->username, $account->domain, $request->get('old_password'), $password->algorithm)
)) {
$this->updatePassword($account, $request->get('password'), $algorithm);
return redirect()->route('account.index');
}
}
return redirect()->back()->withErrors(['old_password' => 'Old password not correct']);
} else {
// No password yet
$this->updatePassword($account, $request->get('password'), $algorithm);
return redirect()->back();
}
}
private function updatePassword(Account $account, $newPassword, $algorithm)
{
$account->passwords()->delete();
$password = new Password;
$password->account_id = $account->id;
$password->password = Utils::bchash($account->username, $account->domain, $newPassword, $algorithm);
$password->algorithm = $algorithm;
$password->save();
}
}
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Account;
use App\Admin;
class AccountController extends Controller
{
public function index(Request $request, $search = '')
{
$accounts = Account::orderBy('creation_time', 'desc');
if (!empty($search)) {
$accounts = $accounts->where('username', 'like', '%'.$search.'%');
}
return view('admin.account.index', [
'search' => $search,
'accounts' => $accounts->paginate(30)->appends($request->query())
]);
}
public function search(Request $request)
{
return redirect()->route('admin.account.index', $request->get('search'));
}
public function show(Request $request, $id)
{
return view('admin.account.show', [
'account' => Account::findOrFail($id)
]);
}
public function activate(Request $request, $id)
{
$account = Account::findOrFail($id);
$account->activated = true;
$account->save();
return redirect()->back();
}
public function deactivate(Request $request, $id)
{
$account = Account::findOrFail($id);
$account->activated = false;
$account->save();
return redirect()->back();
}
public function admin(Request $request, $id)
{
$account = Account::findOrFail($id);
$admin = new Admin;
$admin->account_id = $account->id;
$admin->save();
return redirect()->back();
}
public function unadmin(Request $request, $id)
{
$account = Account::findOrFail($id);
// An admin cannot remove it's own permission
if ($account->id == $request->user()->id) abort(403);
if ($account->admin) $account->admin->delete();
return redirect()->back();
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AdminController extends Controller
{
//
}
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ApiController extends Controller
{
public function documentation(Request $request)
{
return view('documentation');
}
}
......@@ -69,6 +69,7 @@ class Kernel extends HttpKernel
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.admin' => \App\Http\Middleware\AuthenticateAdmin::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.digest' => \App\Http\Middleware\AuthenticateDigest::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
......
......@@ -15,7 +15,7 @@ class Authenticate extends Middleware
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
return route('account.register');
}
}
}
<?php
namespace App\Http\Middleware;
use Closure;
class AuthenticateAdmin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->user()) {
return redirect()->route('account.login');
}
if (!$request->user()->isAdmin()) {
return abort(403, 'Unauthorized area');
}
return $next($request);
}
}
......@@ -27,7 +27,7 @@ class FlexisipConnector
public function __construct()
{
$pid = \trim(\shell_exec('pidof flexisip'));
$pid = file_get_contents('/var/run/flexisip-proxy.pid');
$this->_socket = stream_socket_client('unix:///tmp/flexisip-proxy-'.$pid, $errno, $errstr);
}
......
......@@ -17,18 +17,46 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers;
namespace App\Libraries;
use Illuminate\Http\Request;
use App\Device;
use Ovh\Api;
use App\Account;
class DocumentationController extends Controller
class OvhSMS
{
public function index()
private $_api;
private $_smsService;
public function __construct()
{
$this->_api = new Api(
config('ovh.app_key'),
config('ovh.app_secret'),
config('ovh.app_endpoint'),
config('ovh.app_consumer_key')
);
$smsServices = $this->_api->get('/sms/');
if (!empty($smsServices)) $this->_smsService = $smsServices[0];
}
public function send(string $to, string $message)
{
return view('documentation', [
'accounts' => Account::all(),
]);
$content = (object) [
'charset' => 'UTF-8',
'class' => 'phoneDisplay',
'coding' => '7bit',
'message' => $message,
'noStopClause' => false,
'priority' => 'high',
'receivers' => [ $to ],
'senderForResponse' => true,
'validityPeriod' => 2880
];
$resultPostJob = $this->_api->post('/sms/'. $this->_smsService . '/jobs', $content);
// One credit removed
$smsJobs = $this->_api->get('/sms/'. $this->_smsService . '/jobs');
}
}
}
\ No newline at end of file
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Account;
class ChangedEmail extends Mailable
{
use Queueable, SerializesModels;
public function build()
{
return $this->view('mails.changed_email')
->text('mails.changed_email_text');
}
}
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Account;
class PasswordAuthentication extends Mailable
{
use Queueable, SerializesModels;
private $_account;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Account $account)
{
$this->_account = $account;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('mails.authentication')
->text('mails.authentication_text')
->with([
'link' => route('account.authenticate_email_confirm', [$this->_account->confirmation_key])
]);
}
}
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Account;
class RegisterConfirmation extends Mailable
{
use Queueable, SerializesModels;
private $_account;
public function __construct(Account $account)
{
$this->_account = $account;
}
public function build()
{
return $this->view('mails.register_confirmation')
->text('mails.register_confirmation_text')
->with([
'link' => route('account.authenticate_email_confirm', [$this->_account->confirmation_key])
]);
}
}
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Str;
class SIP implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
// TODO complete me
return Str::contains($value, '@');
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The :attribute must be a SIP address.';
}
}
......@@ -9,9 +9,13 @@
"license": "MIT",
"require": {
"php": "^7.2",
"anhskohbo/no-captcha": "^3.2",
"fideloper/proxy": "^4.0",
"laravel/framework": "^6.2",
"laravel/tinker": "^2.0"
"laravel/tinker": "^2.0",
"laravelcollective/html": "^6.1",
"ovh/ovh": "^2.0",
"propaganistas/laravel-phone": "^4.2"
},
"require-dev": {
"facade/ignition": "^1.4",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -14,6 +14,7 @@ return [
*/
'name' => env('APP_NAME', 'Laravel'),
'sip_domain' => env('APP_SIP_DOMAIN', 'sip.domain.com'),
/*
|--------------------------------------------------------------------------
......
<?php
return [
'copyright' => env('INSTANCE_COPYRIGHT', null),
'intro_registration' => env('INSTANCE_INTRO_REGISTRATION', null),
'custom_theme' => env('INSTANCE_CUSTOM_THEME', false),
];
\ No newline at end of file
<?php
return [
'app_key' => env('OVH_APP_KEY', ''),
'app_secret' => env('OVH_APP_SECRET', ''),
'app_endpoint' => env('OVH_APP_ENDPOINT', 'ovh-eu'),
'app_consumer_key' => env('OVH_APP_CONSUMER_KEY', ''),
];
\ No newline at end of file
......@@ -6,14 +6,9 @@ use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
Schema::connection('local')->create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
......@@ -24,13 +19,8 @@ class CreateUsersTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
Schema::connection('local')->dropIfExists('users');
}
}
......@@ -6,27 +6,17 @@ use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
Schema::connection('local')->create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('password_resets');
Schema::connection('local')->dropIfExists('password_resets');
}
}
......@@ -6,14 +6,9 @@ use Illuminate\Support\Facades\Schema;
class CreateFailedJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('failed_jobs', function (Blueprint $table) {
Schema::connection('local')->create('failed_jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->text('connection');
$table->text('queue');
......@@ -23,13 +18,8 @@ class CreateFailedJobsTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('failed_jobs');
Schema::connection('local')->dropIfExists('failed_jobs');
}
}
......@@ -23,11 +23,6 @@ use Illuminate\Support\Facades\Schema;
class CreateAccountsPasswordsTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!Schema::connection('external')->hasTable('accounts')) {
......@@ -61,14 +56,9 @@ class CreateAccountsPasswordsTables extends Migration
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::connection('external')->dropIfExists('passwords');
Schema::connection('external')->dropIfExists('accounts');
//Schema::connection('external')->dropIfExists('passwords');
//Schema::connection('external')->dropIfExists('accounts');
}
}
......@@ -23,11 +23,6 @@ use Illuminate\Support\Facades\Schema;
class CreateNoncesDigestTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::connection('local')->create('nonces', function (Blueprint $table) {
......@@ -39,11 +34,6 @@ class CreateNoncesDigestTable extends Migration
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::connection('local')->dropIfExists('nonces');
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAdminsTable extends Migration
{
public function up()
{
Schema::connection('local')->create('admins', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('account_id')->unsigned();
$table->timestamps();
});
}
public function down()
{
Schema::connection('local')->dropIfExists('admins');
}
}
@font-face {
font-family: "Montserrat";
src: local("Montserrat Regular"), local("Montserrat-Regular"), url("https://fonts.gstatic.com/s/montserrat/v14/JTUSjIg1_i6t8kCHKm459Wlhyw.woff2") format("woff2");
font-style: normal;
font-weight: 400;
unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Montserrat";
src: local("Montserrat Bold"), local("Montserrat-Bold"), url("https://fonts.gstatic.com/s/montserrat/v14/JTURjIg1_i6t8kCHKm45_dJE3gnD_g.woff2") format("woff2");
font-style: normal;
font-weight: 700;
unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
font-family: "Montserrat";
}
body > div {
flex: 1 0 auto;
}
body > header::after,
body > footer::before {
background-image: url('/img/header.svg');
background-color: white;
background-position: bottom center;
background-repeat: repeat-x;
display: block;
height: 10rem;
width: 100%;
background-size: 40rem;
content: '';
}
@media screen and (max-width: 991px) {
nav.navbar {
display: none;
}
}
h1, h2, h3, a, label {
color: #ff733b;
}
nav ul li a {
color: white;
opacity: 0.8;
}
nav ul li a:hover,
nav ul li.active a {
color: white;
opacity: 1;
}
input.form-control {
border-color: #9ec6dc;
}
.btn {
border: 1px solid #ff733b;
background-color: transparent;
color: #ff733b;
border-radius: 10rem;
}
body > footer,
body > header nav {
background-color: #ff733b;
}
body > footer {
color: white;
padding-bottom: 1rem;
text-align: center;
}
body > header::after {
background-position: top center;
margin-bottom: -4.5rem;
}
body > footer::before {
background-image: url('/img/footer.svg');
height: 9rem;
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="481.54166mm"
height="54.655354mm"
viewBox="0 0 481.54166 54.655354"
version="1.1"
id="svg8"
inkscape:version="1.0rc1 (09960d6f05, 2020-04-09)"
sodipodi:docname="footer.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.98"
inkscape:cx="104.62963"
inkscape:cy="93.881695"
inkscape:document-units="mm"
inkscape:current-layer="g26"
inkscape:document-rotation="0"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1027"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="8.0766935e-06,54.504169"
orientation="1,0"
id="guide42" />
<sodipodi:guide
position="481.54165,-9.0933001e-07"
orientation="1,0"
id="guide44" />
<sodipodi:guide
position="1.0000693e-05,34.883781"
orientation="0,-1"
id="guide887" />
<sodipodi:guide
position="1.0000693e-05,3.0868056"
orientation="0,-1"
id="guide889" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(327.89435,-68.791666)">
<g
id="g26"
transform="translate(9.2773437e-7,54.308819)">
<path
style="fill:#ffb9a0;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -327.89434,14.634035 121.11229,27.528297 62.6859,-9.362178 26.45833,-18.317307 55.766026,24.423076 92.807691,-24.016025 81.124273,17.952698 41.58713,-17.923294 1e-5,54.218899 h -481.54165 z"
id="path36"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccc" />
<path
style="fill:#ff9e79;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -327.89434,34.25442 109.3078,-14.886957 34.19231,10.875621 30.52885,-1.10639 33.3782,11.397435 53.560418,-14.363353 62.3326405,19.188794 40.0075805,-14.594673 66.241771,22.215676 39.77373,-25.273923 12.21834,6.54777 1e-5,34.883781 -481.54166,1e-6 z"
id="path38"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccc" />
<path
style="fill:#ff723b;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -327.89434,69.138201 0,-3.086806 58.83344,-21.446755 69.19693,6.163152 31.34474,-3.720844 25.64423,9.362181 79.375,7.733974 58.9058785,-18.783533 50.7612655,3.720841 37.576541,-14.825991 43.835825,14.201899 26.06779,17.595076 1e-5,3.086806 z"
id="path40"