🔀 Merge pull request #1573 from twsouthwick/oidc

Enable public application OIDC client support
Fixes #823
This commit is contained in:
Alicia Sykes
2024-05-13 21:02:44 +01:00
committed by GitHub
9 changed files with 199 additions and 3 deletions

View File

@@ -24,6 +24,13 @@
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
class="layout-icon" tabindex="-2"
/>
<!-- If user logged in via oidc, show oidc logout button -->
<IconLogout
v-if="userType == userStateEnum.oidcEnabled"
@click="oidcLogout()"
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
class="layout-icon" tabindex="-2"
/>
</div>
</div>
</template>
@@ -32,6 +39,7 @@
import router from '@/router';
import { logout as registerLogout } from '@/utils/Auth';
import { getKeycloakAuth } from '@/utils/KeycloakAuth';
import { getOidcAuth } from '@/utils/OidcAuth';
import { localStorageKeys, userStateEnum } from '@/utils/defaults';
import IconLogout from '@/assets/interface-icons/user-logout.svg';
@@ -56,6 +64,13 @@ export default {
router.push({ path: '/login' });
}, 500);
},
oidcLogout() {
const oidc = getOidcAuth();
this.$toasted.show(this.$t('login.logout-message'));
setTimeout(() => {
oidc.logout();
}, 500);
},
keycloakLogout() {
const keycloak = getKeycloakAuth();
this.$toasted.show(this.$t('login.logout-message'));

View File

@@ -22,6 +22,7 @@ import clickOutside from '@/directives/ClickOutside'; // Directive for closing p
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
import { initHeaderAuth, isHeaderAuthEnabled } from '@/utils/HeaderAuth';
import { initOidcAuth, isOidcEnabled } from '@/utils/OidcAuth';
import Keys from '@/utils/StoreMutations';
import ErrorHandler from '@/utils/ErrorHandler';
@@ -62,7 +63,13 @@ const mount = () => new Vue({
}).$mount('#app');
store.dispatch(Keys.INITIALIZE_CONFIG).then(() => {
if (isKeycloakEnabled()) { // If Keycloak is enabled, initialize auth
if (isOidcEnabled()) {
initOidcAuth()
.then(() => mount())
.catch((e) => {
ErrorHandler('Failed to authenticate with OIDC', e);
});
} else if (isKeycloakEnabled()) { // If Keycloak is enabled, initialize auth
initKeycloakAuth()
.then(() => mount())
.catch((e) => {

View File

@@ -3,6 +3,7 @@ import ConfigAccumulator from '@/utils/ConfigAccumalator';
import ErrorHandler from '@/utils/ErrorHandler';
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
import { isOidcEnabled } from '@/utils/OidcAuth';
/* Uses config accumulator to get and return app config */
const getAppConfig = () => {
@@ -96,7 +97,7 @@ export const isAuthEnabled = () => {
/* Returns true if guest access is enabled */
export const isGuestAccessEnabled = () => {
const appConfig = getAppConfig();
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled() && !isOidcEnabled()) {
return appConfig.auth.enableGuestAccess || false;
}
return false;
@@ -229,8 +230,10 @@ export const getUserState = () => {
loggedIn,
guestAccess,
keycloakEnabled,
oidcEnabled,
} = userStateEnum; // Numeric enum options
if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
if (isOidcEnabled()) return oidcEnabled;
if (!isAuthEnabled()) return notConfigured; // No auth enabled
if (isLoggedIn()) return loggedIn; // User is logged in
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing

View File

@@ -541,6 +541,33 @@
]
}
},
"enableOidc": {
"title": "Enable OIDC?",
"type": "boolean",
"default": false,
"description": "If set to true, enable OIDC. See appConfig.auth.oidc"
},
"oidc": {
"type": "object",
"description": "Configuration for OIDC",
"additionalProperties": false,
"required": [
"clientId",
"endpoint"
],
"properties": {
"endpoint": {
"title": "OIDC Endpoint",
"type": "string",
"description": "Endpoint of OIDC provider"
},
"clientId": {
"title": "OIDC Client Id",
"type": "string",
"description": "ClientId from OIDC provider"
}
}
},
"enableHeaderAuth": {
"title": "Enable HeaderAuth?",
"type": "boolean",

90
src/utils/OidcAuth.js Normal file
View File

@@ -0,0 +1,90 @@
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { localStorageKeys } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import { statusMsg, statusErrorMsg } from '@/utils/CoolConsole';
const getAppConfig = () => {
const Accumulator = new ConfigAccumulator();
const config = Accumulator.config();
return config.appConfig || {};
};
class OidcAuth {
constructor() {
const { auth } = getAppConfig();
const { clientId, endpoint } = auth.oidc;
const settings = {
userStore: new WebStorageStateStore({ store: window.localStorage }),
authority: endpoint,
client_id: clientId,
redirect_uri: `${window.location.origin}`,
response_type: 'code',
scope: 'openid profile email roles groups',
response_mode: 'query',
filterProtocolClaims: true,
};
this.userManager = new UserManager(settings);
}
async login() {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
if (code) {
await this.userManager.signinCallback(window.location.href);
window.location.href = '/';
return;
}
const user = await this.userManager.getUser();
if (user === null) {
await this.userManager.signinRedirect();
} else {
const { roles, groups } = user.profile;
const info = {
groups,
roles,
};
statusMsg(`user: ${user.profile.preferred_username}`, JSON.stringify(info));
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
localStorage.setItem(localStorageKeys.USERNAME, user.profile.preferred_username);
}
}
async logout() {
localStorage.removeItem(localStorageKeys.USERNAME);
localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
try {
await this.userManager.signoutRedirect();
} catch (reason) {
statusErrorMsg('logout', 'could not log out. Redirecting to OIDC instead', reason);
window.location.href = this.userManager.settings.authority;
}
}
}
export const isOidcEnabled = () => {
const { auth } = getAppConfig();
if (!auth) return false;
return auth.enableOidc || false;
};
let oidc;
export const initOidcAuth = () => {
oidc = new OidcAuth();
return oidc.login();
};
export const getOidcAuth = () => {
if (!oidc) {
ErrorHandler("OIDC not initialized, can't get instance of class");
}
return oidc;
};

View File

@@ -305,6 +305,7 @@ module.exports = {
guestAccess: 2,
notLoggedIn: 3,
keycloakEnabled: 4,
oidcEnabled: 5,
},
/* Progressive Web App settings, used by Vue Config */
pwa: {