mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-08 08:12:48 +01:00
Add simple auth (#186)
This commit is contained in:
366
ui/src/App.vue
366
ui/src/App.vue
@@ -1,157 +1,212 @@
|
||||
<template>
|
||||
<v-app class="background">
|
||||
<v-navigation-drawer dark permanent :mini-variant="mini" :expand-on-hover="mini" app color="statusbar">
|
||||
<v-list>
|
||||
<v-list-item class="px-2" :to="{ name: 'Home' }">
|
||||
<v-list-item-avatar rounded="0">
|
||||
<v-img src="/static/flask_white.svg" :width="40"></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="title">
|
||||
Catalyst
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<!--v-list dense nav>
|
||||
<v-list-item class="px-0" dense :to="{ name: 'Profile' }">
|
||||
<v-list-item-avatar>
|
||||
<v-img v-if="$store.state.userdata.image" :src="$store.state.userdata.image"></v-img>
|
||||
<v-icon v-else>mdi-account-circle</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<div v-if="$store.state.user">
|
||||
{{ $store.state.userdata.name }}
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider></v-divider-->
|
||||
|
||||
<v-list nav dense>
|
||||
<v-list-item>
|
||||
<v-list-item-icon>
|
||||
<v-icon class="my-1">mdi-arrow-right-bold</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>
|
||||
<v-text-field
|
||||
placeholder="Goto"
|
||||
outlined
|
||||
dense
|
||||
hide-details
|
||||
v-on:keyup.enter="enter"
|
||||
clearable
|
||||
color="#fff"
|
||||
v-model="goto"></v-text-field>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<AppLink :links="internal"></AppLink>
|
||||
|
||||
<v-list nav dense v-if="$store.state.settings.ticketTypes">
|
||||
<v-list-item
|
||||
v-for="customType in $store.state.settings.ticketTypes"
|
||||
:key="customType.id"
|
||||
link
|
||||
:class="{ 'v-list-item--active': ($route.params.type === customType.id) }"
|
||||
@click="openTicketList(customType.id)">
|
||||
<v-list-item-icon>
|
||||
<v-badge
|
||||
v-if="customType.id in counts && counts[customType.id] > 0"
|
||||
:content="counts[customType.id]"
|
||||
color="red"
|
||||
left
|
||||
offset-x="35"
|
||||
offset-y="8"
|
||||
bottom>
|
||||
<v-icon>{{ customType.icon }}</v-icon>
|
||||
</v-badge>
|
||||
<v-icon v-else>{{ customType.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ customType.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<AppLink :links="settings"></AppLink>
|
||||
|
||||
<template v-slot:append>
|
||||
|
||||
<v-list nav dense>
|
||||
<v-list-item class="version" dense style="min-height: 20px">
|
||||
<div>
|
||||
<v-app v-if="!authenticated" id="app" class="background-dark">
|
||||
<v-container class="login d-flex flex-column justify-center">
|
||||
<v-form ref="form" v-model="valid" @submit.prevent="login">
|
||||
<v-card class="pa-4">
|
||||
<div class="d-flex justify-center">
|
||||
<v-img src="/ui/flask.svg" height="100" width="100" class="flex-grow-0"></v-img>
|
||||
</div>
|
||||
<v-card-title class="text-center justify-center">
|
||||
Catalyst Login
|
||||
</v-card-title>
|
||||
<v-card-text v-if="hassimple" class="text-center">
|
||||
<v-text-field id="username" name="username" label="Name" v-model="username" :rules="[
|
||||
v => !!v || 'Name is required',
|
||||
]"></v-text-field>
|
||||
<v-text-field
|
||||
id="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
:append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
|
||||
:type="show ? 'text' : 'password'"
|
||||
@click:append="show = !show"
|
||||
v-model="password"
|
||||
:rules="[
|
||||
v => !!v || 'Password is required',
|
||||
// v => (v && v.length > 8) || 'Password must be more than 8 characters',
|
||||
]"></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn v-if="hasoidc" text href="/auth/oidclogin">
|
||||
Login with OIDC
|
||||
</v-btn>
|
||||
<v-spacer v-if="hassimple"></v-spacer>
|
||||
<v-btn v-if="hassimple" type="submit" color="primary" elevation="0" :disabled="!valid">
|
||||
Login
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</v-container>
|
||||
</v-app>
|
||||
<v-app v-else class="background">
|
||||
<v-navigation-drawer dark permanent :mini-variant="mini" :expand-on-hover="mini" app color="statusbar">
|
||||
<v-list>
|
||||
<v-list-item class="px-2" :to="{ name: 'Home' }">
|
||||
<v-list-item-avatar rounded="0">
|
||||
<v-img src="/ui/flask_white.svg" :width="40"></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title style="text-align: center; opacity: 0.5;">
|
||||
{{ $store.state.settings.tier }} v{{ $store.state.settings.version }}
|
||||
<v-list-item-title class="title">
|
||||
Catalyst
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list nav dense>
|
||||
|
||||
<v-list-item :to="{ name: 'API' }">
|
||||
<v-list-item>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-share-variant</v-icon>
|
||||
<v-icon class="my-1">mdi-arrow-right-bold</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>API Documentation</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-title>
|
||||
<v-text-field
|
||||
placeholder="Goto"
|
||||
outlined
|
||||
dense
|
||||
hide-details
|
||||
v-on:keyup.enter="enter"
|
||||
clearable
|
||||
color="#fff"
|
||||
v-model="goto"></v-text-field>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-app-bar app dense flat absolute color="transparent">
|
||||
<v-btn icon @click="mini = !mini">
|
||||
<v-icon color="primary">mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
<AppLink :links="internal"></AppLink>
|
||||
|
||||
<v-breadcrumbs :items="crumbs">
|
||||
<template v-slot:item="{ item }">
|
||||
<v-breadcrumbs-item
|
||||
:to="item.to"
|
||||
class="text-subtitle-2 crumb-item"
|
||||
:disabled="item.disabled"
|
||||
exact
|
||||
>
|
||||
{{ item.text }}
|
||||
</v-breadcrumbs-item>
|
||||
<v-list nav dense v-if="$store.state.settings.ticketTypes">
|
||||
<v-list-item
|
||||
v-for="customType in $store.state.settings.ticketTypes"
|
||||
:key="customType.id"
|
||||
link
|
||||
:class="{ 'v-list-item--active': ($route.params.type === customType.id) }"
|
||||
@click="openTicketList(customType.id)">
|
||||
<v-list-item-icon>
|
||||
<v-badge
|
||||
v-if="customType.id in counts && counts[customType.id] > 0"
|
||||
:content="counts[customType.id]"
|
||||
color="red"
|
||||
left
|
||||
offset-x="35"
|
||||
offset-y="8"
|
||||
bottom>
|
||||
<v-icon>{{ customType.icon }}</v-icon>
|
||||
</v-badge>
|
||||
<v-icon v-else>{{ customType.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ customType.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<AppLink :links="settings"></AppLink>
|
||||
|
||||
<template v-slot:append>
|
||||
|
||||
<v-list nav dense>
|
||||
<v-list-item class="version" dense style="min-height: 20px">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title style="text-align: center; opacity: 0.5;">
|
||||
{{ $store.state.settings.tier }} v{{ $store.state.settings.version }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider></v-divider>
|
||||
<v-list nav dense>
|
||||
|
||||
<v-list-item :to="{ name: 'API' }">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-share-variant</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>API Documentation</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
</v-navigation-drawer>
|
||||
<v-app-bar app dense flat absolute color="transparent">
|
||||
<v-btn icon @click="mini = !mini">
|
||||
<v-icon id="toggle_menu" color="primary">mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-breadcrumbs :items="crumbs">
|
||||
<template v-slot:item="{ item }">
|
||||
<v-breadcrumbs-item
|
||||
:to="item.to"
|
||||
class="text-subtitle-2 crumb-item"
|
||||
:disabled="item.disabled"
|
||||
exact
|
||||
>
|
||||
{{ item.text }}
|
||||
</v-breadcrumbs-item>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<v-btn :to="{ name: 'Profile' }" icon>
|
||||
<v-avatar v-if="$store.state.userdata.image" size="32">
|
||||
<v-img :src="$store.state.userdata.image"></v-img>
|
||||
</v-avatar>
|
||||
<v-icon v-else>mdi-account-circle</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
</v-app-bar>
|
||||
<router-view></router-view>
|
||||
<v-snackbar v-model="snackbar" :color="$store.state.alert.type" :timeout="$store.state.alert.type === 'error' ? -1 : 5000" outlined>
|
||||
<b style="display: block">{{ $store.state.alert.name | capitalize }}</b>
|
||||
{{ $store.state.alert.detail }}
|
||||
<template v-slot:action="{ attrs }">
|
||||
<v-btn text v-bind="attrs" @click="snackbar = false">Close</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-app>
|
||||
<v-menu left bottom offset-y>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn
|
||||
icon
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<v-avatar v-if="$store.state.userdata.image" size="32">
|
||||
<v-img :src="$store.state.userdata.image"></v-img>
|
||||
</v-avatar>
|
||||
<v-icon v-else>mdi-account-circle</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-list>
|
||||
<v-list-item :to="{ name: 'Profile' }">
|
||||
<v-list-item-title>Account</v-list-item-title>
|
||||
<v-list-item-icon><v-icon>mdi-account-circle</v-icon></v-list-item-icon>
|
||||
</v-list-item>
|
||||
<v-list-item @click="logout">
|
||||
<v-list-item-title>Logout</v-list-item-title>
|
||||
<v-list-item-icon><v-icon>mdi-logout</v-icon></v-list-item-icon>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
</v-app-bar>
|
||||
<router-view></router-view>
|
||||
<v-snackbar v-model="snackbar" :color="$store.state.alert.type" :timeout="$store.state.alert.type === 'error' ? -1 : 5000" outlined>
|
||||
<b style="display: block">{{ $store.state.alert.name | capitalize }}</b>
|
||||
{{ $store.state.alert.detail }}
|
||||
<template v-slot:action="{ attrs }">
|
||||
<v-btn text v-bind="attrs" @click="snackbar = false">Close</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-app>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import AppLink from "./components/AppLink.vue";
|
||||
import router from "vue-router";
|
||||
import {API} from "@/services/api";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "App",
|
||||
components: {AppLink},
|
||||
data: () => ({
|
||||
show: false,
|
||||
hassimple: false,
|
||||
hasoidc: false,
|
||||
username: "",
|
||||
password: "",
|
||||
valid: true,
|
||||
authenticated: false,
|
||||
settings: [
|
||||
{ icon: "mdi-format-list-bulleted-type", name: "Ticket Types", to: "TicketTypeList", role: "engineer:tickettype:write" },
|
||||
{ icon: "mdi-file-hidden", name: "Templates", to: "TemplateList", role: "analyst:template:read" },
|
||||
@@ -229,12 +284,56 @@ export default Vue.extend({
|
||||
return this.lodash.includes(this.$store.state.user.roles, s);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
login: function () {
|
||||
this.axios.post(
|
||||
"/auth/login",
|
||||
{username: this.username, password: this.password},
|
||||
).then((response) => {
|
||||
console.log(response.data);
|
||||
if (!this.lodash.isObject(response.data)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$store.dispatch("getUser");
|
||||
this.$store.dispatch("getUserData");
|
||||
this.$store.dispatch("getSettings");
|
||||
|
||||
this.authenticated = true;
|
||||
}).catch(() => {
|
||||
this.valid = false;
|
||||
})
|
||||
},
|
||||
logout: function () {
|
||||
this.axios.post("/auth/logout").then(() => {
|
||||
this.authenticated = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch("getUser");
|
||||
this.$store.dispatch("getUserData");
|
||||
this.$store.dispatch("getSettings");
|
||||
this.axios.get("/auth/config").then((response) => {
|
||||
this.hassimple = response.data.simple;
|
||||
this.hasoidc = response.data.oidc;
|
||||
|
||||
API.currentUser().then((response) => {
|
||||
if (!this.lodash.isObject(response.data)) {
|
||||
if (!this.hassimple && this.hasoidc) {
|
||||
window.location.href = "/auth/oidclogin";
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.authenticated = true;
|
||||
|
||||
this.$store.dispatch("getUser");
|
||||
this.$store.dispatch("getUserData");
|
||||
this.$store.dispatch("getSettings");
|
||||
})
|
||||
}).catch(() => {
|
||||
this.hassimple = false;
|
||||
this.hasoidc = false;
|
||||
})
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -244,6 +343,15 @@ export default Vue.extend({
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
|
||||
.background-dark {
|
||||
background-color: #212121 !important;
|
||||
}
|
||||
|
||||
.login {
|
||||
height: 100%;
|
||||
max-width: 400px !important;
|
||||
}
|
||||
|
||||
.v-app-bar.v-toolbar--dense .v-toolbar__content {
|
||||
border-bottom: 1px solid #e0e0e0 !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user