mirror of
https://github.com/simon987/sist2.git
synced 2025-12-11 14:38:54 +00:00
Add support for auth0
This commit is contained in:
@@ -1,19 +1,33 @@
|
||||
<template>
|
||||
<div id="app" :class="getClass()">
|
||||
<div id="app" :class="getClass()" v-if="!authLoading">
|
||||
<NavBar></NavBar>
|
||||
<router-view v-if="!configLoading"/>
|
||||
</div>
|
||||
<div class="loading-page" v-else>
|
||||
<div class="loading-spinners">
|
||||
<b-spinner type="grow" variant="primary"></b-spinner>
|
||||
<b-spinner type="grow" variant="primary"></b-spinner>
|
||||
<b-spinner type="grow" variant="primary"></b-spinner>
|
||||
</div>
|
||||
<div class="loading-text">
|
||||
Loading • Chargement • 装载
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from "@/components/NavBar";
|
||||
import {mapGetters} from "vuex";
|
||||
import {mapActions, mapGetters, mapMutations} from "vuex";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
import {setupAuth0} from "@/main";
|
||||
|
||||
export default {
|
||||
components: {NavBar},
|
||||
data() {
|
||||
return {
|
||||
configLoading: false
|
||||
configLoading: false,
|
||||
authLoading: true,
|
||||
sist2InfoLoading: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -30,9 +44,43 @@ export default {
|
||||
this.configLoading = true;
|
||||
window.setTimeout(() => this.configLoading = false, 10);
|
||||
}
|
||||
|
||||
if (mutation.type === "setAuth0Token") {
|
||||
this.authLoading = false;
|
||||
}
|
||||
});
|
||||
|
||||
Sist2Api.getSist2Info().then(data => {
|
||||
|
||||
if (data.auth0Enabled) {
|
||||
this.authLoading = true;
|
||||
setupAuth0(data.auth0Domain, data.auth0ClientId, data.auth0Audience)
|
||||
|
||||
this.$auth.$watch("loading", loading => {
|
||||
if (loading === false) {
|
||||
|
||||
if (!this.$auth.isAuthenticated) {
|
||||
this.$auth.loginWithRedirect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove "code" param
|
||||
window.history.replaceState({}, "", "/" + window.location.hash);
|
||||
|
||||
this.$store.dispatch("loadAuth0Token");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.authLoading = false;
|
||||
}
|
||||
|
||||
this.setSist2Info(data);
|
||||
this.setIndices(data.indices)
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["setSist2Info",]),
|
||||
...mapMutations(["setIndices",]),
|
||||
getClass() {
|
||||
return {
|
||||
"theme-light": this.optTheme === "light",
|
||||
@@ -314,4 +362,22 @@ mark {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.loading-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
gap: 15px
|
||||
}
|
||||
|
||||
.loading-spinners {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
|
||||
<span class="badge badge-pill version" v-if="$store && $store.state.sist2Info">
|
||||
v{{ sist2Version() }}<span v-if="isDebug()">-dbg</span><span v-if="isLegacy() && !hideLegacy()">-<a
|
||||
href="https://github.com/simon987/sist2/blob/master/docs/USAGE.md#elasticsearch" target="_blank">legacyES</a></span>
|
||||
href="https://github.com/simon987/sist2/blob/master/docs/USAGE.md#elasticsearch"
|
||||
target="_blank">legacyES</a></span>
|
||||
</span>
|
||||
|
||||
<span v-if="$store && $store.state.sist2Info" class="tagline" v-html="tagline()"></span>
|
||||
|
||||
<b-button class="ml-auto" to="stats" variant="link">{{ $t("stats") }}</b-button>
|
||||
<b-button to="config" variant="link">{{ $t("config") }}</b-button>
|
||||
<b-button v-if="$auth && $auth.isAuthenticated" variant="link" @click="onLogoutClick()">logout</b-button>
|
||||
</b-navbar>
|
||||
</template>
|
||||
|
||||
@@ -40,6 +42,9 @@ export default {
|
||||
},
|
||||
hideLegacy() {
|
||||
return this.$store.state.optHideLegacy;
|
||||
},
|
||||
onLogoutClick() {
|
||||
this.$auth.logout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,8 +335,8 @@ export default {
|
||||
indexPicker: {
|
||||
selectNone: "Sélectionner aucun",
|
||||
selectAll: "Sélectionner tout",
|
||||
selectedIndex: "indice sélectionné",
|
||||
selectedIndices: "indices sélectionnés",
|
||||
selectedIndex: "index sélectionné",
|
||||
selectedIndices: "index sélectionnés",
|
||||
},
|
||||
},
|
||||
"zh-CN": {
|
||||
|
||||
@@ -3,16 +3,32 @@ import 'mutationobserver-shim'
|
||||
import Vue from 'vue'
|
||||
import './plugins/bootstrap-vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import router, {setUseAuth0} from './router'
|
||||
import store from './store'
|
||||
import VueI18n from "vue-i18n";
|
||||
import messages from "@/i18n/messages";
|
||||
|
||||
import { Auth0Plugin } from './plugins/auth0';
|
||||
|
||||
import VueRouter from "vue-router";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
export function setupAuth0(domain, clientId, audience) {
|
||||
|
||||
setUseAuth0(true);
|
||||
|
||||
Vue.use(Auth0Plugin, {
|
||||
domain,
|
||||
clientId,
|
||||
audience,
|
||||
onRedirectCallback: appState => {}
|
||||
});
|
||||
}
|
||||
|
||||
Vue.prototype.$auth = null;
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
||||
138
sist2-vue/src/plugins/auth0.js
Normal file
138
sist2-vue/src/plugins/auth0.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import Vue from 'vue';
|
||||
import {createAuth0Client} from '@auth0/auth0-spa-js';
|
||||
|
||||
/** Define a default action to perform after authentication */
|
||||
const DEFAULT_REDIRECT_CALLBACK = () =>
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
|
||||
let instance;
|
||||
|
||||
/** Returns the current instance of the SDK */
|
||||
export const getInstance = () => instance;
|
||||
|
||||
/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */
|
||||
export const useAuth0 = ({
|
||||
domain, clientId, audience,
|
||||
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
|
||||
redirectUri = window.location.origin,
|
||||
}) => {
|
||||
if (instance) return instance;
|
||||
|
||||
// The 'instance' is simply a Vue object
|
||||
instance = new Vue({
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
isAuthenticated: false,
|
||||
user: {},
|
||||
auth0Client: null,
|
||||
popupOpen: false,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/** Authenticates the user using a popup window */
|
||||
async loginWithPopup(options, config) {
|
||||
this.popupOpen = true;
|
||||
|
||||
try {
|
||||
await this.auth0Client.loginWithPopup(options, config);
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.isAuthenticated = await this.auth0Client.isAuthenticated();
|
||||
this.error = null;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
// eslint-disable-next-line
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.popupOpen = false;
|
||||
}
|
||||
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.isAuthenticated = true;
|
||||
},
|
||||
/** Handles the callback when logging in using a redirect */
|
||||
async handleRedirectCallback() {
|
||||
this.loading = true;
|
||||
try {
|
||||
await this.auth0Client.handleRedirectCallback();
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.isAuthenticated = true;
|
||||
this.error = null;
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
/** Authenticates the user using the redirect method */
|
||||
loginWithRedirect(o) {
|
||||
return this.auth0Client.loginWithRedirect(o);
|
||||
},
|
||||
/** Returns all the claims present in the ID token */
|
||||
getIdTokenClaims(o) {
|
||||
return this.auth0Client.getIdTokenClaims(o);
|
||||
},
|
||||
/** Returns the access token. If the token is invalid or missing, a new one is retrieved */
|
||||
getTokenSilently(o) {
|
||||
return this.auth0Client.getTokenSilently(o);
|
||||
},
|
||||
|
||||
/** Gets the access token using a popup window */
|
||||
|
||||
getTokenWithPopup(o) {
|
||||
return this.auth0Client.getTokenWithPopup(o);
|
||||
},
|
||||
/** Logs the user out and removes their session on the authorization server */
|
||||
logout() {
|
||||
return this.auth0Client.logout({ logoutParams: { returnTo: window.location.origin } });
|
||||
}
|
||||
},
|
||||
/** Use this lifecycle method to instantiate the SDK client */
|
||||
async created() {
|
||||
// Create a new instance of the SDK client using members of the given options object
|
||||
this.auth0Client = await createAuth0Client({
|
||||
domain: domain,
|
||||
clientId: clientId,
|
||||
|
||||
authorizationParams: {
|
||||
redirect_uri: redirectUri,
|
||||
audience: audience,
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// If the user is returning to the app after authentication..
|
||||
if (
|
||||
window.location.search.includes('code=') &&
|
||||
window.location.search.includes('state=')
|
||||
) {
|
||||
// handle the redirect and retrieve tokens
|
||||
const {appState} = await this.auth0Client.handleRedirectCallback();
|
||||
|
||||
this.error = null;
|
||||
|
||||
// Notify subscribers that the redirect callback has happened, passing the appState
|
||||
// (useful for retrieving any pre-authentication state)
|
||||
onRedirectCallback(appState);
|
||||
}
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
// Initialize our internal authentication state
|
||||
this.isAuthenticated = await this.auth0Client.isAuthenticated();
|
||||
this.user = await this.auth0Client.getUser();
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
// Create a simple Vue plugin to expose the wrapper object throughout the application
|
||||
export const Auth0Plugin = {
|
||||
install(Vue, options) {
|
||||
Vue.prototype.$auth = useAuth0(options);
|
||||
}
|
||||
};
|
||||
28
sist2-vue/src/router/auth0.ts
Normal file
28
sist2-vue/src/router/auth0.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {getInstance} from "@/plugins/auth0";
|
||||
|
||||
export const authGuard = (to, from, next) => {
|
||||
|
||||
const authService = getInstance();
|
||||
|
||||
const fn = () => {
|
||||
// If the user is authenticated, continue with the route
|
||||
if (authService.isAuthenticated) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Otherwise, log in
|
||||
authService.loginWithRedirect({appState: {targetUrl: to.fullPath}});
|
||||
};
|
||||
|
||||
// If loading has already finished, check our auth state using `fn()`
|
||||
if (!authService.loading) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
// Watch for the loading property to change before we check isAuthenticated
|
||||
authService.$watch("loading", loading => {
|
||||
if (loading === false) {
|
||||
return fn();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -4,14 +4,29 @@ import StatsPage from "../views/StatsPage.vue"
|
||||
import Configuration from "../views/Configuration.vue"
|
||||
import SearchPage from "@/views/SearchPage.vue";
|
||||
import FilePage from "@/views/FilePage.vue";
|
||||
import {authGuard as auth0AuthGuard} from "@/router/auth0";
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
let USE_AUTH0 = false
|
||||
export function setUseAuth0(val) {
|
||||
USE_AUTH0 = val;
|
||||
}
|
||||
|
||||
const authGuard = (to, from, next) => {
|
||||
if (USE_AUTH0) {
|
||||
return auth0AuthGuard(to, from, next);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const routes: Array<RouteConfig> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "SearchPage",
|
||||
component: SearchPage
|
||||
component: SearchPage,
|
||||
beforeEnter: authGuard
|
||||
},
|
||||
{
|
||||
path: "/stats",
|
||||
@@ -34,7 +49,7 @@ const router = new VueRouter({
|
||||
mode: "hash",
|
||||
base: process.env.BASE_URL,
|
||||
routes,
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
// return desired position
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import Vuex from "vuex"
|
||||
import VueRouter, {Route} from "vue-router";
|
||||
import {EsHit, EsResult, EsTag, Index, Tag} from "@/Sist2Api";
|
||||
import {deserializeMimes, randomSeed, serializeMimes} from "@/util";
|
||||
import {getInstance} from "@/plugins/auth0.js";
|
||||
|
||||
const CONF_VERSION = 2;
|
||||
|
||||
@@ -82,7 +83,9 @@ export default new Vuex.Store({
|
||||
uiDetailsMimeAgg: null,
|
||||
uiShowDetails: false,
|
||||
|
||||
uiMimeMap: [] as any[]
|
||||
uiMimeMap: [] as any[],
|
||||
|
||||
auth0Token: null
|
||||
},
|
||||
mutations: {
|
||||
setUiShowDetails: (state, val) => state.uiShowDetails = val,
|
||||
@@ -188,6 +191,7 @@ export default new Vuex.Store({
|
||||
busTnTouchStart: (doc_id) => {
|
||||
// noop
|
||||
},
|
||||
setAuth0Token: (state, val) => state.auth0Token = val,
|
||||
},
|
||||
actions: {
|
||||
setSist2Info: (store, val) => {
|
||||
@@ -332,6 +336,14 @@ export default new Vuex.Store({
|
||||
commit("setUiLightboxCaptions", []);
|
||||
commit("setUiLightboxKey", 0);
|
||||
commit("setUiDetailsMimeAgg", null);
|
||||
},
|
||||
async loadAuth0Token({commit}) {
|
||||
const authService = getInstance();
|
||||
|
||||
const accessToken = await authService.getTokenSilently()
|
||||
commit("setAuth0Token", accessToken);
|
||||
|
||||
document.cookie = `sist2-auth0=${accessToken};`;
|
||||
}
|
||||
},
|
||||
modules: {},
|
||||
|
||||
@@ -153,7 +153,7 @@ export default {
|
||||
components: {LanguageIcon, GearIcon, DebugInfo, Preloader},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
loading: false,
|
||||
configLoading: false,
|
||||
langOptions: [
|
||||
{value: "en", text: this.$t("lang.en")},
|
||||
@@ -257,11 +257,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
sist2.getSist2Info().then(data => {
|
||||
this.setSist2Info(data);
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type.startsWith("setOpt")) {
|
||||
this.$store.dispatch("updateConfiguration");
|
||||
|
||||
@@ -107,13 +107,6 @@ export default Vue.extend({
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if (this.$store.state.sist2Info === null) {
|
||||
sist2.getSist2Info().then(data => {
|
||||
this.$store.dispatch("setSist2Info", data);
|
||||
this.$store.commit("setIndices", data.indices);
|
||||
});
|
||||
}
|
||||
|
||||
let query = null;
|
||||
if (this.$route.query.byId) {
|
||||
query = this.findById(this.$route.query.byId);
|
||||
|
||||
@@ -130,25 +130,18 @@ export default Vue.extend({
|
||||
});
|
||||
});
|
||||
|
||||
this.setIndices(this.$store.getters["sist2Info"].indices)
|
||||
|
||||
this.getDateRange().then((range: { min: number, max: number }) => {
|
||||
this.setDateBoundsMin(range.min);
|
||||
this.setDateBoundsMax(range.max);
|
||||
|
||||
sist2.getSist2Info().then(data => {
|
||||
this.setSist2Info(data);
|
||||
this.setIndices(data.indices);
|
||||
const doBlankSearch = !this.$store.state.optUpdateMimeMap;
|
||||
|
||||
const doBlankSearch = !this.$store.state.optUpdateMimeMap;
|
||||
|
||||
Sist2Api.getMimeTypes(Sist2Query.searchQuery(doBlankSearch)).then(({mimeMap}) => {
|
||||
this.$store.commit("setUiMimeMap", mimeMap);
|
||||
this.uiLoading = false;
|
||||
this.search(true);
|
||||
});
|
||||
|
||||
}).catch(() => {
|
||||
this.showErrorToast();
|
||||
Sist2Api.getMimeTypes(Sist2Query.searchQuery(doBlankSearch)).then(({mimeMap}) => {
|
||||
this.$store.commit("setUiMimeMap", mimeMap);
|
||||
this.uiLoading = false;
|
||||
this.search(true);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user