mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-07 15:52:47 +01:00
feat: reset password (#1092)
This commit is contained in:
16
ui/src/components/common/CatalystLogo.vue
Normal file
16
ui/src/components/common/CatalystLogo.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img src="@/assets/flask.svg" alt="Catalyst Logo" :class="cn('dark:hidden', props.class)" />
|
||||||
|
<img
|
||||||
|
src="@/assets/flask_white.svg"
|
||||||
|
alt="Catalyst Logo"
|
||||||
|
:class="cn('hidden dark:flex', props.class)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import CatalystLogo from '@/components/common/CatalystLogo.vue'
|
||||||
import IncidentNav from '@/components/sidebar/IncidentNav.vue'
|
import IncidentNav from '@/components/sidebar/IncidentNav.vue'
|
||||||
import NavList from '@/components/sidebar/NavList.vue'
|
import NavList from '@/components/sidebar/NavList.vue'
|
||||||
import UserDropDown from '@/components/sidebar/UserDropDown.vue'
|
import UserDropDown from '@/components/sidebar/UserDropDown.vue'
|
||||||
@@ -14,16 +15,8 @@ const catalystStore = useCatalystStore()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-[57px] items-center border-b bg-background">
|
<div class="flex h-[57px] items-center border-b bg-background">
|
||||||
<img
|
<CatalystLogo
|
||||||
src="@/assets/flask.svg"
|
class="size-8"
|
||||||
alt="Catalyst"
|
|
||||||
class="h-8 w-8 dark:hidden"
|
|
||||||
:class="{ 'flex-1': catalystStore.sidebarCollapsed, 'mx-3': !catalystStore.sidebarCollapsed }"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src="@/assets/flask_white.svg"
|
|
||||||
alt="Catalyst"
|
|
||||||
class="hidden h-8 w-8 dark:flex"
|
|
||||||
:class="{ 'flex-1': catalystStore.sidebarCollapsed, 'mx-3': !catalystStore.sidebarCollapsed }"
|
:class="{ 'flex-1': catalystStore.sidebarCollapsed, 'mx-3': !catalystStore.sidebarCollapsed }"
|
||||||
/>
|
/>
|
||||||
<h1 class="text-xl font-bold" v-if="!catalystStore.sidebarCollapsed">Catalyst</h1>
|
<h1 class="text-xl font-bold" v-if="!catalystStore.sidebarCollapsed">Catalyst</h1>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||||||
|
|
||||||
import DashboardView from '@/views/DashboardView.vue'
|
import DashboardView from '@/views/DashboardView.vue'
|
||||||
import LoginView from '@/views/LoginView.vue'
|
import LoginView from '@/views/LoginView.vue'
|
||||||
|
import PasswordResetView from '@/views/PasswordResetView.vue'
|
||||||
import ReactionView from '@/views/ReactionView.vue'
|
import ReactionView from '@/views/ReactionView.vue'
|
||||||
import TicketView from '@/views/TicketView.vue'
|
import TicketView from '@/views/TicketView.vue'
|
||||||
|
|
||||||
@@ -12,25 +13,30 @@ const router = createRouter({
|
|||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/dashboard'
|
redirect: '/dashboard'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/reactions/:id?',
|
|
||||||
name: 'reactions',
|
|
||||||
component: ReactionView
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
component: DashboardView
|
component: DashboardView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tickets/:type/:id?',
|
||||||
|
name: 'tickets',
|
||||||
|
component: TicketView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/reactions/:id?',
|
||||||
|
name: 'reactions',
|
||||||
|
component: ReactionView
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: LoginView
|
component: LoginView
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/tickets/:type/:id?',
|
path: '/password-reset',
|
||||||
name: 'tickets',
|
name: 'password-reset',
|
||||||
component: TicketView
|
component: PasswordResetView
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import CatalystLogo from '@/components/common/CatalystLogo.vue'
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button, buttonVariants } from '@/components/ui/button'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
|
||||||
@@ -8,9 +9,11 @@ import { useQuery } from '@tanstack/vue-query'
|
|||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
import { pb } from '@/lib/pocketbase'
|
import { pb } from '@/lib/pocketbase'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
const mail = ref('')
|
const mail = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
|
const errorTitle = ref('')
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
@@ -20,6 +23,7 @@ const login = () => {
|
|||||||
window.location.href = '/ui/'
|
window.location.href = '/ui/'
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
errorTitle.value = 'Login failed'
|
||||||
errorMessage.value = error.message
|
errorMessage.value = error.message
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -37,7 +41,8 @@ watch(
|
|||||||
mail.value = 'user@catalyst-soar.com'
|
mail.value = 'user@catalyst-soar.com'
|
||||||
password.value = '1234567890'
|
password.value = '1234567890'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -45,12 +50,18 @@ watch(
|
|||||||
<div class="flex h-full w-full flex-1 items-center justify-center">
|
<div class="flex h-full w-full flex-1 items-center justify-center">
|
||||||
<Card class="m-auto w-96">
|
<Card class="m-auto w-96">
|
||||||
<CardHeader class="flex flex-row justify-between">
|
<CardHeader class="flex flex-row justify-between">
|
||||||
<CardTitle>Catalyst</CardTitle>
|
<CardTitle class="flex flex-row">
|
||||||
|
<CatalystLogo class="size-12" />
|
||||||
|
<div>
|
||||||
|
<h1 class="text-lg font-bold">Catalyst</h1>
|
||||||
|
<div class="text-muted-foreground">Login</div>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="flex flex-col gap-4">
|
<CardContent class="flex flex-col gap-4">
|
||||||
<Alert v-if="errorMessage" variant="destructive" class="border-4 p-4">
|
<Alert v-if="errorTitle || errorMessage" variant="destructive" class="border-4 p-4">
|
||||||
<AlertTitle>Error</AlertTitle>
|
<AlertTitle v-if="errorTitle">{{ errorTitle }}</AlertTitle>
|
||||||
<AlertDescription>{{ errorMessage }}</AlertDescription>
|
<AlertDescription v-if="errorMessage">{{ errorMessage }}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Input
|
<Input
|
||||||
v-model="mail"
|
v-model="mail"
|
||||||
@@ -66,7 +77,14 @@ watch(
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
@keydown.enter="login"
|
@keydown.enter="login"
|
||||||
/>
|
/>
|
||||||
<Button variant="outline" class="w-full" @click="login"> Login</Button>
|
<Button variant="outline" class="w-full" @click="login">Login</Button>
|
||||||
|
<RouterLink
|
||||||
|
:to="{ name: 'password-reset' }"
|
||||||
|
:class="
|
||||||
|
cn(buttonVariants({ variant: 'link', size: 'default' }), 'w-full text-foreground')
|
||||||
|
"
|
||||||
|
>Reset Password
|
||||||
|
</RouterLink>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
79
ui/src/views/PasswordResetView.vue
Normal file
79
ui/src/views/PasswordResetView.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CatalystLogo from '@/components/common/CatalystLogo.vue'
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
|
import { Button, buttonVariants } from '@/components/ui/button'
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { pb } from '@/lib/pocketbase'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface AlertData {
|
||||||
|
variant: 'default' | 'destructive'
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const mail = ref('')
|
||||||
|
const alert = ref<AlertData | null>(null)
|
||||||
|
|
||||||
|
const resetPassword = () => {
|
||||||
|
pb.collection('users')
|
||||||
|
.requestPasswordReset(mail.value)
|
||||||
|
.then(() => {
|
||||||
|
alert.value = {
|
||||||
|
variant: 'default',
|
||||||
|
title: 'Password reset',
|
||||||
|
message: 'Password reset email sent'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
alert.value = {
|
||||||
|
variant: 'destructive',
|
||||||
|
title: 'Password reset failed',
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-full w-full flex-1 items-center justify-center">
|
||||||
|
<Card class="m-auto w-96">
|
||||||
|
<CardHeader class="flex flex-row justify-between">
|
||||||
|
<CardTitle class="flex flex-row">
|
||||||
|
<CatalystLogo class="size-12" />
|
||||||
|
<div>
|
||||||
|
<h1 class="text-lg font-bold">Catalyst</h1>
|
||||||
|
<div class="text-muted-foreground">Password Reset</div>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent class="flex flex-col gap-4">
|
||||||
|
<Alert v-if="alert" :variant="alert.variant" class="border-4 p-4">
|
||||||
|
<AlertTitle>{{ alert.title }}</AlertTitle>
|
||||||
|
<AlertDescription>{{ alert.message }}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
<div v-else class="flex flex-col gap-4">
|
||||||
|
<Input
|
||||||
|
v-model="mail"
|
||||||
|
type="text"
|
||||||
|
placeholder="Email"
|
||||||
|
class="w-full"
|
||||||
|
@keydown.enter="resetPassword"
|
||||||
|
/>
|
||||||
|
<Button variant="outline" class="w-full" @click="resetPassword">Reset Password</Button>
|
||||||
|
</div>
|
||||||
|
<RouterLink
|
||||||
|
:to="{ name: 'login' }"
|
||||||
|
:class="
|
||||||
|
cn(buttonVariants({ variant: 'link', size: 'default' }), 'w-full text-foreground')
|
||||||
|
"
|
||||||
|
>Back to Login
|
||||||
|
</RouterLink>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user