diff --git a/docker/kolab/utils/15-create-hosted-domain.sh b/docker/kolab/utils/15-create-hosted-domain.sh
--- a/docker/kolab/utils/15-create-hosted-domain.sh
+++ b/docker/kolab/utils/15-create-hosted-domain.sh
@@ -54,7 +54,7 @@
(
for role in "2fa-user" "activesync-user" "imap-user"; do
- echo "cn=${role},${hosted_domain_rootdn}"
+ echo "dn: cn=${role},${hosted_domain_rootdn}"
echo "cn: ${role}"
echo "description: ${role} role"
echo "objectclass: top"
diff --git a/src/app/Auth/SecondFactor.php b/src/app/Auth/SecondFactor.php
--- a/src/app/Auth/SecondFactor.php
+++ b/src/app/Auth/SecondFactor.php
@@ -231,7 +231,7 @@
if (!isset($this->cache[$key])) {
$factors = $this->getFactors();
- $this->cache[$key] = $factors[$key];
+ $this->cache[$key] = isset($factors[$key]) ? $factors[$key] : null;
}
return $this->cache[$key];
diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php
--- a/src/app/Backends/LDAP.php
+++ b/src/app/Backends/LDAP.php
@@ -309,7 +309,10 @@
}
if (!array_key_exists('nsroledn', $oldEntry)) {
- $oldEntry['nsroledn'] = (array)$ldap->get_entry_attributes($dn, ['nsroledn']);
+ $roles = $ldap->get_entry_attributes($dn, ['nsroledn']);
+ if (!empty($roles)) {
+ $oldEntry['nsroledn'] = (array)$roles['nsroledn'];
+ }
}
$newEntry = $oldEntry;
@@ -385,56 +388,48 @@
$entry['mailquota'] = 0;
- if (!array_key_exists('nsroledn', $entry)) {
- $entry['nsroledn'] = [];
- } else if (!is_array($entry['nsroledn'])) {
- $entry['nsroledn'] = (array)$entry['nsroledn'];
- }
-
$roles = [];
foreach ($user->entitlements as $entitlement) {
\Log::debug("Examining {$entitlement->sku->title}");
switch ($entitlement->sku->title) {
+ case "mailbox":
+ break;
+
case "storage":
$entry['mailquota'] += 1048576;
break;
- }
- $roles[] = $entitlement->sku->title;
+ default:
+ $roles[] = $entitlement->sku->title;
+ break;
+ }
}
$hostedRootDN = \config('ldap.hosted.root_dn');
+ if (empty($roles)) {
+ if (array_key_exists('nsroledn', $entry)) {
+ unset($entry['nsroledn']);
+ }
+
+ return;
+ }
+
+ $entry['nsroledn'] = [];
+
if (in_array("2fa", $roles)) {
$entry['nsroledn'][] = "cn=2fa-user,{$hostedRootDN}";
- } else {
- $key = array_search("cn=2fa-user,{$hostedRootDN}", $entry['nsroledn']);
- if ($key !== false) {
- unset($entry['nsroledn'][$key]);
- }
}
if (in_array("activesync", $roles)) {
$entry['nsroledn'][] = "cn=activesync-user,{$hostedRootDN}";
- } else {
- $key = array_search("cn=activesync-user,{$hostedRootDN}", $entry['nsroledn']);
- if ($key !== false) {
- unset($entry['nsroledn'][$key]);
- }
}
if (!in_array("groupware", $roles)) {
$entry['nsroledn'][] = "cn=imap-user,{$hostedRootDN}";
- } else {
- $key = array_search("cn=imap-user,{$hostedRootDN}", $entry['nsroledn']);
- if ($key !== false) {
- unset($entry['nsroledn'][$key]);
- }
}
-
- $entry['nsroledn'] = array_unique($entry['nsroledn']);
}
/**
diff --git a/src/app/Domain.php b/src/app/Domain.php
--- a/src/app/Domain.php
+++ b/src/app/Domain.php
@@ -350,13 +350,22 @@
return true;
}
- $record = \dns_get_record($this->namespace, DNS_ANY);
+ $records = \dns_get_record($this->namespace, DNS_ANY);
- if ($record === false) {
+ if ($records === false) {
throw new \Exception("Failed to get DNS record for {$this->namespace}");
}
- if (!empty($record)) {
+ // It may happen that result contains other domains depending on the host
+ // DNS setup
+ $hosts = array_map(
+ function ($record) {
+ return $record['host'];
+ },
+ $records
+ );
+
+ if (in_array($this->namespace, $hosts)) {
$this->status |= Domain::STATUS_VERIFIED;
$this->save();
diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/AuthController.php
@@ -0,0 +1,133 @@
+guard()->user();
+ $response = V4\UsersController::userResponse($user);
+
+ return response()->json($response);
+ }
+
+ /**
+ * Helper method for other controllers with user auto-logon
+ * functionality
+ *
+ * @param \App\User $user User model object
+ */
+ public static function logonResponse(User $user)
+ {
+ $token = auth()->login($user);
+
+ return response()->json([
+ 'status' => 'success',
+ 'access_token' => $token,
+ 'token_type' => 'bearer',
+ 'expires_in' => Auth::guard()->factory()->getTTL() * 60,
+ ]);
+ }
+
+ /**
+ * Get a JWT token via given credentials.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function login(Request $request)
+ {
+ // TODO: Redirect to dashboard if authenticated.
+ $v = Validator::make(
+ $request->all(),
+ [
+ 'email' => 'required|min:2',
+ 'password' => 'required|min:4',
+ ]
+ );
+
+ if ($v->fails()) {
+ return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
+ }
+
+ $credentials = $request->only('email', 'password');
+
+ if ($token = $this->guard()->attempt($credentials)) {
+ $sf = new \App\Auth\SecondFactor($this->guard()->user());
+
+ if ($response = $sf->requestHandler($request)) {
+ return $response;
+ }
+
+ return $this->respondWithToken($token);
+ }
+
+ return response()->json(['status' => 'error', 'message' => __('auth.failed')], 401);
+ }
+
+ /**
+ * Log the user out (Invalidate the token)
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function logout()
+ {
+ $this->guard()->logout();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => __('auth.logoutsuccess')
+ ]);
+ }
+
+ /**
+ * Refresh a token.
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function refresh()
+ {
+ return $this->respondWithToken($this->guard()->refresh());
+ }
+
+ /**
+ * Get the token array structure.
+ *
+ * @param string $token Respond with this token.
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ protected function respondWithToken($token)
+ {
+ return response()->json(
+ [
+ 'access_token' => $token,
+ 'token_type' => 'bearer',
+ 'expires_in' => $this->guard()->factory()->getTTL() * 60
+ ]
+ );
+ }
+
+ /**
+ * Get the guard to be used during authentication.
+ *
+ * @return \Illuminate\Contracts\Auth\Guard
+ */
+ public function guard()
+ {
+ return Auth::guard();
+ }
+}
diff --git a/src/app/Http/Controllers/API/PasswordResetController.php b/src/app/Http/Controllers/API/PasswordResetController.php
--- a/src/app/Http/Controllers/API/PasswordResetController.php
+++ b/src/app/Http/Controllers/API/PasswordResetController.php
@@ -138,6 +138,6 @@
// Remove the verification code
$this->code->delete();
- return UsersController::logonResponse($user);
+ return AuthController::logonResponse($user);
}
}
diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php
--- a/src/app/Http/Controllers/API/SignupController.php
+++ b/src/app/Http/Controllers/API/SignupController.php
@@ -232,7 +232,7 @@
DB::commit();
- return UsersController::logonResponse($user);
+ return AuthController::logonResponse($user);
}
/**
diff --git a/src/app/Http/Controllers/API/V4/Admin/DomainsController.php b/src/app/Http/Controllers/API/V4/Admin/DomainsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/Admin/DomainsController.php
@@ -0,0 +1,7 @@
+get()->map(function ($user) {
+ $data = $user->toArray();
+ $data = array_merge($data, self::userStatuses($user));
+ return $data;
+ });
+
+ return response()->json($result);
+ }
+}
diff --git a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
@@ -0,0 +1,7 @@
+middleware('auth:api', ['except' => ['login']]);
- }
-
- /**
- * Helper method for other controllers with user auto-logon
- * functionality
- *
- * @param \App\User $user User model object
- */
- public static function logonResponse(User $user)
- {
- $token = auth()->login($user);
-
- return response()->json([
- 'status' => 'success',
- 'access_token' => $token,
- 'token_type' => 'bearer',
- 'expires_in' => Auth::guard()->factory()->getTTL() * 60,
- ]);
- }
-
- /**
* Delete a user.
*
* @param int $id User identifier
@@ -83,6 +53,7 @@
*/
public function index()
{
+ \Log::debug("Regular API");
$user = $this->guard()->user();
$result = $user->users()->orderBy('email')->get()->map(function ($user) {
@@ -95,98 +66,6 @@
}
/**
- * Get the authenticated User
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function info()
- {
- $user = $this->guard()->user();
- $response = $this->userResponse($user);
-
- return response()->json($response);
- }
-
- /**
- * Get a JWT token via given credentials.
- *
- * @param \Illuminate\Http\Request $request The API request.
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function login(Request $request)
- {
- $v = Validator::make(
- $request->all(),
- [
- 'email' => 'required|min:2',
- 'password' => 'required|min:4',
- ]
- );
-
- if ($v->fails()) {
- return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
- }
-
- $credentials = $request->only('email', 'password');
-
- if ($token = $this->guard()->attempt($credentials)) {
- $sf = new \App\Auth\SecondFactor($this->guard()->user());
-
- if ($response = $sf->requestHandler($request)) {
- return $response;
- }
-
- return $this->respondWithToken($token);
- }
-
- return response()->json(['status' => 'error', 'message' => __('auth.failed')], 401);
- }
-
- /**
- * Log the user out (Invalidate the token)
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function logout()
- {
- $this->guard()->logout();
-
- return response()->json([
- 'status' => 'success',
- 'message' => __('auth.logoutsuccess')
- ]);
- }
-
- /**
- * Refresh a token.
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function refresh()
- {
- return $this->respondWithToken($this->guard()->refresh());
- }
-
- /**
- * Get the token array structure.
- *
- * @param string $token Respond with this token.
- *
- * @return \Illuminate\Http\JsonResponse
- */
- protected function respondWithToken($token)
- {
- return response()->json(
- [
- 'access_token' => $token,
- 'token_type' => 'bearer',
- 'expires_in' => $this->guard()->factory()->getTTL() * 60
- ]
- );
- }
-
- /**
* Display information on the user account specified by $id.
*
* @param int $id The account to show information for.
@@ -458,7 +337,7 @@
*
* @return array Response data
*/
- protected function userResponse(User $user): array
+ public static function userResponse(User $user): array
{
$response = $user->toArray();
@@ -562,7 +441,7 @@
if (empty($email)) {
$errors['email'] = \trans('validation.required', ['attribute' => 'email']);
- } elseif ($error = self::validateEmail($email, $controller, false)) {
+ } elseif ($error = \App\Utils::validateEmail($email, $controller, false)) {
$errors['email'] = $error;
}
}
@@ -582,7 +461,7 @@
// validate new aliases
if (
!in_array($alias, $existing_aliases)
- && ($error = self::validateEmail($alias, $controller, true))
+ && ($error = \App\Utils::validateEmail($alias, $controller, true))
) {
if (!isset($errors['aliases'])) {
$errors['aliases'] = [];
@@ -606,62 +485,4 @@
$settings = $request->only(array_keys($rules));
unset($settings['password'], $settings['aliases'], $settings['email']);
}
-
- /**
- * Email address (login or alias) validation
- *
- * @param string $email Email address
- * @param \App\User $user The account owner
- * @param bool $is_alias The email is an alias
- *
- * @return string Error message on validation error
- */
- protected static function validateEmail(string $email, User $user, bool $is_alias = false): ?string
- {
- $attribute = $is_alias ? 'alias' : 'email';
-
- if (strpos($email, '@') === false) {
- return \trans('validation.entryinvalid', ['attribute' => $attribute]);
- }
-
- list($login, $domain) = explode('@', $email);
-
- // Check if domain exists
- $domain = Domain::where('namespace', Str::lower($domain))->first();
-
- if (empty($domain)) {
- return \trans('validation.domaininvalid');
- }
-
- // Validate login part alone
- $v = Validator::make(
- [$attribute => $login],
- [$attribute => ['required', new UserEmailLocal(!$domain->isPublic())]]
- );
-
- if ($v->fails()) {
- return $v->errors()->toArray()[$attribute][0];
- }
-
- // Check if it is one of domains available to the user
- // TODO: We should have a helper that returns "flat" array with domain names
- // I guess we could use pluck() somehow
- $domains = array_map(
- function ($domain) {
- return $domain->namespace;
- },
- $user->domains()
- );
-
- if (!in_array($domain->namespace, $domains)) {
- return \trans('validation.entryexists', ['attribute' => 'domain']);
- }
-
- // Check if user with specified address already exists
- if (User::findByEmail($email)) {
- return \trans('validation.entryexists', ['attribute' => $attribute]);
- }
-
- return null;
- }
}
diff --git a/src/app/Http/Controllers/API/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php
rename from src/app/Http/Controllers/API/WalletsController.php
rename to src/app/Http/Controllers/API/V4/WalletsController.php
--- a/src/app/Http/Controllers/API/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/WalletsController.php
@@ -1,10 +1,6 @@
\App\Http\Middleware\AuthenticateAdmin::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
@@ -73,9 +74,11 @@
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\AuthenticateAdmin::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
+ \App\Http\Middleware\AuthenticateAdmin::class,
];
}
diff --git a/src/app/Http/Middleware/AuthenticateAdmin.php b/src/app/Http/Middleware/AuthenticateAdmin.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Middleware/AuthenticateAdmin.php
@@ -0,0 +1,30 @@
+user();
+
+ if (!$user) {
+ abort(403, "Unauthorized");
+ }
+
+ if ($user->role !== "admin") {
+ abort(403, "Unauthorized");
+ }
+
+ return $next($request);
+ }
+}
diff --git a/src/app/Jobs/UserVerify.php b/src/app/Jobs/UserVerify.php
--- a/src/app/Jobs/UserVerify.php
+++ b/src/app/Jobs/UserVerify.php
@@ -44,6 +44,26 @@
*/
public function handle()
{
+ // Verify a mailbox sku is among the user entitlements.
+ $skuMailbox = \App\Sku::where('title', 'mailbox')->first();
+
+ if (!$skuMailbox) {
+ return;
+ }
+
+ $mailbox = \App\Entitlement::where(
+ [
+ 'sku_id' => $skuMailbox->id,
+ 'entitleable_id' => $this->user->id,
+ 'entitleable_type' => User::class
+ ]
+ )->first();
+
+ if (!$mailbox) {
+ return;
+ }
+
+ // The user has a mailbox
if (!$this->user->isImapReady()) {
if (IMAP::verifyAccount($this->user->email)) {
$this->user->status |= User::STATUS_IMAP_READY;
diff --git a/src/app/Jobs/WalletPayment.php b/src/app/Jobs/WalletPayment.php
--- a/src/app/Jobs/WalletPayment.php
+++ b/src/app/Jobs/WalletPayment.php
@@ -3,7 +3,7 @@
namespace App\Jobs;
use App\Wallet;
-use App\Http\Controllers\API\PaymentsController;
+use App\Http\Controllers\API\V4\PaymentsController;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -71,6 +71,7 @@
'password',
'password_ldap',
'remember_token',
+ 'role'
];
protected $nullable = [
@@ -221,6 +222,10 @@
return false;
}
+ if ($this->role == "admin") {
+ return true;
+ }
+
$wallet = $object->wallet();
// TODO: For now controller can delete/update the account owner,
@@ -242,6 +247,10 @@
return false;
}
+ if ($this->role == "admin") {
+ return true;
+ }
+
if ($object instanceof User && $this->id == $object->id) {
return true;
}
@@ -264,6 +273,10 @@
return false;
}
+ if ($this->role == "admin") {
+ return true;
+ }
+
if ($object instanceof User && $this->id == $object->id) {
return true;
}
diff --git a/src/app/Utils.php b/src/app/Utils.php
--- a/src/app/Utils.php
+++ b/src/app/Utils.php
@@ -2,6 +2,9 @@
namespace App;
+use App\Rules\UserEmailLocal;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Str;
use Ramsey\Uuid\Uuid;
/**
@@ -92,6 +95,69 @@
$countries = include resource_path('countries.php');
$env['countries'] = $countries ?: [];
+ $env['jsapp'] = strpos(request()->getHttpHost(), 'admin.') === 0 ? 'admin.js' : 'user.js';
+
return $env;
}
+
+ /**
+ * Email address (login or alias) validation
+ *
+ * @param string $email Email address
+ * @param \App\User $user The account owner
+ * @param bool $is_alias The email is an alias
+ *
+ * @return string Error message on validation error
+ */
+ public static function validateEmail(
+ string $email,
+ \App\User $user,
+ bool $is_alias = false
+ ): ?string {
+ $attribute = $is_alias ? 'alias' : 'email';
+
+ if (strpos($email, '@') === false) {
+ return \trans('validation.entryinvalid', ['attribute' => $attribute]);
+ }
+
+ list($login, $domain) = explode('@', $email);
+
+ // Check if domain exists
+ $domain = Domain::where('namespace', Str::lower($domain))->first();
+
+ if (empty($domain)) {
+ return \trans('validation.domaininvalid');
+ }
+
+ // Validate login part alone
+ $v = Validator::make(
+ [$attribute => $login],
+ [$attribute => ['required', new UserEmailLocal(!$domain->isPublic())]]
+ );
+
+ if ($v->fails()) {
+ return $v->errors()->toArray()[$attribute][0];
+ }
+
+ // Check if it is one of domains available to the user
+ // TODO: We should have a helper that returns "flat" array with domain names
+ // I guess we could use pluck() somehow
+ $domains = array_map(
+ function ($domain) {
+ return $domain->namespace;
+ },
+ $user->domains()
+ );
+
+ if (!in_array($domain->namespace, $domains)) {
+ return \trans('validation.entryexists', ['attribute' => 'domain']);
+ }
+
+ // Check if user with specified address already exists
+ if (User::findByEmail($email)) {
+ return \trans('validation.entryexists', ['attribute' => $attribute]);
+ }
+
+ return null;
+ }
}
diff --git a/src/database/migrations/2020_03_27_134609_user_table_add_role_column.php b/src/database/migrations/2020_03_27_134609_user_table_add_role_column.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2020_03_27_134609_user_table_add_role_column.php
@@ -0,0 +1,39 @@
+string('role')->nullable();
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table(
+ 'users',
+ function (Blueprint $table) {
+ $table->dropColumn('role');
+ }
+ );
+ }
+}
diff --git a/src/database/seeds/DomainSeeder.php b/src/database/seeds/DomainSeeder.php
--- a/src/database/seeds/DomainSeeder.php
+++ b/src/database/seeds/DomainSeeder.php
@@ -36,6 +36,16 @@
);
}
+ if (!in_array(\config('app.domain'), $domains)) {
+ Domain::create(
+ [
+ 'namespace' => \config('app.domain'),
+ 'status' => DOMAIN::STATUS_CONFIRMED + Domain::STATUS_ACTIVE,
+ 'type' => Domain::TYPE_PUBLIC
+ ]
+ );
+ }
+
$domains = [
'example.com',
'example.net',
diff --git a/src/database/seeds/UserSeeder.php b/src/database/seeds/UserSeeder.php
--- a/src/database/seeds/UserSeeder.php
+++ b/src/database/seeds/UserSeeder.php
@@ -132,5 +132,18 @@
$john->assignPackage($package_lite, $joe);
factory(User::class, 10)->create();
+
+ $jeroen = User::create(
+ [
+ 'name' => 'Jeroen van Meeuwen',
+ 'email' => 'jeroen@jeroen.jeroen',
+ 'password' => 'jeroen',
+ 'email_verified_at' => now()
+ ]
+ );
+
+ $jeroen->role = "admin";
+
+ $jeroen->save();
}
}
diff --git a/src/public/mix-manifest.json b/src/public/mix-manifest.json
--- a/src/public/mix-manifest.json
+++ b/src/public/mix-manifest.json
@@ -1,4 +1,5 @@
{
- "/js/app.js": "/js/app.js",
+ "/js/admin.js": "/js/admin.js",
+ "/js/user.js": "/js/user.js",
"/css/app.css": "/css/app.css"
}
diff --git a/src/resources/js/admin.js b/src/resources/js/admin.js
new file mode 100644
--- /dev/null
+++ b/src/resources/js/admin.js
@@ -0,0 +1,9 @@
+/**
+ * Application code for the admin UI
+ */
+
+import router from './routes-admin'
+
+window.router = router
+
+require('./app')
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -8,7 +8,6 @@
import AppComponent from '../vue/App'
import MenuComponent from '../vue/Menu'
-import router from './routes'
import store from './store'
import FontAwesomeIcon from './fontawesome'
import VueToastr from '@deveodk/vue-toastr'
@@ -128,7 +127,7 @@
'menu-component': MenuComponent
},
store,
- router,
+ router: window.router,
data() {
return {
isLoading: true
@@ -165,7 +164,7 @@
axios.defaults.headers.common.Authorization = 'Bearer ' + token
if (dashboard !== false) {
- router.push(store.state.afterLogin || { name: 'dashboard' })
+ this.$router.push(store.state.afterLogin || { name: 'dashboard' })
}
store.state.afterLogin = null
@@ -175,7 +174,7 @@
store.commit('logoutUser')
localStorage.setItem('token', '')
delete axios.defaults.headers.common.Authorization
- router.push({ name: 'login' })
+ this.$router.push({ name: 'login' })
},
// Display "loading" overlay (to be used by route components)
startLoading() {
diff --git a/src/resources/js/routes-admin.js b/src/resources/js/routes-admin.js
new file mode 100644
--- /dev/null
+++ b/src/resources/js/routes-admin.js
@@ -0,0 +1,67 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+
+Vue.use(VueRouter)
+
+import DashboardComponent from '../vue/Admin/Dashboard'
+import Error404Component from '../vue/404'
+import LoginComponent from '../vue/Login'
+import LogoutComponent from '../vue/Logout'
+import PasswordResetComponent from '../vue/PasswordReset'
+
+import store from './store'
+
+const routes = [
+ {
+ path: '/',
+ redirect: { name: 'dashboard' }
+ },
+ {
+ path: '/dashboard',
+ name: 'dashboard',
+ component: DashboardComponent,
+ meta: { requiresAuth: true }
+ },
+ {
+ path: '/login',
+ name: 'login',
+ component: LoginComponent
+ },
+ {
+ path: '/logout',
+ name: 'logout',
+ component: LogoutComponent
+ },
+ {
+ path: '/password-reset/:code?',
+ name: 'password-reset',
+ component: PasswordResetComponent
+ },
+ {
+ name: '404',
+ path: '*',
+ component: Error404Component
+ }
+]
+
+const router = new VueRouter({
+ mode: 'history',
+ routes
+})
+
+router.beforeEach((to, from, next) => {
+ // check if the route requires authentication and user is not logged in
+ if (to.matched.some(route => route.meta.requiresAuth) && !store.state.isLoggedIn) {
+ // remember the original request, to use after login
+ store.state.afterLogin = to;
+
+ // redirect to login page
+ next({ name: 'login' })
+
+ return
+ }
+
+ next()
+})
+
+export default router
diff --git a/src/resources/js/routes.js b/src/resources/js/routes-user.js
rename from src/resources/js/routes.js
rename to src/resources/js/routes-user.js
diff --git a/src/resources/js/user.js b/src/resources/js/user.js
new file mode 100644
--- /dev/null
+++ b/src/resources/js/user.js
@@ -0,0 +1,9 @@
+/**
+ * Application code for the user UI
+ */
+
+import router from './routes-user'
+
+window.router = router
+
+require('./app')
diff --git a/src/resources/views/layouts/app.blade.php b/src/resources/views/layouts/app.blade.php
--- a/src/resources/views/layouts/app.blade.php
+++ b/src/resources/views/layouts/app.blade.php
@@ -17,6 +17,6 @@
-
+