Skip to content

Commit

Permalink
Intergrate Authorisation into embedded ui (#311)
Browse files Browse the repository at this point in the history
* WIP
- can login via dex.
- need to wire in auth to

* use user in auth requests to epinio, start tidying up flow

* fix a number of things

* support local auth as well

* started removing todos

* more todos

* tidying up

* apply new info / me changes

* Fix build epinio pkg

* Fix initialisation of cluster model & re-enter once locally logged in

* Fix lint, changes after self review

* Fix redirect, remove useless pagination stuff
- requires new backend

* Fix #361

* Remove unused query params from verify

* bump shell

* Temporarily add typing for is-url
- can be removed once rancher/dashboard#10035 is merged and published
  • Loading branch information
richard-cox authored Nov 14, 2023
1 parent a0870f8 commit 91f6d0b
Show file tree
Hide file tree
Showing 18 changed files with 729 additions and 297 deletions.
10 changes: 6 additions & 4 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
"node": ">=12"
},
"dependencies": {
"@rancher/shell": "^0.3.23",
"@rancher/components": "0.1.3",
"@rancher/shell": "^0.3.28",
"@types/lodash": "4.14.184",
"core-js": "3.21.1",
"css-loader": "6.7.3",
"@types/lodash": "4.14.184",
"@rancher/components": "0.1.3",
"oidc-client-ts": "^2.2.4",
"jszip": "3.10.1"
},
"resolutions": {
Expand All @@ -30,9 +31,10 @@
"lint": "./node_modules/.bin/eslint --max-warnings 0 --ext .js,.ts,.vue ."
},
"devDependencies": {
"@babel/eslint-parser": "^7.22.5",
"@rushstack/eslint-patch": "^1.3.2",
"@vue/eslint-config-standard": "5.1.2",
"@vue/eslint-config-typescript": "^11.0.3",
"@babel/eslint-parser": "^7.22.5"
"@types/is-url": "1.2.30"
}
}
12 changes: 6 additions & 6 deletions dashboard/pkg/epinio/config/epinio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,27 @@ export function init($plugin: any, store: any) {

// Internal Types
spoofedType({
label: store.getters['type-map/labelFor']({ id: EPINIO_TYPES.INSTANCE }, 2),
type: EPINIO_TYPES.INSTANCE,
label: store.getters['type-map/labelFor']({ id: EPINIO_TYPES.CLUSTER }, 2),
type: EPINIO_TYPES.CLUSTER,
product: EPINIO_PRODUCT_NAME,
collectionMethods: [],
schemas: [{
id: EPINIO_TYPES.INSTANCE,
id: EPINIO_TYPES.CLUSTER,
type: 'schema',
collectionMethods: [],
resourceFields: {},
}],
getInstances: async() => await EpinioDiscovery.discover(store),
});
configureType(EPINIO_TYPES.INSTANCE, {
configureType(EPINIO_TYPES.CLUSTER, {
isCreatable: false,
isEditable: false,
isRemovable: false,
showState: false,
showAge: false,
canYaml: false,
});
configureType(EPINIO_TYPES.INSTANCE, { customRoute: createEpinioRoute('c-cluster-resource', { resource: EPINIO_TYPES.INSTANCE }) });
configureType(EPINIO_TYPES.CLUSTER, { customRoute: createEpinioRoute('c-cluster-resource', { resource: EPINIO_TYPES.CLUSTER }) });

// App resource
configureType(EPINIO_TYPES.APP, {
Expand Down Expand Up @@ -266,7 +266,7 @@ export function init($plugin: any, store: any) {
AGE
]);

headers(EPINIO_TYPES.INSTANCE, [
headers(EPINIO_TYPES.CLUSTER, [
STATE,
{
name: 'name',
Expand Down
199 changes: 199 additions & 0 deletions dashboard/pkg/epinio/dialog/LoginDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<script>
import GenericPrompt from '@shell/dialog/GenericPrompt';
import Tabbed from '@shell/components/Tabbed/index.vue';
import Tab from '@shell/components/Tabbed/Tab.vue';
import epinioAuth, { EpinioAuthTypes } from '../utils/auth';
import Password from '@shell/components/form/Password';
import { LabeledInput } from '@components/Form/LabeledInput';
export default {
name: 'LoginDialog',
components: {
GenericPrompt, Tabbed, Tab, LabeledInput, Password
},
props: {
resources: {
type: Array,
required: true
}
},
data() {
return {
selectedTab: '',
username: '',
password: '',
config: {
applyMode: 'login',
applyAction: this.login,
},
tab: {
local: 'local',
dex: 'dex'
}
};
},
computed: {
cluster() {
return this.resources[0];
}
},
mounted() {
if (!this.cluster.oidcEnabled) {
this.selectedTab = this.tab.local;
}
},
methods: {
async login() {
const errors = [];
switch (this.selectedTab) {
case this.tab.local:
if (!this.username) {
errors.push('Username');
}
if (!this.password) {
errors.push('Password');
}
if (errors.length) {
return Promise.reject(new Error(`${ errors.join('/') } Required`));
}
await epinioAuth.login(this.cluster.createAuthConfig(EpinioAuthTypes.LOCAL, {
username: this.username,
password: this.password,
$axios: this.$axios,
}));
break;
case this.tab.dex:
await epinioAuth.login(this.cluster.createAuthConfig(EpinioAuthTypes.DEX));
break;
default:
throw new Error(`Unknown log in type: ${ this.selectedTab }`);
}
this.cluster.loggedIn = true;
this.$router.push({
name: 'epinio-c-cluster-dashboard',
params: { cluster: this.cluster.id }
});
},
tabChanged({ selectedName }) {
this.selectedTab = selectedName;
},
close() {
this.$emit('close', false);
}
}
};
</script>

<template>
<GenericPrompt
v-bind="config"
@close="close"
>
<h4
slot="title"
class="text-default-text login-dialog__title"
>
{{ t('epinio.login.modal.title') }}
</h4>

<template slot="body">
<Tabbed
v-if="cluster.oidcEnabled"
@changed="tabChanged"
>
<Tab
label-key="epinio.login.modal.local.tabLabel"
:name="tab.local"
:weight="3"
class="login-dialog__tab"
>
<form>
<div class="span-10 offset-1">
<div class="mb-20">
<LabeledInput
id="username"
ref="username"
v-model.trim="username"
data-testid="local-login-username"
:label="t('login.username')"
autocomplete="epinio-username"
:required="true"
/>
</div>
<div class="">
<Password
id="password"
ref="password"
v-model="password"
data-testid="local-login-password"
:label="t('login.password')"
autocomplete="epinio-password"
:required="true"
/>
</div>
</div>
</form>
</Tab>
<Tab
label-key="epinio.login.modal.dex.tabLabel"
:name="tab.dex"
:weight="2"
class="login-dialog__tab"
>
<p>
{{ t('epinio.login.modal.dex.prompt') }}
</p>
</Tab>
</Tabbed>
<form v-else>
<div class="span-10 offset-1 mt-15">
<div class="mb-20">
<LabeledInput
id="username"
ref="username"
v-model.trim="username"
data-testid="local-login-username"
:label="t('login.username')"
autocomplete="epinio-username"
:required="true"
/>
</div>
<div class="">
<Password
id="password"
ref="password"
v-model="password"
data-testid="local-login-password"
:label="t('login.password')"
autocomplete="epinio-password"
:required="true"
/>
</div>
</div>
</form>
</template>
</GenericPrompt>
</template>
<style lang='scss' scoped>
.login-dialog {
&__title {
margin-bottom: 0;
}
&__tab {
min-height: 145px;
}
}
</style>
12 changes: 12 additions & 0 deletions dashboard/pkg/epinio/l10n/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ asyncButton:
action: Export
success: Exported
waiting: Exporting&hellip;
login:
action: Log In
success: Log In
waiting: Log In
epinio:
label: Epinio
about:
Expand Down Expand Up @@ -362,6 +366,14 @@ epinio:
helmChart: Helm Chart
warnings:
noNamespace: There are no namespaces. Please create one before proceeding
login:
modal:
title: Log In
local:
tabLabel: Local
dex:
tabLabel: Auth Provider
prompt: Login via Auth Provider
model:
authConfig:
provider:
Expand Down
2 changes: 1 addition & 1 deletion dashboard/pkg/epinio/list/namespaces.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
import ResourceTable from '@shell/components/ResourceTable';
import Masthead from '@shell/components/ResourceList/Masthead';
import { Banner } from '@components/Banner';
import Banner from '@components/Banner/Banner.vue';
import { Card } from '@components/Card';
import { mapGetters, mapState } from 'vuex';
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
Expand Down
81 changes: 81 additions & 0 deletions dashboard/pkg/epinio/models/cluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Resource from '@shell/plugins/dashboard-store/resource-class';
import { EPINIO_TYPES } from '../types';
import epinioAuth, { EpinioAuthConfig, EpinioAuthLocalConfig, EpinioAuthTypes } from '../utils/auth';

export const EpinioInfoPath = `/api/v1/info`;

export default class EpinioCluster extends Resource {
type = EPINIO_TYPES.CLUSTER;

id: string;
name: string;
state?: string;
metadata?: { state: { transitioning: boolean, error: boolean, message: string }};
loggedIn: boolean;
api: string;
mgmtCluster: any;
oidcEnabled: boolean = false;

constructor(data: {
id: string,
name: string,
loggedIn: boolean,
api: string,
mgmtCluster: any,
}, ctx: any) {
super(data, ctx);
this.id = data.id;
this.name = data.name;
this.api = data.api;
this.loggedIn = data.loggedIn;
this.mgmtCluster = data.mgmtCluster;
}

get availableActions() {
return [
{
action: 'logOut',
enabled: this.loggedIn,
icon: 'icon icon-fw icon-chevron-right',
label: this.t('nav.userMenu.logOut'),
disabled: false,
},
];
}

get infoUrl() {
return this.api + EpinioInfoPath;
}

async logOut() {
try {
await epinioAuth.logout(this.createAuthConfig(EpinioAuthTypes.AGNOSTIC));

this.loggedIn = false;
} catch (err) {
console.error(`Failed to log out: ${ err }`);// eslint-disable-line no-console

this.metadata = {
state: {
transitioning: false,
error: true,
message: 'Failed to log out'
}
};
}
}

createAuthConfig(type: EpinioAuthTypes, localConfig?: EpinioAuthLocalConfig): EpinioAuthConfig {
const config: EpinioAuthConfig = {
type,
epinioUrl: this.api,
dexConfig: {
dashboardUrl: window.origin,
dexUrl: this.api.replace('epinio', 'auth')
},
localConfig
};

return config;
}
}
2 changes: 1 addition & 1 deletion dashboard/pkg/epinio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "epinio",
"description": "Application Development Engine for Kubernetes",
"icon": "https://raw.githubusercontent.com/rancher/dashboard/0b6cbe93e9ed3292294da178f119a500cc494db9/pkg/epinio/assets/logo-epinio.svg",
"version": "1.9.0-0",
"version": "1.11.0-0",
"private": false,
"rancher": true,
"license": "Apache-2.0",
Expand Down
Loading

0 comments on commit 91f6d0b

Please sign in to comment.