-
- {{ row.name }}
-
+ @click="login(row)"
+ >{{ row.name }}
{{ row.name }}
@@ -172,7 +185,7 @@ export default Vue.extend({
{{ row.api }}
@@ -181,6 +194,7 @@ export default Vue.extend({
+
@@ -202,6 +216,10 @@ div.root {
height: 40px;
display: flex;
align-items: center;
+
+ a {
+ cursor: pointer;
+ }
}
}
}
diff --git a/dashboard/pkg/epinio/routing/epinio-routing.ts b/dashboard/pkg/epinio/routing/epinio-routing.ts
index 6e5be8a..ac2727c 100644
--- a/dashboard/pkg/epinio/routing/epinio-routing.ts
+++ b/dashboard/pkg/epinio/routing/epinio-routing.ts
@@ -12,6 +12,7 @@ import ListEpinioResource from '../pages/c/_cluster/_resource/index.vue';
import CreateEpinioResource from '../pages/c/_cluster/_resource/create.vue';
import ViewEpinioResource from '../pages/c/_cluster/_resource/_id.vue';
import ViewEpinioNsResource from '../pages/c/_cluster/_resource/_namespace/_id.vue';
+import AuthVerify from '../pages/auth/verify.vue';
const meta = {
product: EPINIO_PRODUCT_NAME,
@@ -19,6 +20,11 @@ const meta = {
};
const routes: RouteConfig[] = [{
+ name: `${ EPINIO_PRODUCT_NAME }-auth-verify`,
+ path: `/:product/auth/verify`,
+ component: AuthVerify,
+ meta
+}, {
name: `${ EPINIO_PRODUCT_NAME }-c-cluster-dashboard`,
path: `/:product/c/:cluster/dashboard`,
component: Dashboard,
diff --git a/dashboard/pkg/epinio/store/epinio-mgmt-store/actions.ts b/dashboard/pkg/epinio/store/epinio-mgmt-store/actions.ts
index ca932d4..e168f1d 100644
--- a/dashboard/pkg/epinio/store/epinio-mgmt-store/actions.ts
+++ b/dashboard/pkg/epinio/store/epinio-mgmt-store/actions.ts
@@ -31,7 +31,7 @@ export default {
// Load management style schemas
const spoofedSchemas = rootGetters['type-map/spoofedSchemas'](EPINIO_PRODUCT_NAME);
- const instances = spoofedSchemas.find((schema: any) => schema.id === EPINIO_TYPES.INSTANCE);
+ const instances = spoofedSchemas.find((schema: any) => schema.id === EPINIO_TYPES.CLUSTER);
const res = { data: [instances] };
diff --git a/dashboard/pkg/epinio/store/epinio-store/actions.ts b/dashboard/pkg/epinio/store/epinio-store/actions.ts
index 450bb50..031d4c9 100644
--- a/dashboard/pkg/epinio/store/epinio-store/actions.ts
+++ b/dashboard/pkg/epinio/store/epinio-store/actions.ts
@@ -3,12 +3,15 @@ import { handleSpoofedRequest } from '@shell/plugins/dashboard-store/actions';
import { classify } from '@shell/plugins/dashboard-store/classify';
import { normalizeType } from '@shell/plugins/dashboard-store/normalize';
import { NAMESPACE_FILTERS } from '@shell/store/prefs';
-import { base64Encode } from '@shell/utils/crypto';
import { createNamespaceFilterKeyWithId } from '@shell/utils/namespace-filter';
import { parse as parseUrl, stringify as unParseUrl } from '@shell/utils/url';
+import epinioAuth, { EpinioAuthTypes } from '../../utils/auth';
+
import {
EpinioInfo, EpinioVersion, EPINIO_MGMT_STORE, EPINIO_PRODUCT_NAME, EPINIO_STANDALONE_CLUSTER_NAME, EPINIO_TYPES
} from '../../types';
+import EpinioCluster from '../../models/cluster';
+import { RedirectToError } from '@shell/utils/error';
const createId = (schema: any, resource: any) => {
const name = resource.meta?.name || resource.name;
@@ -36,17 +39,17 @@ export default {
commit('remove', obj);
},
- async request({ rootGetters, dispatch, getters }: any, {
+ async request(context: any, {
opt, type, clusterId, growlOnError = false
}: any) {
+ const { rootGetters, dispatch, getters } = context;
+
const spoofedRes = await handleSpoofedRequest(rootGetters, EPINIO_PRODUCT_NAME, opt, EPINIO_PRODUCT_NAME);
if (spoofedRes) {
return spoofedRes;
}
- // @TODO queue/defer duplicate requests
- opt.depaginate = opt.depaginate !== false;
opt.url = opt.url.replace(/\/*$/g, '');
const isSingleProduct = rootGetters['isSingleProduct'];
@@ -58,13 +61,13 @@ export default {
ps = dispatch('findSingleProductCNSI').then((cnsi: any) => `/pp/v1/direct/r/${ cnsi?.guid }`);
}
} else {
- ps = dispatch(`${ EPINIO_MGMT_STORE }/findAll`, { type: EPINIO_TYPES.INSTANCE }, { root: true }).then(() => '');
+ ps = dispatch(`${ EPINIO_MGMT_STORE }/findAll`, { type: EPINIO_TYPES.CLUSTER }, { root: true }).then(() => '');
}
// opt.httpsAgent = new https.Agent({ rejectUnauthorized: false });
return await ps
- .then((prependPath = opt?.prependPath) => {
+ .then(async(prependPath = opt?.prependPath) => {
if (isSingleProduct) {
if (opt.url.startsWith('/')) {
opt.url = prependPath + opt.url;
@@ -78,12 +81,14 @@ export default {
}
} else {
const currentClusterId = clusterId || rootGetters['clusterId'];
- const currentCluster = rootGetters[`${ EPINIO_MGMT_STORE }/byId`](EPINIO_TYPES.INSTANCE, currentClusterId);
+ const currentCluster: EpinioCluster = rootGetters[`${ EPINIO_MGMT_STORE }/byId`](EPINIO_TYPES.CLUSTER, currentClusterId);
- opt.headers = {
- ...opt.headers,
- Authorization: `Basic ${ base64Encode(`${ currentCluster.username }:${ currentCluster.password }`) }`
- };
+ if (currentCluster.createAuthConfig) {
+ opt.headers = {
+ ...opt.headers,
+ Authorization: await epinioAuth.authHeader(currentCluster.createAuthConfig(EpinioAuthTypes.AGNOSTIC))
+ };
+ }
opt.url = `${ currentCluster.api }${ opt.url }`;
}
@@ -92,17 +97,7 @@ export default {
})
.then((res) => {
if ( opt.depaginate ) {
- // @TODO but API never sends it
- /*
- return new Promise((resolve, reject) => {
- const next = res.pagination.next;
- if (!next ) [
- return resolve();
- }
-
- dispatch('request')
- });
- */
+ throw Error('depaginate not supported');
}
if ( opt.responseType ) {
@@ -128,9 +123,12 @@ export default {
const res = err.response;
// Go to the logout page for 401s, unless redirectUnauthorized specifically disables (for the login page)
- if ( opt.redirectUnauthorized !== false && (process as any).client && res.status === 401 ) {
- // return Promise.reject(err);
- dispatch('auth/logout', opt.logoutOnError, { root: true });
+ if ( opt.redirectUnauthorized !== false && res.status === 401 ) {
+ if (isSingleProduct) {
+ dispatch('auth/logout', opt.logoutOnError, { root: true });
+ } else {
+ return Promise.reject(new RedirectToError('Auth failed, return user to epinio cluster list', `/epinio`));
+ }
} else if (growlOnError) {
dispatch('growl/fromError', { title: `Epinio Request to ${ opt.url }`, err }, { root: true });
}
@@ -165,6 +163,12 @@ export default {
}
},
+ redirect(ctx: any, location: any) {
+ const router = (this as any).$router;
+
+ router.replace(location);
+ },
+
async onLogout({ dispatch, commit }: any) {
await dispatch(`unsubscribe`);
await commit('reset');
@@ -228,7 +232,7 @@ export default {
};
const spoofedSchemas = rootGetters['type-map/spoofedSchemas'](EPINIO_PRODUCT_NAME);
- const excludeInstances = spoofedSchemas.filter((schema: any) => schema.id !== EPINIO_TYPES.INSTANCE);
+ const excludeInstances = spoofedSchemas.filter((schema: any) => schema.id !== EPINIO_TYPES.CLUSTER);
const data = [
...res.data,
@@ -335,4 +339,5 @@ export default {
return info;
},
+
};
diff --git a/dashboard/pkg/epinio/types.ts b/dashboard/pkg/epinio/types.ts
index 89a6226..8b6b143 100644
--- a/dashboard/pkg/epinio/types.ts
+++ b/dashboard/pkg/epinio/types.ts
@@ -21,7 +21,7 @@ export const EPINIO_TYPES = {
SERVICE_INSTANCE: 'services',
// Internal
DASHBOARD: 'dashboard',
- INSTANCE: 'instance',
+ CLUSTER: 'cluster',
APP_ACTION: 'application-action',
APP_INSTANCE: 'application-instance',
};
diff --git a/dashboard/pkg/epinio/utils/auth.ts b/dashboard/pkg/epinio/utils/auth.ts
new file mode 100644
index 0000000..896fa00
--- /dev/null
+++ b/dashboard/pkg/epinio/utils/auth.ts
@@ -0,0 +1,225 @@
+import { User, UserManager } from 'oidc-client-ts';
+import { base64Encode } from '@shell/utils/crypto';
+
+/* eslint-disable no-unused-vars */
+export enum EpinioAuthTypes {
+ LOCAL = 'local',
+ DEX = 'dex',
+ AGNOSTIC = 'agnostic'
+}
+/* eslint-enable no-unused-vars */
+
+export interface EpinioAuthUser {
+ email: string,
+ name: string,
+}
+
+export interface EpinioAuthDexConfig {
+ dashboardUrl: string,
+ dexUrl: string,
+}
+
+export interface EpinioAuthLocalConfig {
+ username: string,
+ password: string,
+ $axios?: any,
+}
+
+export interface EpinioAuthConfig {
+ type: EpinioAuthTypes,
+ epinioUrl: string,
+ dexConfig?: EpinioAuthDexConfig,
+ localConfig?: EpinioAuthLocalConfig
+}
+
+class EpinioAuth {
+ private dexUserManager?: UserManager;
+ private localUserManager?: {
+ epinioUrl: string,
+ config: EpinioAuthLocalConfig
+ };
+
+ private isLocal() {
+ return this.localUserManager?.config.username && this.localUserManager?.config.password;
+ }
+
+ async isLoggedIn(config: EpinioAuthConfig) {
+ if (!config || (config.type === EpinioAuthTypes.LOCAL || config.type === EpinioAuthTypes.AGNOSTIC)) {
+ if (this.isLocal()) {
+ return true;
+ }
+ }
+
+ if (!config || (config.type === EpinioAuthTypes.DEX || config.type === EpinioAuthTypes.AGNOSTIC)) {
+ if (!this.dexUserManager && config?.dexConfig) {
+ await this.initialiseDex(config.dexConfig);
+ }
+ const dexUser = await this.dexUserManager?.getUser();
+
+ return dexUser && dexUser?.profile.iss === config?.dexConfig?.dexUrl;
+ }
+
+ return false;
+ }
+
+ async login(config: EpinioAuthConfig): Promise