Base authentication system + layout

This commit is contained in:
Timothée Jaussoin 2020-04-09 17:22:18 +02:00
parent 73308bc9d6
commit e9e9d09b37
19 changed files with 1022 additions and 472 deletions

View file

@ -40,4 +40,11 @@ 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);
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Account;
use App\Rules\SIP;
use App\Helpers\Utils;
class AccountController extends Controller
{
public function index(Request $request)
{
return view('account.index', [
'account' => $request->user()
]);
}
public function authenticate(Request $request)
{
$request->validate([
'username' => ['required', new SIP],
'password' => 'required'
]);
list($username, $domain) = explode('@', $request->get('username'));
$account = Account::where('username', $username)
->where('domain', $domain)
->firstOrFail();
// Try out the passwords
foreach ($account->passwords as $password) {
if (hash_equals(
$password->password,
Utils::bchash($username, $domain, $request->get('password'), $password->algorithm)
)) {
Auth::login($account);
return redirect()->route('account.index');
}
}
return redirect()->back();
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AdminController extends Controller
{
//
}

View file

@ -0,0 +1,14 @@
<?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');
}
}

View file

@ -1,34 +0,0 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2019 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Account;
class DocumentationController extends Controller
{
public function index()
{
return view('documentation', [
'accounts' => Account::all(),
]);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Account;
class HomeController extends Controller
{
public function index(Request $request)
{
return view('home');
}
public function logout(Request $request)
{
Auth::logout();
return redirect()->route('home');
}
}

View file

@ -15,7 +15,7 @@ class Authenticate extends Middleware
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
return route('home');
}
}
}

View file

@ -0,0 +1,42 @@
<?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.';
}
}

View file

@ -11,7 +11,8 @@
"php": "^7.2",
"fideloper/proxy": "^4.0",
"laravel/framework": "^6.2",
"laravel/tinker": "^2.0"
"laravel/tinker": "^2.0",
"laravelcollective/html": "^6.1"
},
"require-dev": {
"facade/ignition": "^1.4",

1149
flexiapi/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
@extends('layouts.account')
@section('content')
{{ $account->identifier }}
@endsection

View file

@ -1,33 +1,25 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@extends('layouts.main')
<title>Flexisip API</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<div class="container-lg pt-3">
<h1>Flexisip API</h1>
<p>An API to deal with the Flexisip server</p>
@section('content')
<h1>Flexisip API</h1>
<p>An API to deal with the Flexisip server</p>
<p>The API is available under <code>/api</code></p>
<p>A <code>from</code> (consisting of the user SIP address, prefixed with <code>sip:</code>), <code>content-type</code> and <code>accept</code> HTTP headers are required to use the API properly</p>
<p>The API is available under <code>/api</code></p>
<p>A <code>from</code> (consisting of the user SIP address, prefixed with <code>sip:</code>), <code>content-type</code> and <code>accept</code> HTTP headers are required to use the API properly</p>
<pre>
<pre>
<code>> GET /api/{endpoint}
> from: sip:foobar@sip.example.org
> content-type: application/json
> accept: application/json</code></pre>
<h2>Authentication</h2>
<p>Restricted endpoints are protected using a DIGEST authentication mechanism.</p>
<h2>Authentication</h2>
<p>Restricted endpoints are protected using a DIGEST authentication mechanism.</p>
<p>To discover the available hashing algorythm you MUST send an unauthenticated request to one of the restricted endpoints.<br />
For the moment only DIGEST-MD5 and DIGEST-SHA-256 are supported through the authentication layer.</p>
<p>To discover the available hashing algorythm you MUST send an unauthenticated request to one of the restricted endpoints.<br />
For the moment only DIGEST-MD5 and DIGEST-SHA-256 are supported through the authentication layer.</p>
<pre>
<pre>
<code>> GET /api/{restricted-endpoint}
>
@ -36,19 +28,17 @@
< www-authenticate: Digest realm=test,qop=auth,algorithm=MD5,nonce="{nonce}",opaque="{opaque}"
< www-authenticate: Digest realm=test,qop=auth,algorithm=SHA-256,nonce="{nonce}",opaque="{opaque}"</code></pre>
<p>You can find more documentation on the related <a href="https://tools.ietf.org/html/rfc7616">IETF RFC-7616</a>.</p>
<p>You can find more documentation on the related <a href="https://tools.ietf.org/html/rfc7616">IETF RFC-7616</a>.</p>
<h2>Endpoints</h2>
<h2>Endpoints</h2>
<p>Current implemented endpoints</p>
<p>Current implemented endpoints</p>
<h4><code>GET /devices</code></h4>
<h4><code>GET /devices</code></h4>
<p>Return the user registered devices.</p>
<p>Return the user registered devices.</p>
<h4><code>DELETE /devices/{uuid}</code></h4>
<h4><code>DELETE /devices/{uuid}</code></h4>
<p>Remove one of the user registered devices.</p>
</div>
</body>
</html>
<p>Remove one of the user registered devices.</p>
@endsection

View file

@ -0,0 +1,26 @@
@extends('layouts.main')
@section('content')
@if (Auth::check())
<div class="alert alert-primary" role="alert">
<a class="float-right" href="{{ route('logout') }}">Logout</a>
You are already authenticated
</div>
@else
<div class="card mt-3">
<div class="card-body">
{!! Form::open(['route' => 'account.authenticate']) !!}
<div class="form-group">
{!! Form::label('username', 'Username') !!}
{!! Form::text('username', old('username'), ['class' => 'form-control', 'placeholder' => 'username@sip.linphone.org', 'required']) !!}
</div>
<div class="form-group">
{!! Form::label('password', 'Password') !!}
{!! Form::password('password', ['class' => 'form-control', 'placeholder' => 'myPassword', 'required']) !!}
</div>
{!! Form::submit('Authenticate', ['class' => 'btn btn-primary']) !!}
{!! Form::close() !!}
</div>
</div>
@endif
@endsection

View file

@ -0,0 +1,25 @@
@extends('layouts.base')
@section('body')
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="collapse navbar-collapse" >
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">FlexiAPI</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('account.index') }}">My Account</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="{{ route('logout') }}">Logout</a>
</li>
</ul>
</div>
</nav>
<div class="container-lg pt-3">
@include('parts.errors')
@yield('content')
</div>
@endsection

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flexisip API</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
@yield('body')
</body>
</html>

View file

@ -0,0 +1,25 @@
@extends('layouts.base')
@section('body')
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="collapse navbar-collapse" >
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">FlexiAPI</a>
</li>
@if (isset($user) && get_class($user) == 'App\Account')
<li class="nav-item active">
<a class="nav-link" href="{{ route('account.index') }}">My Account</a>
</li>
@endif
<li class="nav-item">
<a class="nav-link" href="{{ route('api') }}">API</a>
</li>
</ul>
</div>
</nav>
<div class="container-lg pt-3">
@include('parts.errors')
@yield('content')
</div>
@endsection

View file

@ -0,0 +1,9 @@
@if (isset($errors) && $errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

View file

@ -19,6 +19,8 @@
use Illuminate\Http\Request;
Route::get('/', 'Api\ApiController@documentation')->name('api');
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});

View file

@ -17,4 +17,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
Route::get('/', 'DocumentationController@index');
Route::get('/', 'HomeController@index')->name('home');
Route::get('logout', 'HomeController@logout')->name('logout');
Route::post('account/authenticate', 'AccountController@authenticate')->name('account.authenticate');
Route::group(['middleware' => 'auth'], function () {
Route::get('account', 'AccountController@index')->name('account.index');
});