Skip to content

Commit

Permalink
Feature/auth (#1707)
Browse files Browse the repository at this point in the history
Added auth to hermes console
  • Loading branch information
szczygiel-m authored Aug 18, 2023
1 parent 9ff4ae7 commit bbb89bf
Show file tree
Hide file tree
Showing 34 changed files with 598 additions and 58 deletions.
4 changes: 3 additions & 1 deletion hermes-console-vue/json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,9 @@
"auth": {
"oauth": {
"enabled": false,
"url": "localhost:8092/auth",
"url": "localhost:8092",
"authorizationEndpoint": "/auth/oauth/authorize",
"tokenEndpoint": "/auth/oauth/token",
"clientId": "hermes",
"scope": "hermes"
},
Expand Down
4 changes: 4 additions & 0 deletions hermes-console-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
},
"dependencies": {
"axios": "1.4.0",
"base64-arraybuffer": "1.0.2",
"jwt-decode": "3.1.2",
"pinia": "2.1.4",
"pinia-plugin-persistedstate": "3.2.0",
"query-string": "8.1.0",
"vue": "3.3.4",
"vue-i18n": "9.2.2",
"vue-router": "4.2.2",
Expand Down
28 changes: 16 additions & 12 deletions hermes-console-vue/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
<template>
<v-app class="fill-height">
<console-header />

<v-main class="main">
<RouterView />
</v-main>

<console-footer />
</v-app>
</template>

<script setup lang="ts">
import { RouterView } from 'vue-router';
import { useAppConfigStore } from '@/store/app-config/useAppConfigStore';
import ConsoleFooter from '@/components/console-footer/ConsoleFooter.vue';
import ConsoleHeader from '@/components/console-header/ConsoleHeader.vue';
import LoadingSpinner from '@/components/loading-spinner/LoadingSpinner.vue';
const configStore = useAppConfigStore();
configStore.loadConfig();
</script>

<template>
<v-app class="fill-height">
<div v-if="configStore.appConfig">
<console-header />

<v-main class="main">
<RouterView />
</v-main>

<console-footer />
</div>
<loading-spinner v-else />
</v-app>
</template>

<style scoped lang="scss">
.main {
margin: 0 auto;
Expand Down
3 changes: 3 additions & 0 deletions hermes-console-vue/src/api/access-token-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AccessTokenResponse {
access_token: string;
}
2 changes: 2 additions & 0 deletions hermes-console-vue/src/api/app-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export interface AuthConfiguration {
export interface OAuthConfiguration {
enabled: boolean;
url: string;
authorizationEndpoint: string;
tokenEndpoint: string;
clientId: string;
scope: string;
}
Expand Down
25 changes: 25 additions & 0 deletions hermes-console-vue/src/api/hermes-client/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import axios from 'axios';
import qs from 'query-string';
import type { AccessTokenResponse } from '@/api/access-token-response';
import type { AppConfiguration } from '@/api/app-configuration';
import type { AxiosRequestConfig } from 'axios';
import type { ConstraintsConfig } from '@/api/constraints';
import type { ConsumerGroup } from '@/api/consumer-group';
import type { DatacenterReadiness } from '@/api/datacenter-readiness';
Expand Down Expand Up @@ -96,3 +99,25 @@ export function fetchGroupNames(): ResponsePromise<string[]> {
export function fetchStats(): ResponsePromise<Stats> {
return axios.get<Stats>(`/stats`);
}

export function fetchToken(
code: string,
url: string,
clientId: string,
redirectUri: string,
codeVerifier: string,
): ResponsePromise<AccessTokenResponse> {
return axios.post<AccessTokenResponse>(
url,
qs.stringify({
client_id: clientId,
code: code,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
code_verifier: codeVerifier,
}),
{
'Content-Type': 'application/x-www-form-urlencoded',
} as AxiosRequestConfig,
);
}
130 changes: 128 additions & 2 deletions hermes-console-vue/src/components/console-header/ConsoleHeader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { appConfigStoreState } from '@/dummy/store';
import {
appConfigStoreState,
authStoreState,
createTestingPiniaWithState,
} from '@/dummy/store';
import { createTestingPinia } from '@pinia/testing';
import { dummyAppConfig } from '@/dummy/app-config';
import { expect } from 'vitest';
import { expiredToken, validToken } from '@/utils/jwt-utils';
import { render } from '@/utils/test-utils';
import ConsoleHeader from '@/components/console-header/ConsoleHeader.vue';

describe('ConsoleHeader', () => {
it('renders properly', () => {
// when
const { getByRole } = render(ConsoleHeader);
const { getByRole } = render(ConsoleHeader, {
testPinia: createTestingPiniaWithState(),
});

// then
expect(getByRole('img')).toHaveAttribute('alt', 'Hermes');
Expand Down Expand Up @@ -36,4 +43,123 @@ describe('ConsoleHeader', () => {
// then
expect(getByText('TEST ENV')).toBeVisible();
});

it('should display login button', () => {
// when
const { getByText } = render(ConsoleHeader, {
testPinia: createTestingPinia({
initialState: {
appConfig: {
...appConfigStoreState,
appConfig: {
...dummyAppConfig,
auth: {
...dummyAppConfig.auth,
oauth: {
...dummyAppConfig.auth.oauth,
enabled: true,
},
},
},
},
auth: {
...authStoreState,
},
},
}),
});

// then
expect(getByText('header.signIn')).toBeVisible();
});

it('should not display login button when auth is disabled', () => {
// when
const { queryByText } = render(ConsoleHeader, {
testPinia: createTestingPinia({
initialState: {
appConfig: {
...appConfigStoreState,
appConfig: {
...dummyAppConfig,
auth: {
...dummyAppConfig.auth,
oauth: {
...dummyAppConfig.auth.oauth,
enabled: false,
},
},
},
},
auth: {
...authStoreState,
},
},
}),
});

// then
expect(queryByText('header.signIn')).not.toBeInTheDocument();
expect(queryByText('header.signout')).not.toBeInTheDocument();
});

it('should display logout button if token is valid', () => {
// when
const { getByText } = render(ConsoleHeader, {
testPinia: createTestingPinia({
initialState: {
appConfig: {
...appConfigStoreState,
appConfig: {
...dummyAppConfig,
auth: {
...dummyAppConfig.auth,
oauth: {
...dummyAppConfig.auth.oauth,
enabled: true,
},
},
},
},
auth: {
...authStoreState,
accessToken: validToken,
},
},
}),
});

// then
expect(getByText('header.logout')).toBeVisible();
});

it('should display login button if token expired', () => {
// when
const { getByText } = render(ConsoleHeader, {
testPinia: createTestingPinia({
initialState: {
appConfig: {
...appConfigStoreState,
appConfig: {
...dummyAppConfig,
auth: {
...dummyAppConfig.auth,
oauth: {
...dummyAppConfig.auth.oauth,
enabled: true,
},
},
},
},
auth: {
...authStoreState,
accessToken: expiredToken,
},
},
}),
});

// then
expect(getByText('header.signIn')).toBeVisible();
});
});
38 changes: 36 additions & 2 deletions hermes-console-vue/src/components/console-header/ConsoleHeader.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useAppConfigStore } from '@/store/app-config/useAppConfigStore';
import { useAuthStore } from '@/store/auth/useAuthStore';
import { useI18n } from 'vue-i18n';
import { useTheme } from 'vuetify';
import EnvironmentBadge from '@/components/environment-badge/EnviromentBadge.vue';
import ThemeSwitch from '@/components/theme-switch/ThemeSwitch.vue';
const { t } = useI18n();
const theme = useTheme();
const configStore = useAppConfigStore();
const authStore = useAuthStore();
const isLoggedIn = computed(() => authStore.isUserAuthorized);
function logIn() {
authStore.login(window.location.pathname);
}
function logout() {
authStore.logout();
}
</script>

<template>
<v-app-bar :elevation="2" density="compact">
<div class="header">
<!-- TODO: navigate to home -->
<div class="header-left">
<router-link to="/" custom v-slot="{ navigate }">
<router-link to="/ui" custom v-slot="{ navigate }">
<img
@click="navigate"
v-if="!theme.current.value.dark"
Expand All @@ -38,7 +54,25 @@
"
/>
</div>
<theme-switch />
<div>
<theme-switch />
<v-btn
v-if="configStore.loadedConfig.auth.oauth.enabled && !isLoggedIn"
color="primary"
variant="tonal"
@click="logIn()"
>
{{ t('header.signIn') }}
</v-btn>
<v-btn
v-if="configStore.loadedConfig.auth.oauth.enabled && isLoggedIn"
variant="tonal"
color="primary"
@click="logout"
>
{{ t('header.logout') }}
</v-btn>
</div>
</div>
</v-app-bar>
</template>
Expand Down
6 changes: 4 additions & 2 deletions hermes-console-vue/src/dummy/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ export const dummyAppConfig: AppConfiguration = {
auth: {
oauth: {
enabled: false,
url: '',
clientId: '',
url: 'http://localhost:8080',
authorizationEndpoint: '/authorization',
tokenEndpoint: '/token',
clientId: 'hermes',
scope: '',
},
headers: {
Expand Down
13 changes: 12 additions & 1 deletion hermes-console-vue/src/dummy/store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createTestingPinia } from '@pinia/testing';
import { dummyAppConfig } from '@/dummy/app-config';
import type { AppConfigStoreState } from '@/store/types';
import type { AppConfigStoreState } from '@/store/app-config/types';
import type { AuthStoreState } from '@/store/auth/types';

export const appConfigStoreState: AppConfigStoreState = {
appConfig: dummyAppConfig,
Expand All @@ -10,8 +11,18 @@ export const appConfigStoreState: AppConfigStoreState = {
},
};

export const authStoreState: AuthStoreState = {
accessToken: '',
codeVerifier: '',
loading: false,
error: {
loadAuth: null,
},
};

export const dummyStoresState = {
appConfig: appConfigStoreState,
auth: authStoreState,
};

export const createTestingPiniaWithState = () =>
Expand Down
4 changes: 4 additions & 0 deletions hermes-console-vue/src/i18n/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const en_US = {
adminTools: 'Admin tools',
},
},
header: {
signIn: 'Sign in',
logout: 'Logout',
},
consistency: {
connectionError: {
title: 'Connection error',
Expand Down
3 changes: 3 additions & 0 deletions hermes-console-vue/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createVuetify } from 'vuetify';
import App from './App.vue';
import axios from 'axios';
import messages from '@/i18n/messages';
import piniaPluginPersistedState from 'pinia-plugin-persistedstate';
import router from './router';

if (import.meta.env.DEV) {
Expand Down Expand Up @@ -56,6 +57,8 @@ const i18n = createI18n({

const store = createPinia();

store.use(piniaPluginPersistedState);

const app = createApp(App);

app.use(vuetify);
Expand Down
Loading

0 comments on commit bbb89bf

Please sign in to comment.