diff --git a/package-lock.json b/package-lock.json index 59833595..d2149f80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,21 +6,21 @@ "packages": { "": { "name": "web", - "version": "0.101.48", + "version": "0.101.49", "dependencies": { - "@quasar/extras": "1.16.12", - "@vueuse/core": "11.1.0", - "@vueuse/integrations": "11.1.0", - "@vueuse/shared": "11.1.0", + "@quasar/extras": "1.16.13", + "@vueuse/core": "11.2.0", + "@vueuse/integrations": "11.2.0", + "@vueuse/shared": "11.2.0", "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "5.5.0", "apexcharts": "3.54.1", "axios": "1.7.7", "dotenv": "16.4.5", "monaco-editor": "0.50.0", - "pinia": "2.2.4", + "pinia": "2.2.6", "qrcode": "1.5.4", - "quasar": "2.17.1", + "quasar": "2.17.2", "vue": "3.5.12", "vue-router": "4.4.5", "vue3-apexcharts": "1.7.0", @@ -972,9 +972,9 @@ } }, "node_modules/@quasar/extras": { - "version": "1.16.12", - "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.16.12.tgz", - "integrity": "sha512-hLlb3Buxo38Xg/2w0BTkz98RBh/VH8apZ2r6Fl8YpPgrVQ0diHyN/BVTvIOk5Kch2y38L2kvwOIddsB2UcCuIg==", + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.16.13.tgz", + "integrity": "sha512-6QdnYbFYhgeWFAwytUWTDgpP/mcJxydBmgO91cHr9MMTx0GLaVJY6d10m/G/XS9TzMnSsZgqO7kbVHf3Hvml3w==", "license": "MIT", "funding": { "type": "github", @@ -1606,14 +1606,14 @@ "license": "MIT" }, "node_modules/@vueuse/core": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.1.0.tgz", - "integrity": "sha512-P6dk79QYA6sKQnghrUz/1tHi0n9mrb/iO1WTMk/ElLmTyNqgDeSZ3wcDf6fRBGzRJbeG1dxzEOvLENMjr+E3fg==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.2.0.tgz", + "integrity": "sha512-JIUwRcOqOWzcdu1dGlfW04kaJhW3EXnnjJJfLTtddJanymTL7lF1C0+dVVZ/siLfc73mWn+cGP1PE1PKPruRSA==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "11.1.0", - "@vueuse/shared": "11.1.0", + "@vueuse/metadata": "11.2.0", + "@vueuse/shared": "11.2.0", "vue-demi": ">=0.14.10" }, "funding": { @@ -1647,13 +1647,13 @@ } }, "node_modules/@vueuse/integrations": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.1.0.tgz", - "integrity": "sha512-O2ZgrAGPy0qAjpoI2YR3egNgyEqwG85fxfwmA9BshRIGjV4G6yu6CfOPpMHAOoCD+UfsIl7Vb1bXJ6ifrHYDDA==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.2.0.tgz", + "integrity": "sha512-zGXz3dsxNHKwiD9jPMvR3DAxQEOV6VWIEYTGVSB9PNpk4pTWR+pXrHz9gvXWcP2sTk3W2oqqS6KwWDdntUvNVA==", "license": "MIT", "dependencies": { - "@vueuse/core": "11.1.0", - "@vueuse/shared": "11.1.0", + "@vueuse/core": "11.2.0", + "@vueuse/shared": "11.2.0", "vue-demi": ">=0.14.10" }, "funding": { @@ -1739,18 +1739,18 @@ } }, "node_modules/@vueuse/metadata": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.1.0.tgz", - "integrity": "sha512-l9Q502TBTaPYGanl1G+hPgd3QX5s4CGnpXriVBR5fEZ/goI6fvDaVmIl3Td8oKFurOxTmbXvBPSsgrd6eu6HYg==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.2.0.tgz", + "integrity": "sha512-L0ZmtRmNx+ZW95DmrgD6vn484gSpVeRbgpWevFKXwqqQxW9hnSi2Ppuh2BzMjnbv4aJRiIw8tQatXT9uOB23dQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.1.0.tgz", - "integrity": "sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.2.0.tgz", + "integrity": "sha512-VxFjie0EanOudYSgMErxXfq6fo8vhr5ICI+BuE3I9FnX7ePllEsVrRQ7O6Q1TLgApeLuPKcHQxAXpP+KnlrJsg==", "license": "MIT", "dependencies": { "vue-demi": ">=0.14.10" @@ -6520,9 +6520,9 @@ } }, "node_modules/pinia": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.4.tgz", - "integrity": "sha512-K7ZhpMY9iJ9ShTC0cR2+PnxdQRuwVIsXDO/WIEV/RnMC/vmSoKDTKW/exNQYPI+4ij10UjXqdNiEHwn47McANQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.6.tgz", + "integrity": "sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^6.6.3", @@ -6534,7 +6534,7 @@ "peerDependencies": { "@vue/composition-api": "^1.4.0", "typescript": ">=4.4.4", - "vue": "^2.6.14 || ^3.3.0" + "vue": "^2.6.14 || ^3.5.11" }, "peerDependenciesMeta": { "@vue/composition-api": { @@ -6768,9 +6768,9 @@ } }, "node_modules/quasar": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.17.1.tgz", - "integrity": "sha512-4tnprxb+oEFChqcHGeQnprTmsr9gRK/R4N2O6Gg0AxpP22G9ttKeeqhH7+Soyjdi9RUcc6d5Y49T6rRg4hGJXw==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.17.2.tgz", + "integrity": "sha512-wycJcrjXsNxyNFYaw7eviKAo0I3LaQap0GCHXUEiaAi4H+a9LJbgkoZSZKCP4M0UO4iVnkWVkQzsFodyHk5uHQ==", "license": "MIT", "engines": { "node": ">= 10.18.1", diff --git a/package.json b/package.json index 95f512ac..b579507c 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,17 @@ "format": "prettier --write \"**/*.{js,ts,vue,,html,md,json}\" --ignore-path .gitignore" }, "dependencies": { - "@quasar/extras": "1.16.12", - "@vueuse/core": "11.1.0", - "@vueuse/integrations": "11.1.0", - "@vueuse/shared": "11.1.0", + "@quasar/extras": "1.16.13", + "@vueuse/core": "11.2.0", + "@vueuse/integrations": "11.2.0", + "@vueuse/shared": "11.2.0", "apexcharts": "3.54.1", "axios": "1.7.7", "dotenv": "16.4.5", "monaco-editor": "0.50.0", - "pinia": "2.2.4", + "pinia": "2.2.6", "qrcode": "1.5.4", - "quasar": "2.17.1", + "quasar": "2.17.2", "vue": "3.5.12", "vue-router": "4.4.5", "vue3-apexcharts": "1.7.0", diff --git a/quasar.config.js b/quasar.config.js index d9bc1e37..af5aa71b 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -8,6 +8,7 @@ // Configuration for your app // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js +const { mergeConfig } = require("vite"); const { configure } = require("quasar/wrappers"); const path = require("path"); require("dotenv").config(); @@ -78,9 +79,22 @@ module.exports = configure(function (/* ctx */) { // polyfillModulePreload: true, distDir: "dist/", - // extendViteConf (viteConf) {}, + /* eslint-disable quotes */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + extendViteConf(viteConf, { isServer, isClient }) { + viteConf.build = mergeConfig(viteConf.build, { + chunkSizeWarningLimit: 1600, + rollupOptions: { + output: { + entryFileNames: `[hash].js`, + chunkFileNames: `[hash].js`, + assetFileNames: `[hash].[ext]`, + }, + }, + }); + }, + /* eslint-enable quotes */ // viteVuePluginOptions: {}, - // vitePlugins: [] }, diff --git a/src/api/accounts.js b/src/api/accounts.js index b0286732..217b9051 100644 --- a/src/api/accounts.js +++ b/src/api/accounts.js @@ -31,6 +31,34 @@ export async function resetTwoFactor() { } } +// sessions api +export async function fetchUserSessions(id) { + try { + const { data } = await axios.get(`${baseUrl}/users/${id}/sessions/`); + return data; + } catch (e) { + console.error(e); + } +} + +export async function deleteAllUserSessions(id) { + try { + const { data } = await axios.delete(`${baseUrl}/users/${id}/sessions/`); + return data; + } catch (e) { + console.error(e); + } +} + +export async function deleteUserSession(id) { + try { + const { data } = await axios.delete(`${baseUrl}/sessions/${id}/`); + return data; + } catch (e) { + console.error(e); + } +} + // role api function export async function fetchRoles(params = {}) { try { diff --git a/src/api/core.ts b/src/api/core.ts index 6aa77b65..11e91307 100644 --- a/src/api/core.ts +++ b/src/api/core.ts @@ -8,8 +8,15 @@ import type { TestRunURLActionResponse, } from "@/types/core/urlactions"; +import type { CoreSetting } from "@/types/core/settings"; + const baseUrl = "/core"; +export async function fetchCoreSettings(params = {}): Promise { + const { data } = await axios.get("/core/settings/", { params: params }); + return data; +} + export async function fetchDashboardInfo(params = {}) { const { data } = await axios.get(`${baseUrl}/dashinfo/`, { params: params }); return data; diff --git a/src/boot/axios.js b/src/boot/axios.js index 9bd758bf..05f55d52 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -21,6 +21,7 @@ export function setErrorMessage(data, message) { export default function ({ app, router }) { app.config.globalProperties.$axios = axios; + axios.defaults.withCredentials = true; axios.interceptors.request.use( function (config) { @@ -65,12 +66,20 @@ export default function ({ app, router }) { // perms else if (error.response.status === 403) { // don't notify user if method is GET - if (error.config.method === "get" || error.config.method === "patch") + if ( + error.config.method === "get" || + error.config.method === "patch" || + error.config.url === "accounts/ssoproviders/token/" + ) return Promise.reject({ ...error }); text = error.response.data.detail; } // catch all for other 400 error messages - else if (error.response.status >= 400 && error.response.status < 500) { + else if ( + error.response.status >= 400 && + error.response.status < 500 && + error.response.status !== 423 + ) { if (error.config.responseType === "blob") { text = (await error.response.data.text()).replace(/^"|"$/g, ""); } else if (error.response.data.non_field_errors) { @@ -85,7 +94,7 @@ export default function ({ app, router }) { } } - if (text || error.response) { + if ((text || error.response) && error.response.status !== 423) { Notify.create({ color: "negative", message: text ? text : "", diff --git a/src/components/AdminManager.vue b/src/components/AdminManager.vue index e6eaa4f7..e5b82a83 100644 --- a/src/components/AdminManager.vue +++ b/src/components/AdminManager.vue @@ -1,159 +1,202 @@ diff --git a/src/components/modals/alerts/AlertsOverview.vue b/src/components/modals/alerts/AlertsOverview.vue index 5c4b1642..806dff80 100644 --- a/src/components/modals/alerts/AlertsOverview.vue +++ b/src/components/modals/alerts/AlertsOverview.vue @@ -249,6 +249,20 @@ export default { sortable: true, format: (a) => this.formatDate(a), }, + { + name: "client", + label: "Client", + field: "client", + align: "left", + sortable: true, + }, + { + name: "site", + label: "Site", + field: "site", + align: "left", + sortable: true, + }, { name: "hostname", label: "Agent", diff --git a/src/components/modals/coresettings/EditCoreSettings.vue b/src/components/modals/coresettings/EditCoreSettings.vue index 23303293..26009a7d 100644 --- a/src/components/modals/coresettings/EditCoreSettings.vue +++ b/src/components/modals/coresettings/EditCoreSettings.vue @@ -13,6 +13,7 @@ + @@ -636,6 +637,11 @@ + + + + + + + + + diff --git a/src/ee/sso/components/SSOProvidersForm.vue b/src/ee/sso/components/SSOProvidersForm.vue new file mode 100644 index 00000000..56c4795f --- /dev/null +++ b/src/ee/sso/components/SSOProvidersForm.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/ee/sso/components/SSOProvidersTable.vue b/src/ee/sso/components/SSOProvidersTable.vue new file mode 100644 index 00000000..9d2ac97d --- /dev/null +++ b/src/ee/sso/components/SSOProvidersTable.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/ee/sso/components/SSOSettings.vue b/src/ee/sso/components/SSOSettings.vue new file mode 100644 index 00000000..15b96028 --- /dev/null +++ b/src/ee/sso/components/SSOSettings.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/ee/sso/types/sso.ts b/src/ee/sso/types/sso.ts new file mode 100644 index 00000000..66fbdac0 --- /dev/null +++ b/src/ee/sso/types/sso.ts @@ -0,0 +1,33 @@ +/* +Copyright (c) 2023-present Amidaware Inc. +This file is subject to the EE License Agreement. +For details, see: https://license.tacticalrmm.com/ee +*/ + +import { User } from "@/types/accounts"; +export interface SSOProvider { + id: number; + name: string; + provider_id: string; + client_id: string; + secret: string; + server_url: string; + role: number | null; +} + +export interface SSOAccount { + uid: string; + display: string; + provider: string; + last_login: string; + date_joined: string; +} + +export interface SSOUser extends User { + social_accounts: SSOAccount[]; +} + +export interface SSOSettingsType { + sso_enabled: boolean; + block_local_user_logon: boolean; +} diff --git a/src/ee/sso/utils/cookies.ts b/src/ee/sso/utils/cookies.ts new file mode 100644 index 00000000..c646413d --- /dev/null +++ b/src/ee/sso/utils/cookies.ts @@ -0,0 +1,21 @@ +/* +Copyright (c) 2023-present Amidaware Inc. +This file is subject to the EE License Agreement. +For details, see: https://license.tacticalrmm.com/ee +*/ + +export function getCookie(name: string) { + let cookieValue = null; + if (document.cookie && document.cookie !== "") { + const cookies = document.cookie.split(";"); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) === name + "=") { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +} diff --git a/src/ee/sso/views/ProviderCallback.vue b/src/ee/sso/views/ProviderCallback.vue new file mode 100644 index 00000000..749353f1 --- /dev/null +++ b/src/ee/sso/views/ProviderCallback.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index c0c1d237..b57f29bd 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -157,7 +157,7 @@ - + diff --git a/src/store/index.js b/src/store/index.js index 16b8bdbe..20c3fa8d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -43,6 +43,8 @@ export default function () { }, server_scripts_enabled: true, web_terminal_enabled: true, + sso_enabled: false, + block_local_user_logon: false, }; }, getters: { @@ -159,6 +161,12 @@ export default function () { setWebTerminalEnabled(state, obj) { state.web_terminal_enabled = obj; }, + setSSOEnabled(state, obj) { + state.sso_enabled = obj; + }, + setBlockLocalUserLogon(state, obj) { + state.block_local_user_logon = obj; + }, }, actions: { setClientTreeSplitter(context, val) { @@ -245,6 +253,7 @@ export default function () { commit("setRunCmdPlaceholders", data.run_cmd_placeholder_text); commit("setServerScriptsEnabled", data.server_scripts_enabled); commit("setWebTerminalEnabled", data.web_terminal_enabled); + commit("setBlockLocalUserLogon", data.block_local_user_logon); if (data?.date_format !== "") commit("setDateFormat", data.date_format); else commit("setDateFormat", data.default_date_format); diff --git a/src/stores/auth.ts b/src/stores/auth.ts index b4855746..61ec197c 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,5 +1,6 @@ import { defineStore } from "pinia"; import { useStorage } from "@vueuse/core"; + import axios from "axios"; interface CheckCredentialsRequest { @@ -27,12 +28,18 @@ interface TOTPSetupResponse { export const useAuthStore = defineStore("auth", { state: () => ({ username: useStorage("user_name", null), + name: useStorage("name", null), token: useStorage("access_token", null), + ssoLoginProvider: useStorage("sso_provider", null), + provider_id: useStorage("provider_id", null), }), getters: { loggedIn: (state) => { return state.token !== null; }, + displayName: (state) => { + return state.name ? state.name : state.username; + }, }, actions: { async checkCredentials( @@ -43,13 +50,16 @@ export const useAuthStore = defineStore("auth", { if (!data.totp) { this.token = data.token; this.username = data.username; + this.name = data.name; } return data; }, async login(credentials: LoginRequest) { const { data } = await axios.post("/v2/login/", credentials); this.username = data.username; + this.name = data.name; this.token = data.token; + this.ssoLoginProvider = null; return data; }, @@ -61,6 +71,9 @@ export const useAuthStore = defineStore("auth", { } this.token = null; this.username = null; + this.name = null; + this.ssoLoginProvider = null; + this.provider_id = null; }, async setupTotp(): Promise { const { data } = await axios.post("/accounts/users/setup_totp/"); diff --git a/src/types/accounts.ts b/src/types/accounts.ts index 48dcde5c..bdb60f62 100644 --- a/src/types/accounts.ts +++ b/src/types/accounts.ts @@ -1,4 +1,13 @@ export interface User { id: number; username: string; + name: string; + email: string; +} + +export interface AuthToken { + digest: string; + created: string; + expiry: string; + user: string; } diff --git a/src/types/core/settings.ts b/src/types/core/settings.ts new file mode 100644 index 00000000..b0432523 --- /dev/null +++ b/src/types/core/settings.ts @@ -0,0 +1,3 @@ +export interface CoreSetting { + block_local_user_logon: boolean; +} diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index 6d5fbabc..27285b0a 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -49,7 +49,34 @@ + + +
Log in with SSO
+ + + + + + + + + {{ provider.name }} + + + +
+ @@ -84,10 +111,15 @@