@fishawack/lab-velocity 2.0.0-beta.6 → 2.0.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +467 -36
- package/_Build/js/libs/build-id.js +14 -0
- package/_Build/js/libs/filters.js +36 -0
- package/_Build/js/libs/globals.js +7 -0
- package/_Build/js/libs/router.js +22 -0
- package/_Build/js/libs/routes.js +29 -0
- package/_Build/js/libs/store.js +21 -0
- package/_Build/js/libs/utility.js +161 -0
- package/_Build/vue/components/basic/Button.vue +1 -1
- package/_Build/vue/components/form/Avatar.vue +90 -0
- package/_Build/vue/components/form/Checkbox.vue +10 -0
- package/_Build/vue/components/form/InputNumber.vue +1 -1
- package/_Build/vue/components/form/Select.vue +221 -31
- package/_Build/vue/components/form/Wysiwyg2.vue +87 -2
- package/_Build/vue/components/form/wysiwyg2-video.js +41 -0
- package/_Build/vue/components/layout/Alert.vue +5 -5
- package/_Build/vue/components/layout/Audit.vue +159 -0
- package/_Build/vue/{modules/AuthModule/components/VBreadcrumbs.vue → components/layout/Breadcrumbs.vue} +4 -4
- package/_Build/vue/{modules/AuthModule/components → components/layout}/Chips.vue +2 -2
- package/_Build/vue/components/layout/Footer.vue +11 -10
- package/_Build/vue/{modules/AuthModule/components/VFormFooter.vue → components/layout/FormFooter.vue} +2 -2
- package/_Build/vue/{modules/AuthModule/components → components/layout}/FormRole.vue +8 -8
- package/_Build/vue/components/layout/Layout.vue +94 -0
- package/_Build/vue/components/layout/Navigation.vue +83 -0
- package/_Build/vue/{modules/AuthModule/components/VPageHeader.vue → components/layout/PageHeader.vue} +14 -8
- package/_Build/vue/components/layout/SideBar.vue +26 -0
- package/_Build/vue/{modules/AuthModule/components/VTable.vue → components/layout/Table.vue} +24 -12
- package/_Build/vue/{modules/AuthModule/components/VTableSorter.vue → components/layout/TableSorter.vue} +92 -48
- package/_Build/vue/components/layout/TokenDisplay.vue +52 -0
- package/_Build/vue/components/layout/pageTitle.vue +1 -1
- package/_Build/vue/components/navigation/Menu.vue +6 -1
- package/_Build/vue/components/navigation/MenuItem.vue +7 -2
- package/_Build/vue/components/navigation/MenuItemGroup.vue +7 -2
- package/_Build/vue/modules/AuthModule/js/axios.js +21 -1
- package/_Build/vue/modules/AuthModule/js/clear-cookies.js +34 -0
- package/_Build/vue/modules/AuthModule/js/guest-request.js +32 -0
- package/_Build/vue/modules/AuthModule/js/impersonation-banner.js +102 -0
- package/_Build/vue/modules/AuthModule/js/router.js +91 -114
- package/_Build/vue/modules/AuthModule/js/store.js +23 -6
- package/_Build/vue/modules/AuthModule/routes/PCompanies/columns.js +268 -0
- package/_Build/vue/modules/AuthModule/routes/PCompanies/resource.js +213 -0
- package/_Build/vue/modules/AuthModule/routes/PIntegrations/columns.js +58 -0
- package/_Build/vue/modules/AuthModule/routes/PIntegrations/resource.js +79 -0
- package/_Build/vue/modules/AuthModule/routes/PTeams/columns.js +78 -0
- package/_Build/vue/modules/AuthModule/routes/PTeams/resource.js +251 -0
- package/_Build/vue/modules/AuthModule/routes/PUsers/SetPasswordAction.vue +51 -0
- package/_Build/vue/modules/AuthModule/routes/PUsers/SetPasswordDialog.vue +138 -0
- package/_Build/vue/modules/AuthModule/routes/PUsers/columns.js +349 -0
- package/_Build/vue/modules/AuthModule/routes/PUsers/resource.js +239 -0
- package/_Build/vue/modules/AuthModule/routes/account-exists.vue +2 -2
- package/_Build/vue/modules/AuthModule/routes/change-password.vue +28 -32
- package/_Build/vue/modules/AuthModule/routes/container.vue +2 -11
- package/_Build/vue/modules/AuthModule/routes/expired-reset.vue +4 -4
- package/_Build/vue/modules/AuthModule/routes/expired-verification.vue +10 -9
- package/_Build/vue/modules/AuthModule/routes/force-reset.vue +44 -58
- package/_Build/vue/modules/AuthModule/routes/forgot.vue +10 -5
- package/_Build/vue/modules/AuthModule/routes/login.vue +12 -19
- package/_Build/vue/modules/AuthModule/routes/logincallback.vue +1 -3
- package/_Build/vue/modules/AuthModule/routes/loginsso.vue +14 -10
- package/_Build/vue/modules/AuthModule/routes/logout.vue +14 -5
- package/_Build/vue/modules/AuthModule/routes/logoutheadless.vue +16 -5
- package/_Build/vue/modules/AuthModule/routes/register.vue +24 -28
- package/_Build/vue/modules/AuthModule/routes/reset.vue +20 -14
- package/_Build/vue/modules/AuthModule/routes/success-forgot.vue +14 -8
- package/_Build/vue/modules/AuthModule/routes/success-reset.vue +2 -2
- package/_Build/vue/modules/AuthModule/routes/success-verify.vue +1 -3
- package/_Build/vue/modules/AuthModule/routes/verify.vue +11 -14
- package/_Build/vue/modules/resource/Children/create.vue +99 -0
- package/_Build/vue/modules/resource/Children/edit.vue +124 -0
- package/_Build/vue/modules/resource/Children/index.vue +42 -0
- package/_Build/vue/modules/resource/Children/partials/form.vue +111 -0
- package/_Build/vue/modules/resource/Children/show.vue +170 -0
- package/_Build/vue/modules/resource/index.js +562 -0
- package/_Build/vue/modules/resource/parent.vue +63 -0
- package/_Build/vue/modules/resource/trashable.js +104 -0
- package/_base.scss +0 -1
- package/_defaults.scss +2 -2
- package/components/_auth.scss +19 -61
- package/components/_datepicker.scss +1 -0
- package/components/_descriptions.scss +2 -0
- package/components/_footer.scss +1 -0
- package/components/_form.scss +18 -0
- package/components/_header.scss +3 -27
- package/components/_layout.scss +56 -0
- package/components/_menu.scss +0 -5
- package/components/_sidebar.scss +12 -27
- package/components/_table.scss +3 -0
- package/components/_token-display.scss +41 -0
- package/components/_wysiwyg2.scss +10 -0
- package/general.scss +1 -0
- package/index.js +31 -1
- package/package.json +7 -4
- package/_Build/vue/components/layout/sideBar.vue +0 -25
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/Upload/upload.vue +0 -259
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/create.vue +0 -62
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/edit.vue +0 -98
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/index.vue +0 -90
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/partials/form.vue +0 -181
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/Children/show.vue +0 -266
- package/_Build/vue/modules/AuthModule/adminRoutes/PCompanies/parent.vue +0 -36
- package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/create.vue +0 -112
- package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/edit.vue +0 -103
- package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/index.vue +0 -112
- package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/partials/form.vue +0 -173
- package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/Children/show.vue +0 -123
- package/_Build/vue/modules/AuthModule/adminRoutes/PUsers/parent.vue +0 -36
- package/components/_input.scss +0 -0
- /package/_Build/vue/{modules/AuthModule/components → components/layout}/AuthModal.vue +0 -0
- /package/_Build/vue/{modules/AuthModule/components → components/layout}/Chip.vue +0 -0
- /package/_Build/vue/{modules/AuthModule/components/VPasswordValidation.vue → components/layout/PasswordValidation.vue} +0 -0
- /package/_Build/vue/{modules/AuthModule/components/VRoleLegend.vue → components/layout/RoleLegend.vue} +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clears all cookies by:
|
|
5
|
+
* 1. Calling the server endpoint to expire HttpOnly cookies (remember token, session)
|
|
6
|
+
* across all parent domain variants
|
|
7
|
+
* 2. Clearing all client-accessible cookies via document.cookie as a fallback
|
|
8
|
+
*/
|
|
9
|
+
export default async function clearCookies() {
|
|
10
|
+
try {
|
|
11
|
+
await axios.post("/hydrate/clear-cookies");
|
|
12
|
+
} catch {
|
|
13
|
+
/* endpoint may not exist on older backends */
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const hostname = location.hostname;
|
|
17
|
+
const parts = hostname.split(".");
|
|
18
|
+
const domains = [hostname];
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
21
|
+
domains.push("." + parts.slice(i).join("."));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
document.cookie.split(";").forEach((cookie) => {
|
|
25
|
+
const name = cookie.split("=")[0].trim();
|
|
26
|
+
if (!name) return;
|
|
27
|
+
|
|
28
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
|
|
29
|
+
|
|
30
|
+
domains.forEach((domain) => {
|
|
31
|
+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=${domain}`;
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a form POST to a guest-only route with automatic logged-in detection.
|
|
6
|
+
*
|
|
7
|
+
* If the server redirects to /hydrate/logged-in (because a stale session
|
|
8
|
+
* exists), the helper dispatches a logout, fetches a fresh CSRF cookie,
|
|
9
|
+
* and retries the original request once.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} options
|
|
12
|
+
* @param {object} options.form - form-backend-validation Form instance
|
|
13
|
+
* @param {string} options.url - endpoint to POST to
|
|
14
|
+
* @param {string} [options.method] - HTTP method (default: "post")
|
|
15
|
+
* @param {import('vuex').Store} options.store - Vuex store (for dispatch("logout"))
|
|
16
|
+
* @returns {Promise<object>} resolved response data
|
|
17
|
+
*/
|
|
18
|
+
export async function guestRequest({ form, url, method = "post", store }) {
|
|
19
|
+
const res = await form[method](url);
|
|
20
|
+
|
|
21
|
+
if (res && res["logged-in"]) {
|
|
22
|
+
try {
|
|
23
|
+
await store.dispatch("logout");
|
|
24
|
+
} catch (_) {}
|
|
25
|
+
|
|
26
|
+
await axios.get("/sanctum/csrf-cookie");
|
|
27
|
+
|
|
28
|
+
return await form[method](url);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return res;
|
|
32
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { createApp, h, ref } from "vue";
|
|
4
|
+
|
|
5
|
+
let bannerApp = null;
|
|
6
|
+
let bannerContainer = null;
|
|
7
|
+
let storeRef = null;
|
|
8
|
+
const currentUser = ref(null);
|
|
9
|
+
|
|
10
|
+
function mountBanner() {
|
|
11
|
+
if (bannerApp) return;
|
|
12
|
+
|
|
13
|
+
bannerContainer = document.createElement("div");
|
|
14
|
+
document.body.insertBefore(bannerContainer, document.body.firstChild);
|
|
15
|
+
|
|
16
|
+
bannerApp = createApp({
|
|
17
|
+
setup() {
|
|
18
|
+
function stop() {
|
|
19
|
+
storeRef.dispatch("stopImpersonating");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return () =>
|
|
23
|
+
h(
|
|
24
|
+
"div",
|
|
25
|
+
{
|
|
26
|
+
style: {
|
|
27
|
+
background: "#c0392b",
|
|
28
|
+
color: "#fff",
|
|
29
|
+
display: "flex",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
justifyContent: "center",
|
|
32
|
+
gap: "16px",
|
|
33
|
+
padding: "12px 16px",
|
|
34
|
+
fontSize: "14px",
|
|
35
|
+
fontFamily: "sans-serif",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
[
|
|
39
|
+
h(
|
|
40
|
+
"span",
|
|
41
|
+
`Impersonating: ${currentUser.value?.name} (${currentUser.value?.email})`,
|
|
42
|
+
),
|
|
43
|
+
h(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
onClick: stop,
|
|
47
|
+
style: {
|
|
48
|
+
background: "#fff",
|
|
49
|
+
color: "#c0392b",
|
|
50
|
+
border: "none",
|
|
51
|
+
padding: "4px 12px",
|
|
52
|
+
borderRadius: "4px",
|
|
53
|
+
cursor: "pointer",
|
|
54
|
+
fontWeight: "bold",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
"Stop impersonating",
|
|
58
|
+
),
|
|
59
|
+
],
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
bannerApp.mount(bannerContainer);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unmountBanner() {
|
|
68
|
+
if (!bannerApp) return;
|
|
69
|
+
|
|
70
|
+
bannerApp.unmount();
|
|
71
|
+
bannerContainer?.remove();
|
|
72
|
+
bannerApp = null;
|
|
73
|
+
bannerContainer = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function syncImpersonationBanner(user) {
|
|
77
|
+
currentUser.value = user;
|
|
78
|
+
|
|
79
|
+
if (user?.impersonating_as) {
|
|
80
|
+
mountBanner();
|
|
81
|
+
} else {
|
|
82
|
+
unmountBanner();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function setImpersonationStore(store) {
|
|
87
|
+
storeRef = store;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function ImpersonationPlugin(store) {
|
|
91
|
+
setImpersonationStore(store);
|
|
92
|
+
|
|
93
|
+
// Handle persisted state on load (vuex-persistedstate bypasses mutations)
|
|
94
|
+
syncImpersonationBanner(store.state.auth?.user ?? store.state.user);
|
|
95
|
+
|
|
96
|
+
// Handle all future setUser mutations
|
|
97
|
+
store.subscribe((mutation) => {
|
|
98
|
+
if (mutation.type === "auth/setUser" || mutation.type === "setUser") {
|
|
99
|
+
syncImpersonationBanner(mutation.payload);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
import { h } from "vue";
|
|
4
4
|
import { RouterView } from "vue-router";
|
|
5
5
|
|
|
6
|
+
import { routes as resourceRoutes } from "../../resource/index.js";
|
|
7
|
+
|
|
8
|
+
import defaultUserResource from "../routes/PUsers/resource.js";
|
|
9
|
+
import defaultCompanyResource from "../routes/PCompanies/resource.js";
|
|
10
|
+
import defaultTeamResource from "../routes/PTeams/resource.js";
|
|
11
|
+
import defaultIntegrationResource from "../routes/PIntegrations/resource.js";
|
|
12
|
+
|
|
6
13
|
// Admin routes export - minimal auth flow (headless login only)
|
|
7
|
-
export function adminRoutes(node) {
|
|
14
|
+
export function adminRoutes(node, overrides = {}) {
|
|
15
|
+
const {
|
|
16
|
+
userResource = defaultUserResource,
|
|
17
|
+
companyResource = defaultCompanyResource,
|
|
18
|
+
teamResource = defaultTeamResource,
|
|
19
|
+
integrationResource = defaultIntegrationResource,
|
|
20
|
+
} = overrides;
|
|
21
|
+
|
|
8
22
|
return [
|
|
9
23
|
{
|
|
10
24
|
path: "/auth",
|
|
@@ -37,96 +51,25 @@ export function adminRoutes(node) {
|
|
|
37
51
|
: require("../routes/logincallback.vue").default,
|
|
38
52
|
name: "auth.callback",
|
|
39
53
|
},
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
path: "/users",
|
|
44
|
-
component: node
|
|
45
|
-
? ""
|
|
46
|
-
: require("../adminRoutes/PUsers/parent.vue").default,
|
|
47
|
-
children: [
|
|
48
54
|
{
|
|
49
|
-
path: "",
|
|
50
|
-
component:
|
|
51
|
-
|
|
52
|
-
: require("../adminRoutes/PUsers/Children/index.vue")
|
|
53
|
-
.default,
|
|
54
|
-
name: "users.index",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
path: "create",
|
|
58
|
-
component: node
|
|
59
|
-
? ""
|
|
60
|
-
: require("../adminRoutes/PUsers/Children/create.vue")
|
|
61
|
-
.default,
|
|
62
|
-
name: "users.create",
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
path: ":id",
|
|
66
|
-
component: node
|
|
67
|
-
? ""
|
|
68
|
-
: require("../adminRoutes/PUsers/Children/show.vue")
|
|
69
|
-
.default,
|
|
70
|
-
name: "users.show",
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
path: ":id/edit",
|
|
74
|
-
component: node
|
|
75
|
-
? ""
|
|
76
|
-
: require("../adminRoutes/PUsers/Children/edit.vue")
|
|
77
|
-
.default,
|
|
78
|
-
name: "users.edit",
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
path: "/companies",
|
|
84
|
-
component: node
|
|
85
|
-
? ""
|
|
86
|
-
: require("../adminRoutes/PCompanies/parent.vue").default,
|
|
87
|
-
children: [
|
|
88
|
-
{
|
|
89
|
-
path: "",
|
|
90
|
-
component: node
|
|
91
|
-
? ""
|
|
92
|
-
: require("../adminRoutes/PCompanies/Children/index.vue")
|
|
93
|
-
.default,
|
|
94
|
-
name: "companies.index",
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
path: "create",
|
|
98
|
-
component: node
|
|
99
|
-
? ""
|
|
100
|
-
: require("../adminRoutes/PCompanies/Children/create.vue")
|
|
101
|
-
.default,
|
|
102
|
-
name: "companies.create",
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
path: ":id",
|
|
106
|
-
component: node
|
|
107
|
-
? ""
|
|
108
|
-
: require("../adminRoutes/PCompanies/Children/show.vue")
|
|
109
|
-
.default,
|
|
110
|
-
name: "companies.show",
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
path: ":id/edit",
|
|
114
|
-
component: node
|
|
115
|
-
? ""
|
|
116
|
-
: require("../adminRoutes/PCompanies/Children/edit.vue")
|
|
117
|
-
.default,
|
|
118
|
-
name: "companies.edit",
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
path: ":id/upload",
|
|
122
|
-
component: node
|
|
123
|
-
? ""
|
|
124
|
-
: require("../adminRoutes/PCompanies/Children/Upload/upload.vue")
|
|
125
|
-
.default,
|
|
126
|
-
name: "companies.upload",
|
|
55
|
+
path: "verify",
|
|
56
|
+
component: require("../routes/verify.vue").default,
|
|
57
|
+
name: "auth.verify",
|
|
127
58
|
},
|
|
128
59
|
],
|
|
129
60
|
},
|
|
61
|
+
...resourceRoutes(node, ...userResource),
|
|
62
|
+
...resourceRoutes(node, ...companyResource, [
|
|
63
|
+
...resourceRoutes(node, teamResource[0], {
|
|
64
|
+
...teamResource[1],
|
|
65
|
+
routeName: "companies.teams",
|
|
66
|
+
}),
|
|
67
|
+
]),
|
|
68
|
+
...resourceRoutes(node, teamResource[0], {
|
|
69
|
+
...teamResource[1],
|
|
70
|
+
routeName: "teams",
|
|
71
|
+
}),
|
|
72
|
+
...resourceRoutes(node, ...integrationResource),
|
|
130
73
|
];
|
|
131
74
|
}
|
|
132
75
|
|
|
@@ -241,50 +184,84 @@ export function routes(node) {
|
|
|
241
184
|
}
|
|
242
185
|
|
|
243
186
|
export function beforeEach(router, store) {
|
|
187
|
+
let initialLoad = true;
|
|
188
|
+
|
|
189
|
+
// These routes must always be reachable regardless of auth/verification state.
|
|
190
|
+
// Without this, force_password_change or unverified state can trap the user with
|
|
191
|
+
// no way to log out or complete an SSO callback.
|
|
192
|
+
const ALWAYS_ALLOW = new Set(["auth.logout", "auth.callback"]);
|
|
193
|
+
|
|
244
194
|
router.beforeEach(async (to, from, next) => {
|
|
245
|
-
//
|
|
246
|
-
|
|
195
|
+
// Refresh user state on initial page load (handles cross-app state sync)
|
|
196
|
+
// or when authenticated query param is present (bypass/redirect flow)
|
|
197
|
+
if (
|
|
198
|
+
(store.getters.authenticated && initialLoad) ||
|
|
199
|
+
to.query.authenticated
|
|
200
|
+
) {
|
|
247
201
|
await store.dispatch("getUser", {
|
|
248
202
|
errors: (e) => console.error(e),
|
|
249
203
|
});
|
|
250
204
|
}
|
|
251
205
|
|
|
206
|
+
initialLoad = false;
|
|
207
|
+
|
|
252
208
|
const { user, redirect } = store.state.auth;
|
|
209
|
+
const isGuestRoute = to.matched.some((d) => d.meta.guest) === true;
|
|
253
210
|
|
|
254
|
-
//
|
|
211
|
+
// Logout and SSO callback must always be reachable — prevents every
|
|
212
|
+
// possible "stuck" scenario caused by state-based redirects below.
|
|
213
|
+
if (ALWAYS_ALLOW.has(to.name)) {
|
|
214
|
+
return next();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Redirect already-verified users away from verification routes
|
|
255
218
|
if (
|
|
256
219
|
to.query.verified ||
|
|
257
220
|
(to.name === "auth.expired-verification" && user?.email_verified_at)
|
|
258
221
|
) {
|
|
259
|
-
next({ name: "auth.success-verify" });
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
222
|
+
return next({ name: "auth.success-verify" });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (store.getters.authenticated) {
|
|
226
|
+
// 1. Email verification is the highest priority redirect.
|
|
227
|
+
// Must come before force_password_change — there is no point forcing
|
|
228
|
+
// a password change on an account that hasn't been verified yet.
|
|
229
|
+
// Guest routes, auth.verify and auth.expired-verification are exempt
|
|
230
|
+
// so the user can always complete or re-request verification.
|
|
231
|
+
if (
|
|
269
232
|
!user?.email_verified_at &&
|
|
270
|
-
|
|
233
|
+
!isGuestRoute &&
|
|
234
|
+
to.name !== "auth.verify" &&
|
|
235
|
+
to.name !== "auth.expired-verification"
|
|
271
236
|
) {
|
|
272
|
-
|
|
273
|
-
next({ name: "auth.verify" });
|
|
274
|
-
} else {
|
|
275
|
-
// User is authenticated and authorized - proceed
|
|
276
|
-
next();
|
|
237
|
+
return next({ name: "auth.verify" });
|
|
277
238
|
}
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
239
|
+
|
|
240
|
+
// 2. Force password change applies only after verification is satisfied.
|
|
241
|
+
// Guest routes are exempt so the user can still navigate within /auth
|
|
242
|
+
// (e.g. auth.verify, auth.expired-verification) without looping.
|
|
243
|
+
if (
|
|
244
|
+
user?.force_password_change &&
|
|
245
|
+
!isGuestRoute &&
|
|
246
|
+
to.name !== "auth.force-reset"
|
|
247
|
+
) {
|
|
248
|
+
return next({ name: "auth.force-reset" });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 3. Redirect an already-authenticated user away from the login page
|
|
252
|
+
if (to.name === "auth.login") {
|
|
253
|
+
return next({ name: redirect });
|
|
286
254
|
}
|
|
255
|
+
|
|
256
|
+
return next();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Unauthenticated: allow guest routes, otherwise redirect to login
|
|
260
|
+
if (isGuestRoute) {
|
|
261
|
+
return next();
|
|
287
262
|
}
|
|
263
|
+
|
|
264
|
+
return next({ name: "auth.login" });
|
|
288
265
|
});
|
|
289
266
|
}
|
|
290
267
|
|
|
@@ -7,6 +7,9 @@ const store = {
|
|
|
7
7
|
intended: null,
|
|
8
8
|
user: null,
|
|
9
9
|
redirect: process.env.HYDRATE_REDIRECT ?? "index",
|
|
10
|
+
logo: process.env.HYDRATE_LOGO ?? "example-logo",
|
|
11
|
+
logoReverse:
|
|
12
|
+
process.env.HYDRATE_LOGO_REVERSE ?? process.env.HYDRATE_LOGO,
|
|
10
13
|
contact:
|
|
11
14
|
process.env.HYDRATE_CONTACT ?? "mailto:det@avalerehealth.com",
|
|
12
15
|
};
|
|
@@ -15,12 +18,18 @@ const store = {
|
|
|
15
18
|
getters: {
|
|
16
19
|
authenticated: (state) => !!state.user,
|
|
17
20
|
can: (state) => (permission) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const arr = Array.isArray(permission) ? permission : [permission];
|
|
22
|
+
const userPermissions = state.user?.permissions.map(
|
|
23
|
+
({ name }) => name,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return arr.every((name) => userPermissions?.includes(name));
|
|
21
27
|
},
|
|
22
28
|
hasRole: (state) => (role) => {
|
|
23
|
-
|
|
29
|
+
const arr = Array.isArray(role) ? role : [role];
|
|
30
|
+
const userRoles = state.user?.roles.map(({ name }) => name);
|
|
31
|
+
|
|
32
|
+
return arr.every((name) => userRoles?.includes(name));
|
|
24
33
|
},
|
|
25
34
|
},
|
|
26
35
|
|
|
@@ -38,7 +47,7 @@ const store = {
|
|
|
38
47
|
},
|
|
39
48
|
|
|
40
49
|
actions: {
|
|
41
|
-
getUser({ commit }, { errors, query = "", params }) {
|
|
50
|
+
getUser({ commit }, { errors = console.log, query = "", params } = {}) {
|
|
42
51
|
return axios
|
|
43
52
|
.get(`/api/users/self${query}`, {
|
|
44
53
|
params,
|
|
@@ -51,11 +60,19 @@ const store = {
|
|
|
51
60
|
.catch(errors);
|
|
52
61
|
},
|
|
53
62
|
|
|
54
|
-
logout({ commit }
|
|
63
|
+
logout({ commit }) {
|
|
55
64
|
commit("setUser", null);
|
|
56
65
|
|
|
57
66
|
return axios.post("/logout");
|
|
58
67
|
},
|
|
68
|
+
|
|
69
|
+
stopImpersonating({ commit }) {
|
|
70
|
+
return axios.post("/api/users/impersonate/stop").then((res) => {
|
|
71
|
+
commit("setUser", res.data.data);
|
|
72
|
+
|
|
73
|
+
return res.data.data;
|
|
74
|
+
});
|
|
75
|
+
},
|
|
59
76
|
},
|
|
60
77
|
};
|
|
61
78
|
|