Update wording

Send a confirmation email when the password is set for the first time
Remove the API link from the menu and move it to the footer
Allow different domains to be set in the POST /api/accounts endpoints + related tests
Cleanup the API tests
Update the dependencies
This commit is contained in:
Timothée Jaussoin 2020-09-07 16:53:58 +02:00
parent ad29ce0722
commit 30fcf9792f
40 changed files with 356 additions and 213 deletions

View file

@ -3,9 +3,12 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
APP_SIP_DOMAIN=
APP_SIP_DOMAIN=sip.example.com
APP_FLEXISIP_PROXY_PID=/var/run/flexisip-proxy.pid
ACCOUNT_PROXY_REGISTRAR_ADDRESS=sip.example.com
ACCOUNT_TRANSPORT_PROTOCOL="TLS (recommended), TCP or UDP"
INSTANCE_COPYRIGHT=
INSTANCE_INTRO_REGISTRATION=
INSTANCE_CUSTOM_THEME=false
@ -37,6 +40,7 @@ MAIL_FROM_NAME=
MAIL_ALLOW_SELF_SIGNED=false
MAIL_VERIFY_PEER=true
MAIL_VERIFY_PEER_NAME=true
MAIL_SIGNATURE="The Linphone Team"
NEWSLETTER_REGISTRATION_ADDRESS=
PHONE_AUTHENTICATION=true

View file

@ -28,7 +28,7 @@ class AuthenticateController extends Controller
public function authenticate(Request $request)
{
$request->validate([
'username' => 'required',
'username' => 'required|exists:external.accounts,username',
'password' => 'required'
]);

View file

@ -4,10 +4,12 @@ namespace App\Http\Controllers\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Account;
use App\Password;
use App\Helpers\Utils;
use App\Mail\ConfirmedRegistration;
class PasswordController extends Controller
{
@ -21,7 +23,7 @@ class PasswordController extends Controller
public function update(Request $request)
{
$request->validate([
'password' => 'required|confirmed|min:6',
'password' => 'required|confirmed|filled',
]);
$account = $request->user();
@ -40,6 +42,8 @@ class PasswordController extends Controller
Utils::bchash($account->username, $account->domain, $request->get('old_password'), $password->algorithm)
)) {
$this->updatePassword($account, $request->get('password'), $algorithm);
$request->session()->flash('success', 'Password successfully changed');
return redirect()->route('account.panel');
}
}
@ -48,9 +52,14 @@ class PasswordController extends Controller
} else {
// No password yet
$this->updatePassword($account, $request->get('password'), $algorithm);
$request->session()->flash('success', 'Password changed');
return redirect()->back();
if (!empty($account->email)) {
Mail::to($account)->send(new ConfirmedRegistration($account));
}
$request->session()->flash('success', 'Password successfully set. Your SIP account creation process is now finished.');
return redirect()->route('account.panel');
}
}

View file

@ -11,6 +11,7 @@ use Carbon\Carbon;
use App\Account;
use App\Alias;
use App\Rules\SIP;
use App\Rules\WithoutSpaces;
use App\Helpers\Utils;
use App\Libraries\OvhSMS;
use App\Mail\RegisterConfirmation;
@ -47,7 +48,9 @@ class RegisterController extends Controller
{
$request->validate([
'terms' => 'accepted',
'username' => 'required|unique:external.accounts,username|min:6',
'username' => [
'required', 'unique:external.accounts,username', 'filled', new WithoutSpaces
],
'g-recaptcha-response' => 'required|captcha',
'email' => 'required|email|confirmed'
]);
@ -81,13 +84,17 @@ class RegisterController extends Controller
{
$request->validate([
'terms' =>'accepted',
'username' => 'unique:external.accounts,username|nullable|min:6',
'phone' => 'required|unique:external.aliases,alias|unique:external.accounts,username|starts_with:+|phone:AUTO',
'username' => 'unique:external.accounts,username|nullable|filled',
'phone' => [
'required', 'unique:external.aliases,alias',
'unique:external.accounts,username',
new WithoutSpaces, 'starts_with:+', 'phone:AUTO'
],
'g-recaptcha-response' => 'required|captcha',
]);
$account = new Account;
$account->username = $request->has('username')
$account->username = !empty($request->get('username'))
? $request->get('username')
: $request->get('phone');

View file

@ -15,9 +15,10 @@ class AccountController extends Controller
public function store(Request $request)
{
$request->validate([
'username' => 'required|unique:external.accounts,username|min:6',
'username' => 'required|unique:external.accounts,username|filled',
'algorithm' => 'required|in:SHA-256,MD5',
'password' => 'required|min:6',
'password' => 'required|filled',
'domain' => 'min:3',
]);
$algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
@ -26,7 +27,9 @@ class AccountController extends Controller
$account->username = $request->get('username');
$account->email = $request->get('email');
$account->activated = true;
$account->domain = config('app.sip_domain');
$account->domain = $request->has('domain')
? $request->get('domain')
: config('app.sip_domain');
$account->ip_address = $request->ip();
$account->creation_time = Carbon::now();
$account->user_agent = config('app.name');

View file

@ -0,0 +1,29 @@
<?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 ConfirmedRegistration extends Mailable
{
use Queueable, SerializesModels;
private $_account;
public function __construct(Account $account)
{
$this->_account = $account;
}
public function build()
{
return $this->view('mails.confirmed_registration')
->text('mails.confirmed_registration_text')
->with(['account' => $this->_account]);
}
}

View file

@ -15,21 +15,11 @@ class PasswordAuthentication extends Mailable
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')

View file

@ -6,21 +6,11 @@ use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//

View file

@ -7,34 +7,17 @@ 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

@ -0,0 +1,23 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class WithoutSpaces implements Rule
{
public function __construct()
{
//
}
public function passes($attribute, $value)
{
return preg_match('/^\S*$/u', $value);
}
public function message()
{
return 'The :attribute contains spaces';
}
}

126
flexiapi/composer.lock generated
View file

@ -8,21 +8,21 @@
"packages": [
{
"name": "anhskohbo/no-captcha",
"version": "3.2.1",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/anhskohbo/no-captcha.git",
"reference": "6386365b76cfa6a6043dc2783a2a615c8574faf9"
"reference": "f654a4093bd2e9ddbdfad3297bb7aa142a55e611"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/anhskohbo/no-captcha/zipball/6386365b76cfa6a6043dc2783a2a615c8574faf9",
"reference": "6386365b76cfa6a6043dc2783a2a615c8574faf9",
"url": "https://api.github.com/repos/anhskohbo/no-captcha/zipball/f654a4093bd2e9ddbdfad3297bb7aa142a55e611",
"reference": "f654a4093bd2e9ddbdfad3297bb7aa142a55e611",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.2|^7.0",
"illuminate/support": "^5.0|^6.0|^7.0",
"illuminate/support": "^5.0|^6.0|^7.0|^8.0",
"php": ">=5.5.5"
},
"require-dev": {
@ -64,7 +64,7 @@
"no-captcha",
"recaptcha"
],
"time": "2020-08-06T05:45:09+00:00"
"time": "2020-09-10T02:31:52+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
@ -322,16 +322,16 @@
},
{
"name": "egulias/email-validator",
"version": "2.1.19",
"version": "2.1.20",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "840d5603eb84cc81a6a0382adac3293e57c1c64c"
"reference": "f46887bc48db66c7f38f668eb7d6ae54583617ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/840d5603eb84cc81a6a0382adac3293e57c1c64c",
"reference": "840d5603eb84cc81a6a0382adac3293e57c1c64c",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f46887bc48db66c7f38f668eb7d6ae54583617ff",
"reference": "f46887bc48db66c7f38f668eb7d6ae54583617ff",
"shasum": ""
},
"require": {
@ -376,7 +376,7 @@
"validation",
"validator"
],
"time": "2020-08-08T21:28:19+00:00"
"time": "2020-09-06T13:44:32+00:00"
},
{
"name": "fideloper/proxy",
@ -740,16 +740,16 @@
},
{
"name": "laravel/framework",
"version": "v6.18.38",
"version": "v6.18.40",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "94a29cadcc0d48b89b4aa820f414fb7721ba0e22"
"reference": "e42450df0896b7130ccdb5290a114424e18887c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/94a29cadcc0d48b89b4aa820f414fb7721ba0e22",
"reference": "94a29cadcc0d48b89b4aa820f414fb7721ba0e22",
"url": "https://api.github.com/repos/laravel/framework/zipball/e42450df0896b7130ccdb5290a114424e18887c9",
"reference": "e42450df0896b7130ccdb5290a114424e18887c9",
"shasum": ""
},
"require": {
@ -885,7 +885,7 @@
"framework",
"laravel"
],
"time": "2020-09-01T13:40:51+00:00"
"time": "2020-09-09T15:02:20+00:00"
},
{
"name": "laravel/tinker",
@ -953,28 +953,28 @@
},
{
"name": "laravelcollective/html",
"version": "v6.1.2",
"version": "v6.2.0",
"source": {
"type": "git",
"url": "https://github.com/LaravelCollective/html.git",
"reference": "5ef9a3c9ae2423fe5618996f3cde375d461a3fc6"
"reference": "3bb99be7502feb2129b375cd026ccb0fa4b66628"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/LaravelCollective/html/zipball/5ef9a3c9ae2423fe5618996f3cde375d461a3fc6",
"reference": "5ef9a3c9ae2423fe5618996f3cde375d461a3fc6",
"url": "https://api.github.com/repos/LaravelCollective/html/zipball/3bb99be7502feb2129b375cd026ccb0fa4b66628",
"reference": "3bb99be7502feb2129b375cd026ccb0fa4b66628",
"shasum": ""
},
"require": {
"illuminate/http": "^6.0|^7.0",
"illuminate/routing": "^6.0|^7.0",
"illuminate/session": "^6.0|^7.0",
"illuminate/support": "^6.0|^7.0",
"illuminate/view": "^6.0|^7.0",
"illuminate/http": "^6.0|^7.0|^8.0",
"illuminate/routing": "^6.0|^7.0|^8.0",
"illuminate/session": "^6.0|^7.0|^8.0",
"illuminate/support": "^6.0|^7.0|^8.0",
"illuminate/view": "^6.0|^7.0|^8.0",
"php": ">=7.2.5"
},
"require-dev": {
"illuminate/database": "^6.0|^7.0",
"illuminate/database": "^6.0|^7.0|^8.0",
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~7.1"
},
@ -1017,20 +1017,20 @@
],
"description": "HTML and Form Builders for the Laravel Framework",
"homepage": "https://laravelcollective.com",
"time": "2020-05-19T18:02:16+00:00"
"time": "2020-09-07T19:59:40+00:00"
},
{
"name": "league/commonmark",
"version": "1.5.4",
"version": "1.5.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "21819c989e69bab07e933866ad30c7e3f32984ba"
"reference": "45832dfed6007b984c0d40addfac48d403dc6432"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/21819c989e69bab07e933866ad30c7e3f32984ba",
"reference": "21819c989e69bab07e933866ad30c7e3f32984ba",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/45832dfed6007b984c0d40addfac48d403dc6432",
"reference": "45832dfed6007b984c0d40addfac48d403dc6432",
"shasum": ""
},
"require": {
@ -1042,7 +1042,7 @@
},
"require-dev": {
"cebe/markdown": "~1.0",
"commonmark/commonmark.js": "0.29.1",
"commonmark/commonmark.js": "0.29.2",
"erusev/parsedown": "~1.0",
"ext-json": "*",
"github/gfm": "0.29.0",
@ -1112,7 +1112,7 @@
"type": "tidelift"
}
],
"time": "2020-08-18T01:19:12+00:00"
"time": "2020-09-13T14:44:46+00:00"
},
{
"name": "league/flysystem",
@ -1403,16 +1403,16 @@
},
{
"name": "nesbot/carbon",
"version": "2.39.0",
"version": "2.39.2",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "0a41ea7f7fedacf307b7a339800e10356a042918"
"reference": "326efde1bc09077a26cb77f6e2e32e13f06c27f2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0a41ea7f7fedacf307b7a339800e10356a042918",
"reference": "0a41ea7f7fedacf307b7a339800e10356a042918",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/326efde1bc09077a26cb77f6e2e32e13f06c27f2",
"reference": "326efde1bc09077a26cb77f6e2e32e13f06c27f2",
"shasum": ""
},
"require": {
@ -1425,7 +1425,7 @@
"doctrine/orm": "^2.7",
"friendsofphp/php-cs-fixer": "^2.14 || ^3.0",
"kylekatarnls/multi-tester": "^2.0",
"phpmd/phpmd": "^2.8",
"phpmd/phpmd": "^2.9",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12.35",
"phpunit/phpunit": "^7.5 || ^8.0",
@ -1488,7 +1488,7 @@
"type": "tidelift"
}
],
"time": "2020-08-24T12:35:58+00:00"
"time": "2020-09-10T12:16:42+00:00"
},
{
"name": "nikic/php-parser",
@ -1544,16 +1544,16 @@
},
{
"name": "opis/closure",
"version": "3.5.6",
"version": "3.5.7",
"source": {
"type": "git",
"url": "https://github.com/opis/closure.git",
"reference": "e8d34df855b0a0549a300cb8cb4db472556e8aa9"
"reference": "4531e53afe2fc660403e76fb7644e95998bff7bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opis/closure/zipball/e8d34df855b0a0549a300cb8cb4db472556e8aa9",
"reference": "e8d34df855b0a0549a300cb8cb4db472556e8aa9",
"url": "https://api.github.com/repos/opis/closure/zipball/4531e53afe2fc660403e76fb7644e95998bff7bf",
"reference": "4531e53afe2fc660403e76fb7644e95998bff7bf",
"shasum": ""
},
"require": {
@ -1601,7 +1601,7 @@
"serialization",
"serialize"
],
"time": "2020-08-11T08:46:50+00:00"
"time": "2020-09-06T17:02:15+00:00"
},
{
"name": "ovh/ovh",
@ -1750,22 +1750,22 @@
},
{
"name": "propaganistas/laravel-phone",
"version": "4.2.4",
"version": "4.2.5",
"source": {
"type": "git",
"url": "https://github.com/Propaganistas/Laravel-Phone.git",
"reference": "e207b24c3d51623aaceda784c6ba0a7b9d9de2f9"
"reference": "090a5617cfd5a565df359d3b8bd680b0f87ad99e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Propaganistas/Laravel-Phone/zipball/e207b24c3d51623aaceda784c6ba0a7b9d9de2f9",
"reference": "e207b24c3d51623aaceda784c6ba0a7b9d9de2f9",
"url": "https://api.github.com/repos/Propaganistas/Laravel-Phone/zipball/090a5617cfd5a565df359d3b8bd680b0f87ad99e",
"reference": "090a5617cfd5a565df359d3b8bd680b0f87ad99e",
"shasum": ""
},
"require": {
"giggsey/libphonenumber-for-php": "^7.0|^8.0",
"illuminate/support": "^6.0|^7.0",
"illuminate/validation": "^6.0|^7.0",
"illuminate/support": "^6.0|^7.0|^8.0",
"illuminate/validation": "^6.0|^7.0|^8.0",
"league/iso3166": "^2.0",
"php": ">=7.1"
},
@ -1807,7 +1807,7 @@
"phone",
"validation"
],
"time": "2020-06-22T06:43:41+00:00"
"time": "2020-09-08T18:43:48+00:00"
},
{
"name": "psr/container",
@ -3895,16 +3895,16 @@
},
{
"name": "symfony/service-contracts",
"version": "v2.1.3",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "58c7475e5457c5492c26cc740cc0ad7464be9442"
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/58c7475e5457c5492c26cc740cc0ad7464be9442",
"reference": "58c7475e5457c5492c26cc740cc0ad7464be9442",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
"shasum": ""
},
"require": {
@ -3917,7 +3917,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -3967,7 +3967,7 @@
"type": "tidelift"
}
],
"time": "2020-07-06T13:23:11+00:00"
"time": "2020-09-07T11:33:47+00:00"
},
{
"name": "symfony/translation",
@ -4061,16 +4061,16 @@
},
{
"name": "symfony/translation-contracts",
"version": "v2.1.3",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
"reference": "616a9773c853097607cf9dd6577d5b143ffdcd63"
"reference": "77ce1c3627c9f39643acd9af086631f842c50c4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/616a9773c853097607cf9dd6577d5b143ffdcd63",
"reference": "616a9773c853097607cf9dd6577d5b143ffdcd63",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/77ce1c3627c9f39643acd9af086631f842c50c4d",
"reference": "77ce1c3627c9f39643acd9af086631f842c50c4d",
"shasum": ""
},
"require": {
@ -4082,7 +4082,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
"dev-master": "2.2-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -4132,7 +4132,7 @@
"type": "tidelift"
}
],
"time": "2020-07-06T13:23:11+00:00"
"time": "2020-09-07T11:33:47+00:00"
},
{
"name": "symfony/var-dumper",

View file

@ -16,9 +16,13 @@ return [
'name' => env('APP_NAME', 'Laravel'),
'sip_domain' => env('APP_SIP_DOMAIN', 'sip.domain.com'),
'flexisip_proxy_pid' => env('APP_FLEXISIP_PROXY_PID', '/var/run/flexisip-proxy.pid'),
'newsletter_registration_address' => env('NEWSLETTER_REGISTRATION_ADDRESS', ''),
'phone_authentication' => env('PHONE_AUTHENTICATION', true),
'proxy_registrar_address' => env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com'),
'transport_protocol' => env('ACCOUNT_TRANSPORT_PROTOCOL', 'TLS (recommended), TCP or UDP'),
/*
|--------------------------------------------------------------------------
| Application Environment

View file

@ -1,6 +1,7 @@
<?php
return [
'signature' => env('MAIL_SIGNATURE', 'The Linphone Team'),
/*
|--------------------------------------------------------------------------

View file

@ -27,19 +27,14 @@ body > div {
}
.container {
max-width: 960px;
max-width: 800px;
}
/*
@media screen and (max-width: 991px) {
nav.navbar {
display: none;
}
}*/
h1, h2, h3, a, label {
color: #6081C9;
}
body > footer a,
nav ul li a {
color: white;
opacity: 0.8;
@ -100,15 +95,3 @@ body > footer {
padding: 1rem;
text-align: center;
}
/*
body > footer::before {
background-image: url('/img/footer.svg');
height: 9rem;
background-color: white;
background-position: bottom center;
background-repeat: repeat-x;
display: block;
width: 100%;
background-size: 40rem;
content: '';
}*/

View file

@ -53,6 +53,7 @@ h1, h2, h3, a, label {
color: #ff733b;
}
body > footer a,
nav ul li a {
color: white;
opacity: 0.8;

View file

@ -5,9 +5,9 @@
@include('parts.already_auth')
@else
@if ($account->activated)
<p>A unique authentication link was sent by email to <b>{{ $account->email }}</b></p>
<p>A unique authentication link was sent by email to <b>{{ $account->email }}</b>.</p>
@else
<p>To finish your registration process and set a password please follow the link sent on your email address <b>{{ $account->email }}</b></p>
<p>To finish your registration process and set a password please follow the link sent on your email address <b>{{ $account->email }}</b>.</p>
@endif
@endif
@endsection

View file

@ -4,7 +4,12 @@
@if (Auth::check())
@include('parts.already_auth')
@else
<div class="card mt-3">
@if ($account->activated)
<p>Please enter the PIN code was sent to your phone number to finish your authentication.</p>
@else
<p>To finish your registration process and set a password please enter the PIN code you just received by SMS.</p>
@endif
<div class="card mt-1">
<div class="card-body">
{!! Form::open(['route' => 'account.authenticate.phone_confirm']) !!}
<div class="form-group">
@ -12,7 +17,7 @@
{!! Form::hidden('account_id', $account->id) !!}
{!! Form::text('code', old('code'), ['class' => 'form-control', 'placeholder' => '1234', 'required']) !!}
</div>
{!! Form::submit('Authenticate', ['class' => 'btn btn-primary btn-centered']) !!}
{!! Form::submit('Login', ['class' => 'btn btn-primary btn-centered']) !!}
{!! Form::close() !!}
</div>
</div>

View file

@ -11,7 +11,7 @@
<div class="form-group">
{!! Form::label('identifier', 'Username') !!}
{!! Form::text('identifier', old('identifier'), ['class' => 'form-control', 'placeholder' => 'username@server.com', 'required']) !!}
{!! Form::text('identifier', old('identifier'), ['class' => 'form-control', 'placeholder' => 'bob@example.net', 'required']) !!}
</div>
{!! Form::hidden('identifier_confirm', $account->identifier) !!}

View file

@ -13,11 +13,11 @@
{!! Form::open(['route' => 'account.email.update']) !!}
<div class="form-group">
{!! Form::label('email', 'New email') !!}
{!! Form::email('email', old('email'), ['class' => 'form-control', 'placeholder' => 'username@server.com', 'required']) !!}
{!! Form::email('email', old('email'), ['class' => 'form-control', 'placeholder' => 'bob@example.net', 'required']) !!}
</div>
<div class="form-group">
{!! Form::label('email_confirmation', 'Email confirmation') !!}
{!! Form::email('email_confirmation', old('email_confirm'), ['class' => 'form-control', 'placeholder' => 'username@server.com', 'required']) !!}
{!! Form::email('email_confirmation', old('email_confirm'), ['class' => 'form-control', 'placeholder' => 'bob@example.net', 'required']) !!}
</div>
{!! Form::hidden('email_current', $account->email) !!}

View file

@ -4,7 +4,7 @@
<h2>{{ config('app.name') }}</h2>
<p>There are {{ $count }} users registered with this service.</p>
<p>There are {{ number_format($count) }} users registered with this service.</p>
@if (config('instance.intro_registration'))
<p>{!! nl2br(config('instance.intro_registration')) !!}</p>

View file

@ -28,7 +28,7 @@
</div>
</div>
{!! Form::submit('Authenticate', ['class' => 'btn btn-primary btn-centered mt-1']) !!}
{!! Form::submit('Login', ['class' => 'btn btn-primary btn-centered mt-1']) !!}
{!! Form::close() !!}

View file

@ -9,7 +9,7 @@
{!! Form::open(['route' => 'account.authenticate.email']) !!}
<div class="form-group">
{!! Form::label('email', 'Email') !!}
{!! Form::email('email', old('email'), ['class' => 'form-control', 'placeholder' => 'myemail@address.org', 'required']) !!}
{!! Form::email('email', old('email'), ['class' => 'form-control', 'placeholder' => 'bob@example.com', 'required']) !!}
</div>
<div class="form-group">
{!! Form::label('username', 'SIP Username') !!}

View file

@ -55,4 +55,19 @@
</div>
@endif
<h3 class="mt-3">Account information</h3>
<div class="list-group">
<b>SIP address:</b> sip:{{ $account->identifier }}<br />
<b>Username:</b> {{ $account->username }}<br />
<b>Domain:</b> {{ $account->domain }}<br />
<br />
@if (!empty(config('app.proxy_registrar_address')))
<b>Proxy/registrar address: </b> sip:{{ config('app.proxy_registrar_address') }}<br />
@endif
@if (!empty(config('app.transport_protocol')))
<b>Transport: </b> {{ config('app.transport_protocol') }} <br />
@endif
</div>
@endsection

View file

@ -4,7 +4,7 @@
<p class="text-center">
You already have an account?
<a class="ml-2 btn btn-primary btn-sm" href="{{ route('account.login') }}">Authenticate</a>
<a class="ml-2 btn btn-primary btn-sm" href="{{ route('account.login') }}">Login</a>
</p>
<hr />

View file

@ -22,11 +22,11 @@
<div class="form-row">
<div class="form-group col-md-6">
{!! Form::label('email', 'Email') !!}
{!! Form::email('email', old('email'), ['class' => 'form-control', 'placeholder' => 'username@server.com']) !!}
{!! Form::email('email', old('email'), ['class' => 'form-control', 'placeholder' => 'bob@example.net']) !!}
</div>
<div class="form-group col-md-6">
{!! Form::label('email_confirmation', 'Email confirmation') !!}
{!! Form::email('email_confirmation', old('email_confirm'), ['class' => 'form-control', 'placeholder' => 'username@server.com']) !!}
{!! Form::email('email_confirmation', old('email_confirm'), ['class' => 'form-control', 'placeholder' => 'bob@example.net']) !!}
</div>
</div>

View file

@ -6,7 +6,7 @@
{!! Form::open(['route' => 'account.store.phone']) !!}
<p>Fill a phone number and a username (optional) you will then be able to set a password to finish the registration process.</p>
<p>Please enter your phone number and optionally a username. When the username is set, it will identify you on the service: other users won't need to know your phone number to call you.<br />The next step of the registration procedure will ask you to setup a password.</p>
<div class="form-group">
{!! Form::label('phone', 'Phone number') !!}

View file

@ -42,6 +42,7 @@ For the moment only DIGEST-MD5 and DIGEST-SHA-256 are supported through the auth
<li><code>username</code> unique username, minimum 6 characters</li>
<li><code>password</code> required minimum 6 characters</li>
<li><code>algorithm</code> required, values can be <code>SHA-256</code> or <code>MD5</code></li>
<li><code>domain</code> optional, the value is set to the default registration domain if not set</li>
</ul>
<p>To create an account directly from the API.<br />This endpoint is authenticated and requires an admin account.</p>

View file

@ -22,8 +22,9 @@
@yield('body')
<footer class="text-center mt-2">
@if (config('instance.copyright'))
{{ config('instance.copyright') }}
{{ config('instance.copyright') }} |
@endif
<a href="{{ route('api') }}">API</a>
</footer>
</body>
</html>

View file

@ -12,9 +12,6 @@
<a class="nav-link" href="{{ route('account.panel') }}">My Account</a>
</li>
@endif
<li class="nav-item @if (request()->routeIs('api')) active @endif">
<a class="nav-link" href="{{ route('api') }}">API</a>
</li>
</ul>
<ul class="navbar-nav">
@ -22,7 +19,7 @@
<a class="nav-link" href="{{ route('account.register') }}">Register</a>
</li>
<li class="nav-item @if (request()->routeIs('account.login')) active @endif">
<a class="nav-link" href="{{ route('account.login') }}">Authenticate</a>
<a class="nav-link" href="{{ route('account.login') }}">Login</a>
</li>
</ul>
</div>

View file

@ -13,7 +13,7 @@
</p>
<p>
Regards,<br />
The Linphone team.
{{ config('mail.signature') }}
</p>
</body>
</html>

View file

@ -6,4 +6,4 @@ Please follow the unique link bellow to finish the authentication process.
{{ $link }}
Regards,
The Linphone team.
{{ config('mail.signature') }}

View file

@ -9,7 +9,7 @@
</p>
<p>
Regards,<br />
The Linphone team.
{{ config('mail.signature') }}
</p>
</body>
</html>

View file

@ -3,4 +3,4 @@ Hello,
You have changed your email address to the current one on {{ config('app.name') }}.
Regards,
The Linphone team.
{{ config('mail.signature') }}

View file

@ -0,0 +1,28 @@
<html>
<head>
<title>Registration confirmed {{ config('app.name') }}</title>
</head>
<body>
<p>Hello,</p>
<p>
Your SIP account has been successfully created using {{ config('app.name') }}.<br />
You can now configure this account on any SIP-compatible application using the following parameters:<br />
<br />
<b>SIP address:</b> sip:{{ $account->identifier }}<br />
<b>Username:</b> {{ $account->username }}<br />
<b>Domain:</b> {{ $account->domain }}<br />
<br />
@if (!empty(config('app.proxy_registrar_address')))
<b>Proxy/registrar address: </b> sip:{{ config('app.proxy_registrar_address') }}<br />
@endif
@if (!empty(config('app.transport_protocol')))
<b>Transport: </b> {{ config('app.transport_protocol') }} <br />
@endif
</p>
<p>
Regards,<br />
{{ config('mail.signature') }}
</p>
</body>
</html>

View file

@ -0,0 +1,20 @@
Registration confirmed {{ config('app.name') }}
Hello,
Your SIP account has been successfully created using {{ config('app.name') }}.
You can now configure this account on any SIP-compatible application using the following parameters:
SIP address: sip:{{ $account->identifier }}
Username: {{ $account->username }}
Domain: {{ $account->domain }}
@if (!empty(config('app.proxy_registrar_address')))
Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}
@endif
@if (!empty(config('app.transport_protocol')))
Transport: {{ config('app.transport_protocol') }}
@endif
Regards,
{{ config('mail.signature') }}

View file

@ -6,14 +6,14 @@
<p>Hello,</p>
<p>
You just created an account on {{ config('app.name') }} using your email account.<br />
Please follow the unique link bellow to finish the registration process.
Please follow the unique link bellow to set up your password and finish the registration process.
</p>
<p>
<a href="{{ $link }}">{{ $link }}</a>
</p>
<p>
Regards,<br />
The Linphone team.
{{ config('mail.signature') }}
</p>
</body>
</html>

View file

@ -1,9 +1,9 @@
Hello,
You just created an account on {{ config('app.name') }} using your email account.
Please follow the unique link bellow to finish the registration process.
Please follow the unique link bellow to set up your password and finish the registration process.
{{ $link }}
Regards,
The Linphone team.
{{ config('mail.signature') }}

View file

@ -26,14 +26,9 @@ class AccountApiTest extends TestCase
public function testNotAdminForbidden()
{
$password = factory(Password::class)->create();
$response0 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response1 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response0),
])->json($this->method, $this->route);
$response0 = $this->generateFirstResponse($password);
$response1 = $this->generateSecondResponse($password, $response0)
->json($this->method, $this->route);
$response1->assertStatus(403);
}
@ -42,21 +37,15 @@ class AccountApiTest extends TestCase
{
$admin = factory(Admin::class)->create();
$password = $admin->account->passwords()->first();
$response0 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$username = 'foobar';
$response1 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response0),
])->json($this->method, $this->route, [
'username' => $username,
'algorithm' => 'SHA-256',
'password' => '123456',
]);
$response0 = $this->generateFirstResponse($password);
$response1 = $this->generateSecondResponse($password, $response0)
->json($this->method, $this->route, [
'username' => $username,
'algorithm' => 'SHA-256',
'password' => '123456',
]);
$response1
->assertStatus(200)
@ -65,4 +54,71 @@ class AccountApiTest extends TestCase
'username' => $username
]);
}
public function testDomain()
{
$admin = factory(Admin::class)->create();
$password = $admin->account->passwords()->first();
$username = 'foobar';
$domain = 'example.com';
$response0 = $this->generateFirstResponse($password);
$response1 = $this->generateSecondResponse($password, $response0)
->json($this->method, $this->route, [
'username' => $username,
'domain' => $domain,
'algorithm' => 'SHA-256',
'password' => '123456',
]);
$response1
->assertStatus(200)
->assertJson([
'id' => 2,
'username' => $username,
'domain' => $domain,
]);
}
public function testUsernameNoDomain()
{
$admin = factory(Admin::class)->create();
$password = $admin->account->passwords()->first();
$username = 'username';
$response0 = $this->generateFirstResponse($password);
$response1 = $this->generateSecondResponse($password, $response0)
->json($this->method, $this->route, [
'username' => $username,
'algorithm' => 'SHA-256',
'password' => '123456',
]);
$response1
->assertStatus(200)
->assertJson([
'id' => 2,
'username' => $username,
'domain' => config('app.sip_domain'),
]);
}
public function testUsernameEmpty()
{
$admin = factory(Admin::class)->create();
$password = $admin->account->passwords()->first();
$username = 'username';
$response0 = $this->generateFirstResponse($password);
$response1 = $this->generateSecondResponse($password, $response0)
->json($this->method, $this->route, [
'username' => '',
'algorithm' => 'SHA-256',
'password' => '2',
]);
$response1->assertStatus(422);
}
}

View file

@ -79,14 +79,9 @@ class AuthenticateDigestTest extends TestCase
public function testReplayNonce()
{
$password = factory(Password::class)->create();
$response0 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response1 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response0),
])->json($this->method, $this->route);
$response0 = $this->generateFirstResponse($password);
$response1 = $this->generateSecondResponse($password, $response0)
->json($this->method, $this->route);
$response1->assertStatus(200);
@ -111,10 +106,7 @@ class AuthenticateDigestTest extends TestCase
public function testClearedNonce()
{
$password = factory(Password::class)->create();
$response1 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response1 = $this->generateFirstResponse($password);
$response2 = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response1, 'md5', '00000001'),
@ -138,14 +130,9 @@ class AuthenticateDigestTest extends TestCase
public function testAuthenticationMD5()
{
$password = factory(Password::class)->create();
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response),
])->json($this->method, $this->route);
$response = $this->generateFirstResponse($password);
$response = $this->generateSecondResponse($password, $response)
->json($this->method, $this->route);
$this->assertStringContainsString('algorithm=MD5', $response->headers->all()['www-authenticate'][0]);
@ -155,10 +142,7 @@ class AuthenticateDigestTest extends TestCase
public function testAuthenticationSHA265()
{
$password = factory(Password::class)->states('sha256')->create();
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response = $this->generateFirstResponse($password);
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response, 'sha256'),
@ -172,9 +156,7 @@ class AuthenticateDigestTest extends TestCase
public function testAuthenticationSHA265FromCLRTXT()
{
$password = factory(Password::class)->states('clrtxt')->create();
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response = $this->generateFirstResponse($password);;
// The server is generating all the available hash algorythms
$this->assertStringContainsString('algorithm=MD5', $response->headers->all()['www-authenticate'][0]);
@ -201,15 +183,11 @@ class AuthenticateDigestTest extends TestCase
public function testAuthenticationBadPassword()
{
$password = factory(Password::class)->create();
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
$response = $this->generateFirstResponse($password);;
$password->password = 'wrong';
$response = $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $response),
])->json($this->method, $this->route);
$response = $this->generateSecondResponse($password, $response)
->json($this->method, $this->route);
$response->assertStatus(401);
}

View file

@ -13,6 +13,21 @@ abstract class TestCase extends BaseTestCase
const ALGORITHMS = ['md5' => 'MD5', 'sha256' => 'SHA-256'];
protected function generateFirstResponse(Password $password)
{
return $this->withHeaders([
'From' => 'sip:'.$password->account->identifier
])->json($this->method, $this->route);
}
protected function generateSecondResponse(Password $password, $firstResponse)
{
return $this->withHeaders([
'From' => 'sip:'.$password->account->identifier,
'Authorization' => $this->generateDigest($password, $firstResponse),
]);
}
protected function generateDigest(Password $password, $response, $hash = 'md5', $nc = '00000001')
{
$challenge = \substr($response->headers->get('www-authenticate'), 7);