Add a Privacy page + validation

Slight UI adjustments
Allow Markdown in the intro text
Add toggle for Devices Management panel
This commit is contained in:
Timothée Jaussoin 2020-09-24 14:24:42 +02:00
parent 89bee3ca18
commit 9326245cfb
35 changed files with 632 additions and 24 deletions

View file

@ -44,6 +44,7 @@ MAIL_SIGNATURE="The Linphone Team"
NEWSLETTER_REGISTRATION_ADDRESS=
PHONE_AUTHENTICATION=true
DEVICES_MANAGEMENT=true
OVH_APP_KEY=
OVH_APP_SECRET=

View file

@ -71,7 +71,7 @@ We advise you to copy the `style.css` file and rename it to make your custom CSS
### systemd restrictions
To retrieve the devices configuration, FlexiAPI connects to the UNIX socket opened by FlexiSIP. The socket is located in the `/tmp` directory.
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.

View file

@ -49,8 +49,11 @@ class AuthenticateController extends Controller
'password' => 'required'
]);
$account = Account::where('username', $request->get('username'))
->firstOrFail();
$account = Account::where('username', $request->get('username'))->first();
if (!$account) {
return redirect()->back()->withErrors(['authentication' => 'The account doesn\'t exists']);
}
// Try out the passwords
foreach ($account->passwords as $password) {

View file

@ -65,6 +65,7 @@ class RegisterController extends Controller
{
$request->validate([
'terms' => 'accepted',
'privacy' => 'accepted',
'username' => [
'required', 'unique:external.accounts,username', 'filled', new WithoutSpaces
],
@ -101,6 +102,7 @@ class RegisterController extends Controller
{
$request->validate([
'terms' =>'accepted',
'privacy' => 'accepted',
'username' => 'unique:external.accounts,username|nullable|filled',
'phone' => [
'required', 'unique:external.aliases,alias',

View file

@ -52,6 +52,11 @@ class AccountController extends Controller
return view('account.terms');
}
public function privacy(Request $request)
{
return view('account.privacy');
}
public function delete(Request $request)
{
return view('account.delete', [

View file

@ -15,6 +15,7 @@
"laravel/tinker": "^2.4",
"laravelcollective/html": "^6.2",
"ovh/ovh": "^2.0",
"parsedown/laravel": "^1.2",
"propaganistas/laravel-phone": "^4.2"
},
"require-dev": {

105
flexiapi/composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5063139d9245c72528e7b86c22887d58",
"content-hash": "f32d08746d9f85a865c21dc34baf5713",
"packages": [
{
"name": "anhskohbo/no-captcha",
@ -430,6 +430,52 @@
],
"time": "2020-09-19T14:37:56+00:00"
},
{
"name": "erusev/parsedown",
"version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"time": "2019-12-30T22:54:17+00:00"
},
{
"name": "fideloper/proxy",
"version": "4.4.0",
@ -1811,6 +1857,63 @@
],
"time": "2018-07-02T15:55:56+00:00"
},
{
"name": "parsedown/laravel",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/parsedown/laravel.git",
"reference": "c713ffe28c76730754389180e86e93e8e84087e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/parsedown/laravel/zipball/c713ffe28c76730754389180e86e93e8e84087e7",
"reference": "c713ffe28c76730754389180e86e93e8e84087e7",
"shasum": ""
},
"require": {
"erusev/parsedown": "^1.7",
"php": ">=7.1.3"
},
"require-dev": {
"orchestra/testbench": "^3.8",
"php": ">=7.2",
"phpunit/phpunit": "^8.3"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Parsedown\\Providers\\ParsedownServiceProvider"
]
}
},
"autoload": {
"files": [
"src/Support/helpers.php"
],
"psr-4": {
"Parsedown\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Eduardo Agostini",
"email": "edu.agostini@gmail.com"
}
],
"description": "Official Parsedown's Laravel Wrapper.",
"homepage": "http://parsedown.org",
"keywords": [
"laravel",
"parsedown"
],
"time": "2020-01-07T02:12:55+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.7.5",

View file

@ -19,6 +19,7 @@ return [
'newsletter_registration_address' => env('NEWSLETTER_REGISTRATION_ADDRESS', ''),
'phone_authentication' => env('PHONE_AUTHENTICATION', true),
'devices_management' => env('DEVICES_MANAGEMENT', true),
'proxy_registrar_address' => env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com'),
'transport_protocol' => env('ACCOUNT_TRANSPORT_PROTOCOL', 'TLS (recommended), TCP or UDP'),

View file

@ -7,7 +7,7 @@
{!! Form::open(['route' => 'account.destroy', 'method' => 'delete']) !!}
<p>You are going to permanently delete your account.</p>
<p>Please enter your username <b>{{ $account->identifier }}</b> to confirm.</p>
<p>Please enter your complete username to confirm: <b>{{ $account->identifier }}</b>.</p>
<div class="form-group">
{!! Form::label('identifier', 'Username') !!}

View file

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

View file

@ -1,13 +1,6 @@
@extends('layouts.main')
@section('content')
<p class="text-center">
No account yet?
<a class="btn btn-secondary ml-2" href="{{ route('account.register') }}">Register</a>
</p>
<hr />
@if (Auth::check())
@include('parts.already_auth')
@else
@ -34,4 +27,12 @@
@include('parts.password_recovery')
@endif
<hr />
<p class="text-center">
No account yet?
<a class="btn btn-secondary ml-2" href="{{ route('account.register') }}">Register</a>
</p>
@endsection

View file

@ -15,12 +15,14 @@
<p class="mb-1">No email yet</p>
@endif
</a>
<a href="{{ route('account.device.index') }}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Manage my devices</h5>
</div>
<p class="mb-1">See and delete the devices linked to your account</p>
</a>
@if (config('app.devices_management') == true)
<a href="{{ route('account.device.index') }}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Manage my devices</h5>
</div>
<p class="mb-1">See and delete the devices linked to your account</p>
</a>
@endif
<a href="{{ route('account.password') }}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
@if ($account->passwords()->count() > 0)
@ -50,7 +52,7 @@
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Accounts</h5>
</div>
<p class="mb-1">Manage the FlexiSIP accounts</p>
<p class="mb-1">Manage the Flexisip accounts</p>
</a>
</div>
@endif

View file

@ -0,0 +1,5 @@
@extends('layouts.main')
@section('content')
<h1>Privacy policy</h1>
@endsection

View file

@ -5,7 +5,7 @@
<body>
<p>Hello,</p>
<p>
Your SIP account has been successfully created using {{ config('app.name') }}.<br />
Your SIP account has been successfully created.<br />
You can now configure this account on any SIP-compatible application using the following parameters:<br />
<br />

View file

@ -2,7 +2,7 @@ Registration confirmed {{ config('app.name') }}
Hello,
Your SIP account has been successfully created using {{ config('app.name') }}.
Your SIP account has been successfully created.
You can now configure this account on any SIP-compatible application using the following parameters:
SIP address: sip:{{ $account->identifier }}

View file

@ -1,4 +1,4 @@
<p class="text-center">
<p class="text-center pt-3">
Set or recover your password using your <a href="{{ route('account.login_email') }}">Email address</a>
@if (config('app.phone_authentication'))
or your <a href="{{ route('account.login_phone') }}">Phone number</a>

View file

@ -4,4 +4,10 @@
<p>Read the <a href="{{ route('account.terms') }}">Terms and Conditions</a></p>
</div>
<div class="form-check mb-3">
{!! Form::checkbox('privacy', 'true', false, ['class' => 'form-check-input', 'id' => 'privacy']) !!}
<label class="form-check-label" for="privacy">I accept the Privacy policy: </a></label>
<p>Read the <a href="{{ route('account.privacy') }}">Privacy policy</a></p>
</div>
@include('parts.captcha')

View file

@ -0,0 +1,19 @@
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
<a href="{{ $url }}" class="button button-{{ $color ?? 'primary' }}" target="_blank" rel="noopener">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>

View file

@ -0,0 +1,11 @@
<tr>
<td>
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>

View file

@ -0,0 +1,11 @@
<tr>
<td class="header">
<a href="{{ $url }}" style="display: inline-block;">
@if (trim($slot) === 'Laravel')
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
@else
{{ $slot }}
@endif
</a>
</td>
</tr>

View file

@ -0,0 +1,54 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<style>
@media only screen and (max-width: 600px) {
.inner-body {
width: 100% !important;
}
.footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
{{ $header ?? '' }}
<!-- Email Body -->
<tr>
<td class="body" width="100%" cellpadding="0" cellspacing="0">
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<!-- Body content -->
<tr>
<td class="content-cell">
{{ Illuminate\Mail\Markdown::parse($slot) }}
{{ $subcopy ?? '' }}
</td>
</tr>
</table>
</td>
</tr>
{{ $footer ?? '' }}
</table>
</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,27 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent

View file

@ -0,0 +1,14 @@
<table class="panel" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-content">
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="panel-item">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>
</table>

View file

@ -0,0 +1,7 @@
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td>
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>

View file

@ -0,0 +1,3 @@
<div class="table">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</div>

View file

@ -0,0 +1,289 @@
/* Base */
body,
body *:not(html):not(style):not(br):not(tr):not(code) {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
position: relative;
}
body {
-webkit-text-size-adjust: none;
background-color: #ffffff;
color: #718096;
height: 100%;
line-height: 1.4;
margin: 0;
padding: 0;
width: 100% !important;
}
p,
ul,
ol,
blockquote {
line-height: 1.4;
text-align: left;
}
a {
color: #3869d4;
}
a img {
border: none;
}
/* Typography */
h1 {
color: #3d4852;
font-size: 18px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h2 {
font-size: 16px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h3 {
font-size: 14px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
p {
font-size: 16px;
line-height: 1.5em;
margin-top: 0;
text-align: left;
}
p.sub {
font-size: 12px;
}
img {
max-width: 100%;
}
/* Layout */
.wrapper {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.content {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 0;
padding: 0;
width: 100%;
}
/* Header */
.header {
padding: 25px 0;
text-align: center;
}
.header a {
color: #3d4852;
font-size: 19px;
font-weight: bold;
text-decoration: none;
}
/* Logo */
.logo {
height: 75px;
width: 75px;
}
/* Body */
.body {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
background-color: #edf2f7;
border-bottom: 1px solid #edf2f7;
border-top: 1px solid #edf2f7;
margin: 0;
padding: 0;
width: 100%;
}
.inner-body {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
background-color: #ffffff;
border-color: #e8e5ef;
border-radius: 2px;
border-width: 1px;
box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);
margin: 0 auto;
padding: 0;
width: 570px;
}
/* Subcopy */
.subcopy {
border-top: 1px solid #e8e5ef;
margin-top: 25px;
padding-top: 25px;
}
.subcopy p {
font-size: 14px;
}
/* Footer */
.footer {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
margin: 0 auto;
padding: 0;
text-align: center;
width: 570px;
}
.footer p {
color: #b0adc5;
font-size: 12px;
text-align: center;
}
.footer a {
color: #b0adc5;
text-decoration: underline;
}
/* Tables */
.table table {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
width: 100%;
}
.table th {
border-bottom: 1px solid #edeff2;
margin: 0;
padding-bottom: 8px;
}
.table td {
color: #74787e;
font-size: 15px;
line-height: 18px;
margin: 0;
padding: 10px 0;
}
.content-cell {
max-width: 100vw;
padding: 32px;
}
/* Buttons */
.action {
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
margin: 30px auto;
padding: 0;
text-align: center;
width: 100%;
}
.button {
-webkit-text-size-adjust: none;
border-radius: 4px;
color: #fff;
display: inline-block;
overflow: hidden;
text-decoration: none;
}
.button-blue,
.button-primary {
background-color: #2d3748;
border-bottom: 8px solid #2d3748;
border-left: 18px solid #2d3748;
border-right: 18px solid #2d3748;
border-top: 8px solid #2d3748;
}
.button-green,
.button-success {
background-color: #48bb78;
border-bottom: 8px solid #48bb78;
border-left: 18px solid #48bb78;
border-right: 18px solid #48bb78;
border-top: 8px solid #48bb78;
}
.button-red,
.button-error {
background-color: #e53e3e;
border-bottom: 8px solid #e53e3e;
border-left: 18px solid #e53e3e;
border-right: 18px solid #e53e3e;
border-top: 8px solid #e53e3e;
}
/* Panels */
.panel {
border-left: #2d3748 solid 4px;
margin: 21px 0;
}
.panel-content {
background-color: #edf2f7;
color: #718096;
padding: 16px;
}
.panel-content p {
color: #718096;
}
.panel-item {
padding: 0;
}
.panel-item p:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
}
/* Utilities */
.break-all {
word-break: break-all;
}

View file

@ -0,0 +1 @@
{{ $slot }}: {{ $url }}

View file

@ -0,0 +1 @@
{{ $slot }}

View file

@ -0,0 +1 @@
[{{ $slot }}]({{ $url }})

View file

@ -0,0 +1,9 @@
{!! strip_tags($header) !!}
{!! strip_tags($slot) !!}
@isset($subcopy)
{!! strip_tags($subcopy) !!}
@endisset
{!! strip_tags($footer) !!}

View file

@ -0,0 +1,27 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Subcopy --}}
@isset($subcopy)
@slot('subcopy')
@component('mail::subcopy')
{{ $subcopy }}
@endcomponent
@endslot
@endisset
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent

View file

@ -0,0 +1 @@
{{ $slot }}

View file

@ -0,0 +1 @@
{{ $slot }}

View file

@ -0,0 +1 @@
{{ $slot }}

View file

@ -21,6 +21,7 @@
Route::get('/', 'AccountController@home')->name('account.home');
Route::get('terms', 'AccountController@terms')->name('account.terms');
Route::get('privacy', 'AccountController@privacy')->name('account.privacy');
Route::get('login', 'Account\AuthenticateController@login')->name('account.login');
Route::post('authenticate', 'Account\AuthenticateController@authenticate')->name('account.authenticate');