PUSH COMMITS

This commit is contained in:
Abhinav Raut
2024-08-13 03:32:31 +05:30
parent 06db29f7f3
commit ce30055223
42 changed files with 548 additions and 554 deletions

View File

@@ -1,6 +1,8 @@
package main package main
import ( import (
"strconv"
"github.com/abhinavxd/artemis/internal/envelope" "github.com/abhinavxd/artemis/internal/envelope"
"github.com/abhinavxd/artemis/internal/stringutil" "github.com/abhinavxd/artemis/internal/stringutil"
@@ -8,35 +10,52 @@ import (
"github.com/zerodha/fastglue" "github.com/zerodha/fastglue"
) )
// handleOIDCLogin initializes an OIDC request and redirects to the OIDC provider for login. // handleOIDCLogin redirects to the OIDC provider for login.
func handleOIDCLogin(r *fastglue.Request) error { func handleOIDCLogin(r *fastglue.Request) error {
var ( var (
app = r.Context.(*App) app = r.Context.(*App)
providerID, err = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
) )
if err != nil {
app.lo.Error("error parsing provider id", "error", err)
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error parsing provider id.", nil, envelope.GeneralError)
}
// TODO: Figure csrf thing out
state, err := stringutil.RandomAlNumString(30) state, err := stringutil.RandomAlNumString(30)
if err != nil { if err != nil {
app.lo.Error("error generating random string", "error", err) app.lo.Error("error generating random string", "error", err)
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Something went wrong, Please try again.", nil, envelope.GeneralError) return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Something went wrong, Please try again.", nil, envelope.GeneralError)
} }
authURL := app.auth.LoginURL(state)
authURL, err := app.auth.LoginURL(providerID, state)
if err != nil {
return sendErrorEnvelope(r, err)
}
return r.Redirect(authURL, fasthttp.StatusFound, nil, "") return r.Redirect(authURL, fasthttp.StatusFound, nil, "")
} }
// handleOIDCCallback receives the redirect callback from the OIDC provider and completes the handshake. // handleOIDCCallback receives the redirect callback from the OIDC provider and completes the handshake.
func handleOIDCCallback(r *fastglue.Request) error { func handleOIDCCallback(r *fastglue.Request) error {
var ( var (
app = r.Context.(*App) app = r.Context.(*App)
code = string(r.RequestCtx.QueryArgs().Peek("code")) code = string(r.RequestCtx.QueryArgs().Peek("code"))
state = string(r.RequestCtx.QueryArgs().Peek("state")) state = string(r.RequestCtx.QueryArgs().Peek("state"))
providerID, err = strconv.Atoi(string(r.RequestCtx.QueryArgs().Peek("id")))
) )
if err != nil {
app.lo.Error("error parsing provider id", "error", err)
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error parsing provider id.", nil, envelope.GeneralError)
}
_, claims, err := app.auth.ExchangeOIDCToken(r.RequestCtx, code) _, claims, err := app.auth.ExchangeOIDCToken(r.RequestCtx, providerID, code)
if err != nil { if err != nil {
app.lo.Error("error exchanging oidc token", "error", err) app.lo.Error("error exchanging oidc token", "error", err)
return err return err
} }
// Get user by e-mail received from OIDC. // Get user by e-mail received.
user, err := app.user.GetByEmail(claims.Email) user, err := app.user.GetByEmail(claims.Email)
if err != nil { if err != nil {
return err return err

View File

@@ -14,7 +14,7 @@ import (
func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) { func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
g.POST("/api/login", handleLogin) g.POST("/api/login", handleLogin)
g.GET("/api/logout", handleLogout) g.GET("/api/logout", handleLogout)
g.GET("/api/oidc/login", handleOIDCLogin) g.GET("/api/oidc/{id}/login", handleOIDCLogin)
g.GET("/api/oidc/finish", handleOIDCCallback) g.GET("/api/oidc/finish", handleOIDCCallback)
// Health check. // Health check.

View File

@@ -269,7 +269,7 @@ func initMedia(db *sqlx.DB) *media.Manager {
case "localfs": case "localfs":
store, err = localfs.New(localfs.Opts{ store, err = localfs.New(localfs.Opts{
UploadURI: "/uploads", UploadURI: "/uploads",
UploadPath: filepath.Clean(ko.String("app.localfs.upload_path")), UploadPath: filepath.Clean(ko.String("upload.localfs.upload_path")),
RootURL: ko.String("app.root_url"), RootURL: ko.String("app.root_url"),
}) })
if err != nil { if err != nil {
@@ -411,16 +411,31 @@ func registerInboxes(mgr *inbox.Manager, store inbox.MessageStore) {
} }
} }
func initAuth(rd *redis.Client) *auth.Auth { func initAuth(o *oidc.Manager, rd *redis.Client) *auth.Auth {
lo := initLogger("auth") var lo = initLogger("auth")
oidc, err := o.GetAll()
if err != nil {
log.Fatalf("error initializing auth: %v", err)
}
var providers = make([]auth.Provider, 0, len(oidc))
for _, o := range oidc {
if o.Disabled {
continue
}
providers = append(providers, auth.Provider{
ID: o.ID,
Provider: o.Provider,
ProviderURL: o.ProviderURL,
RedirectURL: o.RedirectURI,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
})
}
a, err := auth.New(auth.Config{ a, err := auth.New(auth.Config{
OIDC: auth.OIDCConfig{ Providers: providers,
Enabled: true,
ProviderURL: "https://accounts.google.com",
RedirectURL: "http://localhost:5173/auth/oidc/finish",
ClientID: "a",
ClientSecret: "a",
},
}, rd, lo) }, rd, lo)
if err != nil { if err != nil {
log.Fatalf("error initializing auth: %v", err) log.Fatalf("error initializing auth: %v", err)

View File

@@ -83,6 +83,8 @@ func main() {
i18n = initI18n(fs) i18n = initI18n(fs)
lo = initLogger("artemis") lo = initLogger("artemis")
rdb = initRedis() rdb = initRedis()
oidc = initOIDC(db)
auth = initAuth(oidc, rdb)
template = initTemplate(db) template = initTemplate(db)
media = initMedia(db) media = initMedia(db)
contact = initContact(db) contact = initContact(db)
@@ -119,7 +121,7 @@ func main() {
var app = &App{ var app = &App{
lo: lo, lo: lo,
rdb: rdb, rdb: rdb,
auth: initAuth(rdb), auth: auth,
fs: fs, fs: fs,
i18n: i18n, i18n: i18n,
media: media, media: media,
@@ -131,7 +133,7 @@ func main() {
tmpl: template, tmpl: template,
conversation: conversation, conversation: conversation,
automation: automation, automation: automation,
oidc: initOIDC(db), oidc: oidc,
role: initRole(db), role: initRole(db),
constant: initConstants(), constant: initConstants(),
tag: initTags(db), tag: initTags(db),

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"strconv" "strconv"
"github.com/abhinavxd/artemis/internal/envelope" "github.com/abhinavxd/artemis/internal/envelope"
@@ -9,32 +10,38 @@ import (
"github.com/zerodha/fastglue" "github.com/zerodha/fastglue"
) )
// handleGetAllOIDC returns all oidc records const (
redirectURI = "/api/oidc/finish?id=%d"
)
// handleGetAllOIDC returns all OIDC records
func handleGetAllOIDC(r *fastglue.Request) error { func handleGetAllOIDC(r *fastglue.Request) error {
var ( app := r.Context.(*App)
app = r.Context.(*App)
) out, err := app.oidc.GetAll()
o, err := app.oidc.GetAll()
if err != nil { if err != nil {
return sendErrorEnvelope(r, err) return sendErrorEnvelope(r, err)
} }
return r.SendEnvelope(o) return r.SendEnvelope(out)
} }
// handleGetOIDC returns an OIDC record by id. // handleGetOIDC returns an OIDC record by id.
func handleGetOIDC(r *fastglue.Request) error { func handleGetOIDC(r *fastglue.Request) error {
var ( app := r.Context.(*App)
app = r.Context.(*App)
)
id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string)) id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string))
if err != nil || id == 0 { if err != nil || id <= 0 {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, return r.SendErrorEnvelope(fasthttp.StatusBadRequest,
"Invalid oidc `id`", nil, envelope.InputError) "Invalid OIDC `id`", nil, envelope.InputError)
} }
o, err := app.oidc.Get(id) o, err := app.oidc.Get(id)
if err != nil { if err != nil {
return sendErrorEnvelope(r, err) return sendErrorEnvelope(r, err)
} }
o.RedirectURI = fmt.Sprintf("%s%s", app.constant.AppBaseURL, fmt.Sprintf(redirectURI, o.ID))
return r.SendEnvelope(o) return r.SendEnvelope(o)
} }

View File

@@ -54,7 +54,7 @@
"textarea": "^0.3.0", "textarea": "^0.3.0",
"tiptap-extension-resize-image": "^1.1.5", "tiptap-extension-resize-image": "^1.1.5",
"vee-validate": "^4.13.2", "vee-validate": "^4.13.2",
"vue": "^3.4.15", "vue": "^3.4.37",
"vue-draggable-resizable": "^3.0.0", "vue-draggable-resizable": "^3.0.0",
"vue-i18n": "9", "vue-i18n": "9",
"vue-letter": "^0.2.0", "vue-letter": "^0.2.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -7,7 +7,7 @@
<ResizablePanel id="resize-panel-1" collapsible :default-size="10" :collapsed-size="1" :min-size="7" <ResizablePanel id="resize-panel-1" collapsible :default-size="10" :collapsed-size="1" :min-size="7"
:max-size="20" :class="cn(isCollapsed && 'min-w-[50px] transition-all duration-200 ease-in-out')" :max-size="20" :class="cn(isCollapsed && 'min-w-[50px] transition-all duration-200 ease-in-out')"
@expand="toggleNav(false)" @collapse="toggleNav(true)"> @expand="toggleNav(false)" @collapse="toggleNav(true)">
<NavBar :is-collapsed="isCollapsed" :links="navLinks" /> <NavBar :is-collapsed="isCollapsed" :links="navLinks" :bottom-links="bottomLinks" />
</ResizablePanel> </ResizablePanel>
<ResizableHandle id="resize-handle-1" /> <ResizableHandle id="resize-handle-1" />
<ResizablePanel id="resize-panel-2"> <ResizablePanel id="resize-panel-2">
@@ -70,6 +70,16 @@ const allNavLinks = ref([
permission: 'admin:get', permission: 'admin:get',
}, },
]); ]);
const bottomLinks = ref(
[
{
to: '/logout',
icon: 'lucide:log-out',
title: 'Logout'
}
]
)
const userStore = useUserStore(); const userStore = useUserStore();
const router = useRouter(); const router = useRouter();

View File

@@ -8,7 +8,8 @@ import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/comp
defineProps({ defineProps({
isCollapsed: Boolean, isCollapsed: Boolean,
links: Array links: Array,
bottomLinks: Array
}) })
const route = useRoute() const route = useRoute()
@@ -25,7 +26,7 @@ const getButtonVariant = (to) => {
</script> </script>
<template> <template>
<div :data-collapsed="isCollapsed" class="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"> <div :data-collapsed="isCollapsed" class="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2 h-full">
<nav class="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2"> <nav class="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2">
<template v-for="(link, index) of links"> <template v-for="(link, index) of links">
<!-- Collapsed --> <!-- Collapsed -->
@@ -74,5 +75,30 @@ const getButtonVariant = (to) => {
</router-link> </router-link>
</template> </template>
</nav> </nav>
<!-- Bottom Links -->
<div class="mt-auto px-2">
<template v-for="(bottomLink, index) in bottomLinks" :key="`bottom-${index}`">
<TooltipProvider :delay-duration="10">
<Tooltip>
<TooltipTrigger as-child>
<router-link :to="bottomLink.to" :class="cn(
buttonVariants({ variant: getButtonVariant(bottomLink.to), size: isCollapsed ? 'icon' : 'sm' }),
bottomLink.variant === getButtonVariant(bottomLink.to) &&
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
'justify-start'
)">
<Icon :icon="bottomLink.icon" class="mr-2 size-5" v-if="!isCollapsed" />
<span v-if="!isCollapsed">{{ bottomLink.title }}</span>
<Icon :icon="bottomLink.icon" class="size-5 mx-auto" v-else />
</router-link>
</TooltipTrigger>
<TooltipContent side="right" class="flex items-center gap-4">
{{ bottomLink.title }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</template>
</div>
</div> </div>
</template> </template>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="flex flex-col space-y-5"> <div class="flex flex-col space-y-5">
<div> <div class="space-y-1">
<span class="sub-title">Public avatar</span> <span class="sub-title">Public avatar</span>
<p class="text-muted-foreground text-xs">Change your avatar here.</p> <p class="text-muted-foreground text-xs">Change your avatar here.</p>
</div> </div>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="border rounded-lg"> <div class="box rounded-lg">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id"> <TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">

View File

@@ -12,7 +12,7 @@
</div> </div>
</RadioGroup> </RadioGroup>
</div> </div>
<div class="box border p-5 space-y-5"> <div class="box border p-5 space-y-5 rounded-lg">
<div class="space-y-5"> <div class="space-y-5">
<div v-for="(rule, index) in ruleGroup.rules" :key="rule" class="space-y-5"> <div v-for="(rule, index) in ruleGroup.rules" :key="rule" class="space-y-5">
<div v-if="index > 0"> <div v-if="index > 0">

View File

@@ -1,14 +1,14 @@
<template> <template>
<div class="flex flex-col box border p-5 space-y-2"> <div class="flex flex-col box px-5 py-6 rounded-lg justify-center">
<div class="flex justify-between"> <div class="flex justify-between space-y-3">
<div> <div>
<span class="sub-title flex space-x-3 items-center"> <span class="sub-title space-x-3 flex justify-center items-center">
<div class="text-base"> <div class="text-base">
{{ rule.name }} {{ rule.name }}
</div> </div>
<div> <div class="mb-1">
<Badge v-if="!rule.disabled" class="text-[10px] py-0 px-1">Enabled</Badge> <Badge v-if="!rule.disabled">Enabled</Badge>
<Badge v-else class="text-[10px] py-0 px-1" variant="secondary">Disabled</Badge> <Badge v-else variant="secondary">Disabled</Badge>
</div> </div>
</span> </span>
</div> </div>
@@ -36,7 +36,7 @@
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
<p class="text-muted-foreground text-sm"> <p class="text-sm-muted">
{{ rule.description }} {{ rule.description }}
</p> </p>
</div> </div>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="space-y-5"> <div class="space-y-5">
<div> <div>
<p class="text-muted-foreground text-sm">Rules that run when a new conversation is created</p> <p class="text-sm-muted">Rules that run when a new conversation is created</p>
</div> </div>
<div v-if="showRuleList" class="space-y-5"> <div v-if="showRuleList" class="space-y-5">
<RuleList v-for="rule in rules" :key="rule.name" :rule="rule" @delete-rule="deleteRule" @toggle-rule="toggleRule"/> <RuleList v-for="rule in rules" :key="rule.name" :rule="rule" @delete-rule="deleteRule" @toggle-rule="toggleRule"/>

View File

@@ -1,10 +1,10 @@
<template> <template>
<div <div
class="max-w-sm overflow-hidden box rounded-lg px-8 py-5 transition-shadow duration-170 cursor-pointer hover:bg-muted" class="box rounded-lg px-8 py-4 transition-shadow duration-170 cursor-pointer hover:bg-muted"
@click="handleClick"> @click="handleClick">
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<component :is="icon" size="17" class="mr-2" /> <component :is="icon" size="16" class="mr-2" />
<p class="text-xl">{{ title }}</p> <p class="text-lg">{{ title }}</p>
</div> </div>
<p class="text-xs-muted">{{ subTitle }}</p> <p class="text-xs-muted">{{ subTitle }}</p>
</div> </div>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="flex flex-col space-y-1"> <div class="flex flex-col space-y-2">
<span class="text-2xl">{{ title }}</span> <span class="text-2xl">{{ title }}</span>
<p class="text-xs-muted">{{ description }}</p> <p class="text-xs-muted">{{ description }}</p>
</div> </div>

View File

@@ -2,23 +2,25 @@
<div class="mb-5"> <div class="mb-5">
<CustomBreadcrumb :links="breadcrumbLinks" /> <CustomBreadcrumb :links="breadcrumbLinks" />
</div> </div>
<OIDCForm :initial-values="oidc" :submitForm="submitForm" /> <OIDCForm :initial-values="oidc" :submitForm="submitForm" :isNewForm=isNewForm />
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref, computed } from 'vue'
import api from '@/api' import api from '@/api'
import OIDCForm from './OIDCForm.vue' import OIDCForm from './OIDCForm.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { CustomBreadcrumb } from '@/components/ui/breadcrumb' import { CustomBreadcrumb } from '@/components/ui/breadcrumb'
const oidc = ref({}) const oidc = ref({
"provider": "Google"
})
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
id: { id: {
type: String, type: String,
required: true required: false
} }
}) })
@@ -35,6 +37,10 @@ const breadCrumLabel = () => {
return props.id ? "Edit" : 'New'; return props.id ? "Edit" : 'New';
} }
const isNewForm = computed(() => {
return props.id ? false : true;
})
const breadcrumbLinks = [ const breadcrumbLinks = [
{ path: '/admin/oidc', label: 'OIDC' }, { path: '/admin/oidc', label: 'OIDC' },
{ path: '#', label: breadCrumLabel() } { path: '#', label: breadCrumLabel() }

View File

@@ -1,10 +1,48 @@
<template> <template>
<form @submit="onSubmit" class="w-2/3 space-y-6"> <form @submit="onSubmit" class="w-2/3 space-y-6">
<FormField v-slot="{ componentField }" name="provider">
<FormItem>
<FormLabel>Provider</FormLabel>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger>
<SelectValue placeholder="Select a provider" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="Google">
Google
</SelectItem>
<SelectItem value="Github">
Github
</SelectItem>
<SelectItem value="Custom">
Custom
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="name">
<FormItem v-auto-animate>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="text" placeholder="Google" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="provider_url"> <FormField v-slot="{ componentField }" name="provider_url">
<FormItem v-auto-animate> <FormItem v-auto-animate>
<FormLabel>Provider URL</FormLabel> <FormLabel>Provider URL</FormLabel>
<FormControl> <FormControl>
<Input type="url" placeholder="Provider URL" v-bind="componentField" /> <Input type="text" placeholder="Provider URL" v-bind="componentField" />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -30,12 +68,30 @@
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField }" name="redirect_uri"> <FormField v-slot="{ componentField }" name="redirect_uri" v-if="!isNewForm">
<FormItem v-auto-animate> <FormItem v-auto-animate>
<FormLabel>Redirect URI</FormLabel> <FormLabel>Redirect URI</FormLabel>
<FormControl> <FormControl>
<Input type="url" placeholder="Redirect URI" v-bind="componentField" /> <Input type="text" placeholder="Redirect URI" v-bind="componentField" readonly />
<span class="absolute end-0 inset-y-0 flex items-center justify-center px-2 cursor-pointer">
<Copy size="16" />
</span>
</FormControl> </FormControl>
<FormDescription>Set this URI for callback.</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField name="disabled" v-slot="{ value, handleChange }" v-if="!isNewForm">
<FormItem>
<FormControl>
<div class="flex items-center space-x-2">
<Checkbox :checked="value" @update:checked="handleChange" />
<Label>Disable</Label>
</div>
</FormControl>
<FormDescription></FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
@@ -50,15 +106,27 @@ import { Button } from '@/components/ui/button'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import { oidcLoginFormSchema } from './formSchema.js' import { oidcLoginFormSchema } from './formSchema.js'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import { vAutoAnimate } from '@formkit/auto-animate/vue' import { vAutoAnimate } from '@formkit/auto-animate/vue'
import { import {
FormControl, FormControl,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage FormMessage,
FormDescription,
} from '@/components/ui/form' } from '@/components/ui/form'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Copy } from 'lucide-vue-next';
const props = defineProps({ const props = defineProps({
initialValues: { initialValues: {
@@ -74,6 +142,9 @@ const props = defineProps({
required: false, required: false,
default: () => 'Save' default: () => 'Save'
}, },
isNewForm: {
type: Boolean
}
}) })
const form = useForm({ const form = useForm({
@@ -90,6 +161,6 @@ watch(
(newValues) => { (newValues) => {
form.setValues(newValues) form.setValues(newValues)
}, },
{ deep: true } { deep: true, immediate: true }
) )
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="flex justify-between mb-5"> <div class="flex justify-between mb-5">
<PageHeader title="OIDC" description="Manage OpenID Connect configurations" /> <PageHeader title="OIDC" description="Manage OpenID Connect configurations" />
<div> <div>
<Button size="sm" @click="navigateToAddOIDC">New OIDC</Button> <Button size="sm" @click="navigateToAddOIDC">New OIDC</Button>
@@ -17,16 +17,23 @@ import DataTable from '@/components/admin/DataTable.vue'
import { columns } from '@/components/admin/oidc/dataTableColumns.js' import { columns } from '@/components/admin/oidc/dataTableColumns.js'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useEmitter } from '@/composables/useEmitter'
import PageHeader from '../common/PageHeader.vue' import PageHeader from '../common/PageHeader.vue'
import api from '@/api' import api from '@/api'
const oidc = ref([]) const oidc = ref([])
const router = useRouter() const router = useRouter()
const emit = useEmitter()
onMounted(async () => { onMounted(() => {
fetchAll()
emit.on('refresh-inbox-list', fetchAll)
})
const fetchAll = async () => {
const resp = await api.getAllOIDC() const resp = await api.getAllOIDC()
oidc.value = resp.data.data oidc.value = resp.data.data
}) }
const navigateToAddOIDC = () => { const navigateToAddOIDC = () => {
router.push("/admin/oidc/new") router.push("/admin/oidc/new")

View File

@@ -3,14 +3,36 @@ import dropdown from './dataTableDropdown.vue'
import { format } from 'date-fns' import { format } from 'date-fns'
export const columns = [ export const columns = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: function () {
return h('div', { class: 'text-center' }, 'Name')
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('name'))
}
},
{
accessorKey: 'provider',
header: function () { header: function () {
return h('div', { class: 'text-center' }, 'Provider') return h('div', { class: 'text-center' }, 'Provider')
}, },
cell: function ({ row }) { cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('name')) return h('div', { class: 'text-center font-medium' }, row.getValue('provider'))
}
},
{
accessorKey: 'disabled',
header: () => h('div', { class: 'text-center' }, 'Enabled'),
cell: ({ row }) => {
const disabled = row.getValue('disabled')
return h('div', { class: 'text-center' }, [
h('input', {
type: 'checkbox',
checked: !disabled,
disabled: true
})
])
} }
}, },
{ {

View File

@@ -9,8 +9,10 @@ import {
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import api from '@/api' import api from '@/api'
import { useEmitter } from '@/composables/useEmitter'
const router = useRouter() const router = useRouter()
const emit = useEmitter()
const props = defineProps({ const props = defineProps({
role: { role: {
@@ -28,6 +30,7 @@ function edit (id) {
async function deleteOIDC (id) { async function deleteOIDC (id) {
await api.deleteOIDC(id) await api.deleteOIDC(id)
emit.emit('refresh-inbox-list')
} }
</script> </script>

View File

@@ -1,6 +1,14 @@
import * as z from 'zod' import * as z from 'zod'
export const oidcLoginFormSchema = z.object({ export const oidcLoginFormSchema = z.object({
disabled: z
.boolean().optional(),
name: z
.string({
required_error: 'Name is required.'
}),
provider: z
.string().optional(),
provider_url: z provider_url: z
.string({ .string({
required_error: 'Provider URL is required.' required_error: 'Provider URL is required.'
@@ -16,11 +24,5 @@ export const oidcLoginFormSchema = z.object({
.string({ .string({
required_error: 'Client Secret is required.' required_error: 'Client Secret is required.'
}), }),
redirect_uri: z redirect_uri: z.string().readonly().optional(),
.string({
required_error: 'Redirect URI is required.'
})
.url({
message: 'Redirect URI must be a valid URL.'
})
}) })

View File

@@ -38,7 +38,6 @@
</FormField> </FormField>
</div> </div>
</div> </div>
<Button type="submit" size="sm">{{ submitLabel }}</Button> <Button type="submit" size="sm">{{ submitLabel }}</Button>
</form> </form>
</template> </template>
@@ -118,7 +117,6 @@ const form = useForm({
const onSubmit = form.handleSubmit((values) => { const onSubmit = form.handleSubmit((values) => {
values.permissions = selectedPermissions.value values.permissions = selectedPermissions.value
console.log("submitting ", values)
props.submitForm(values) props.submitForm(values)
}) })

View File

@@ -15,7 +15,7 @@ import api from '@/api'
const { toast } = useToast() const { toast } = useToast()
const breadcrumbLinks = [ const breadcrumbLinks = [
{ path: '/admin/teams', label: 'Teams' }, { path: '/admin/teams', label: 'Teams' },
{ path: '/admin/teams/users', label: 'Users'}, { path: '/admin/teams/users', label: 'Users' },
{ path: '#', label: 'Add user' } { path: '#', label: 'Add user' }
] ]

View File

@@ -2,7 +2,7 @@
<div class="mb-5"> <div class="mb-5">
<CustomBreadcrumb :links="breadcrumbLinks" /> <CustomBreadcrumb :links="breadcrumbLinks" />
</div> </div>
<UserForm :initial-values="user" :submitForm="submitForm" /> <UserForm :initialValues="user" :submitForm="submitForm" />
</template> </template>
<script setup> <script setup>

View File

@@ -1,5 +1,5 @@
<template> <template>
<form @submit.prevent="onSubmit" class="w-2/3 space-y-6"> <form @submit.prevent="onSubmit" class="space-y-6">
<FormField v-slot="{ field }" name="first_name"> <FormField v-slot="{ field }" name="first_name">
<FormItem v-auto-animate> <FormItem v-auto-animate>
<FormLabel>First name</FormLabel> <FormLabel>First name</FormLabel>
@@ -29,23 +29,23 @@
</FormItem> </FormItem>
</FormField> </FormField>
<FormField name="teams" v-slot="{ field }"> <FormField name="teams" v-slot="{ componentField }">
<FormItem> <FormItem>
<FormLabel>Select teams</FormLabel> <FormLabel>Select teams</FormLabel>
<FormControl> <FormControl>
<SelectTag :initialValue="field.value" v-model="selectedTeamNames" :items="teamNames" <SelectTag v-model="componentField.modelValue" :items="teamNames" placeHolder="Select teams"></SelectTag>
placeHolder="Select teams"></SelectTag>
</FormControl> </FormControl>
<FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
<FormField name="roles" v-slot="{ field }"> <FormField name="roles" v-slot="{ componentField }">
<FormItem> <FormItem>
<FormLabel>Select roles</FormLabel> <FormLabel>Select roles</FormLabel>
<FormControl> <FormControl>
<SelectTag :initialValue="field.value" v-model="selectedRoleNames" :items="roleNames" <SelectTag v-model="componentField.modelValue" :items="roleNames" placeHolder="Select roles"></SelectTag>
placeHolder="Select roles"></SelectTag>
</FormControl> </FormControl>
<FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
@@ -81,9 +81,6 @@ import {
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import api from '@/api' import api from '@/api'
const selectedRoleNames = ref([])
const selectedTeamNames = ref([])
const teams = ref([]) const teams = ref([])
const roles = ref([]) const roles = ref([])
@@ -133,7 +130,6 @@ const form = useForm({
}) })
const onSubmit = form.handleSubmit((values) => { const onSubmit = form.handleSubmit((values) => {
console.log('submitting ', values)
props.submitForm(values) props.submitForm(values)
}) })
@@ -142,15 +138,7 @@ watch(
() => props.initialValues, () => props.initialValues,
(newValues) => { (newValues) => {
form.setValues(newValues) form.setValues(newValues)
if (newValues) {
if (newValues.roles)
selectedRoleNames.value = newValues.roles
if (newValues.teams)
selectedTeamNames.value = newValues.teams
}
}, },
{ immediate: true } { immediate: true, deep: true }
) )
</script> </script>

View File

@@ -114,8 +114,7 @@
<FormItem v-auto-animate> <FormItem v-auto-animate>
<FormLabel>S3 backend URL</FormLabel> <FormLabel>S3 backend URL</FormLabel>
<FormControl> <FormControl>
<Input type="url" placeholder="https://ap-south-1.s3.amazonaws.com" v-bind="componentField" <Input type="url" placeholder="https://ap-south-1.s3.amazonaws.com" v-bind="componentField"/>
defaultValue="https://ap-south-1.s3.amazonaws.com" />
</FormControl> </FormControl>
<FormDescription>Only change if using a custom S3 compatible backend like Minio.</FormDescription> <FormDescription>Only change if using a custom S3 compatible backend like Minio.</FormDescription>
<FormMessage /> <FormMessage />

View File

@@ -1,249 +0,0 @@
<template>
<div v-if="formProvider === 's3'">
<form @submit="onS3FormSubmit" class="w-2/3 space-y-6">
<FormField v-slot="{ componentField }" name="provider">
<FormItem v-auto-animate>
<FormLabel>Provider</FormLabel>
<FormControl>
<Select v-bind="componentField" v-model="componentField.modelValue"
@update:modelValue="handleProviderUpdate">
<SelectTrigger>
<SelectValue placeholder="Select a provider" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="s3">
S3
</SelectItem>
<SelectItem value="localfs">
Local filesystem
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="region">
<FormItem v-auto-animate>
<FormLabel>Region</FormLabel>
<FormControl>
<Input type="text" placeholder="ap-south-1" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="access_key">
<FormItem v-auto-animate>
<FormLabel>AWS access key</FormLabel>
<FormControl>
<Input type="text" placeholder="AWS access key" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="access_secret">
<FormItem v-auto-animate>
<FormLabel>AWS access secret</FormLabel>
<FormControl>
<Input type="password" placeholder="AWS access secret" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="bucket_type">
<FormItem v-auto-animate>
<FormLabel>Bucket type</FormLabel>
<FormControl>
<Select v-bind="componentField" v-model="componentField.modelValue">
<SelectTrigger>
<SelectValue placeholder="Select bucket type" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="public">
Public
</SelectItem>
<SelectItem value="private">
Private
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="bucket">
<FormItem v-auto-animate>
<FormLabel>Bucket</FormLabel>
<FormControl>
<Input type="text" placeholder="Bucket" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="bucket_path">
<FormItem v-auto-animate>
<FormLabel>Bucket path</FormLabel>
<FormControl>
<Input type="text" placeholder="Bucket path" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="upload_expiry">
<FormItem v-auto-animate>
<FormLabel>Upload expiry</FormLabel>
<FormControl>
<Input type="text" placeholder="24h" v-bind="componentField" />
</FormControl>
<FormDescription>Only applicable for private buckets.</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="url">
<FormItem v-auto-animate>
<FormLabel>S3 backend URL</FormLabel>
<FormControl>
<Input type="url" placeholder="https://ap-south-1.s3.amazonaws.com" v-bind="componentField"
defaultValue="https://ap-south-1.s3.amazonaws.com" />
</FormControl>
<FormDescription>Only change if using a custom S3 compatible backend like Minio.</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit" size="sm"> {{ submitLabel }} </Button>
</form>
</div>
<div v-else>
<form @submit="onLocalFsSubmit" class="w-2/3 space-y-6">
<FormField v-slot="{ componentField }" name="provider">
<FormItem v-auto-animate>
<FormLabel>Provider</FormLabel>
<FormControl>
<Select v-bind="componentField" v-model="componentField.modelValue"
@update:modelValue="handleProviderUpdate">
<SelectTrigger>
<SelectValue placeholder="Select a provider" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="s3">
S3
</SelectItem>
<SelectItem value="localfs">
Local filesystem
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="upload_path">
<FormItem v-auto-animate>
<FormLabel>Upload path</FormLabel>
<FormControl>
<Input type="text" placeholder="/home/ubuntu/uploads" v-bind="componentField" />
</FormControl>
<FormDescription> Path to the directory where files will be uploaded. </FormDescription>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit" size="sm"> {{ submitLabel }} </Button>
</form>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { Button } from '@/components/ui/button'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { s3FormSchema, localFsFormSchema } from './formSchema.js'
import { vAutoAnimate } from '@formkit/auto-animate/vue'
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
FormDescription
} from '@/components/ui/form'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Input } from '@/components/ui/input'
const props = defineProps({
initialValues: {
type: Object,
required: false
},
submitForm: {
type: Function,
required: true
},
submitLabel: {
type: String,
required: false,
default: () => 'Submit'
},
})
const s3Form = useForm({
validationSchema: toTypedSchema(s3FormSchema),
})
const localFsForm = useForm({
validationSchema: toTypedSchema(localFsFormSchema),
})
const formProvider = ref(props.initialValues?.provider || 's3')
const onS3FormSubmit = s3Form.handleSubmit((values) => {
console.log("v ", values)
props.submitForm(values)
})
const onLocalFsSubmit = localFsForm.handleSubmit((values) => {
props.submitForm(values)
})
const handleProviderUpdate = (value) => {
formProvider.value = value
}
// Watch for changes in initialValues and update the form.
watch(
() => props.initialValues,
(newValues) => {
formProvider.value = newValues.provider
if (newValues.provider === 's3') {
s3Form.setValues(newValues)
} else {
localFsForm.setValues(newValues)
}
},
{ deep: true, immediate: true }
)
</script>

View File

@@ -55,7 +55,6 @@
</div> </div>
<Error :errorMessage="conversationStore.conversations.errorMessage"></Error>
<EmptyList v-if="emptyConversations"></EmptyList> <EmptyList v-if="emptyConversations"></EmptyList>
<div class="h-screen overflow-y-scroll pb-[180px] flex flex-col"> <div class="h-screen overflow-y-scroll pb-[180px] flex flex-col">

View File

@@ -1,7 +1,6 @@
<template> <template>
<BarChart :data="data" index="status" :categories="['Low', 'Medium', 'High']" :show-grid-line="true" :y-formatter="(tick) => { <BarChart :data="data" index="status" :categories="['Low', 'Medium', 'High']" :show-grid-line="true"
return tick :margin="{ top: 0, bottom: 0, left: 0, right: 0 }" />
}" :margin="{ top: 0, bottom: 0, left: 0, right: 0 }" />
</template> </template>
<script setup> <script setup>

View File

@@ -39,7 +39,6 @@ const emitter = useEmitter()
const scrollToBottom = () => { const scrollToBottom = () => {
setTimeout(() => { setTimeout(() => {
console.log("Scrolling bottom")
const thread = threadEl.value const thread = threadEl.value
if (thread) { if (thread) {
thread.scrollTop = thread.scrollHeight thread.scrollTop = thread.scrollHeight

View File

@@ -8,7 +8,7 @@
</TagsInputItem> </TagsInputItem>
</div> </div>
<ComboboxRoot v-model="filteredItems" v-model:open="isOpen" @onUpdate:searchTerm="searchTerm" class="w-full"> <ComboboxRoot v-model:open="isOpen" class="w-full">
<ComboboxAnchor as-child> <ComboboxAnchor as-child>
<ComboboxInput :placeholder="placeHolder" as-child> <ComboboxInput :placeholder="placeHolder" as-child>
<TagsInputInput class="w-full px-3" :class="selectedItems.length > 0 ? 'mt-2' : ''" <TagsInputInput class="w-full px-3" :class="selectedItems.length > 0 ? 'mt-2' : ''"
@@ -57,7 +57,7 @@ const props = defineProps({
} }
}); });
const selectedItems = defineModel(); const selectedItems = defineModel({ default: [] });
const isOpen = ref(false); const isOpen = ref(false);
const searchTerm = ref(''); const searchTerm = ref('');
const dropdownRef = ref(null); const dropdownRef = ref(null);

View File

@@ -226,6 +226,11 @@ export const useConversationStore = defineStore('conversation', () => {
} }
} catch (error) { } catch (error) {
conversations.errorMessage = handleHTTPError(error).message; conversations.errorMessage = handleHTTPError(error).message;
toast({
title: 'Could not fetch conversations.',
variant: 'destructive',
description: handleHTTPError(error).message,
});
} finally { } finally {
conversations.loading = false; conversations.loading = false;
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="page-content w-11/12"> <div class="page-content w-10/12">
<div class="flex flex-col space-y-6"> <div class="flex flex-col space-y-6">
<div> <div>
<span class="font-medium text-2xl space-y-1" v-if="userStore.getFullName"> <span class="font-medium text-2xl space-y-1" v-if="userStore.getFullName">
@@ -79,7 +79,7 @@
<script setup> <script setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { format, subWeeks, subMonths, subYears, formatISO } from 'date-fns' import { format } from 'date-fns'
import api from '@/api'; import api from '@/api';
import { useToast } from '@/components/ui/toast/use-toast' import { useToast } from '@/components/ui/toast/use-toast'
@@ -94,13 +94,14 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select' } from '@/components/ui/select'
import { useLocalStorage } from '@vueuse/core'
const { toast } = useToast() const { toast } = useToast()
const userStore = useUserStore() const userStore = useUserStore()
const cardCounts = ref({}) const cardCounts = ref({})
const chartData = ref({}) const chartData = ref({})
const filter = ref("me") const filter = useLocalStorage('dashboard_filter', 'me');
const barChartFilter = ref("last_week") const barChartFilter = ref("last_week")
const lineChartFilter = ref("last_week") const lineChartFilter = ref("last_week")
const agentCountCardsLabels = { const agentCountCardsLabels = {
@@ -110,12 +111,6 @@ const agentCountCardsLabels = {
awaiting_response_count: "Awaiting Response", awaiting_response_count: "Awaiting Response",
}; };
const chartFilters = {
last_week: getLastWeekRange(),
last_month: getLastMonthRange(),
last_year: getLastYearRange(),
};
``
onMounted(() => { onMounted(() => {
getCardStats() getCardStats()
getDashboardCharts() getDashboardCharts()
@@ -132,7 +127,6 @@ const onDashboardFilterChange = (v) => {
} }
const onLineChartFilterChange = (v) => { const onLineChartFilterChange = (v) => {
console.log("chart filter -> ", chartFilters)
lineChartFilter.value = v lineChartFilter.value = v
} }
@@ -140,33 +134,6 @@ const onBarChartFilterChange = (v) => {
barChartFilter.value = v barChartFilter.value = v
} }
function getLastWeekRange () {
const today = new Date();
const lastWeekStart = subWeeks(today, 1);
return {
start: formatISO(lastWeekStart, { representation: 'date' }),
end: formatISO(today, { representation: 'date' }),
};
}
function getLastMonthRange () {
const today = new Date();
const lastMonthStart = subMonths(today, 1);
return {
start: formatISO(lastMonthStart, { representation: 'date' }),
end: formatISO(today, { representation: 'date' }),
};
}
function getLastYearRange () {
const today = new Date();
const lastYearStart = subYears(today, 1);
return {
start: formatISO(lastYearStart, { representation: 'date' }),
end: formatISO(today, { representation: 'date' }),
};
}
const getCardStats = () => { const getCardStats = () => {
let apiCall; let apiCall;
switch (filter.value) { switch (filter.value) {

View File

@@ -9,10 +9,10 @@
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent class="grid gap-4"> <CardContent class="grid gap-4">
<div class="grid grid-cols-1 gap-6" v-for="oidcProvider in oidcProviders" <div v-for="oidcProvider in enabledOIDCProviders" :key="oidcProvider.id"
:key="oidcProvider.id"> class="grid grid-cols-1 gap-6">
<Button variant="outline" @click.prevent="redirectToOIDCLogin"> <Button variant="outline" @click.prevent="redirectToOIDC(oidcProvider)">
<img :src="oidcProvider.logo_url" width="15" class="mr-2"/> <img :src="oidcProvider.logo_url" width="15" class="mr-2" />
{{ oidcProvider.name }} {{ oidcProvider.name }}
</Button> </Button>
</div> </div>
@@ -52,7 +52,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { handleHTTPError } from '@/utils/http' import { handleHTTPError } from '@/utils/http'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
@@ -82,9 +82,9 @@ const loginForm = ref({
const userStore = useUserStore() const userStore = useUserStore()
const oidcProviders = ref([]) const oidcProviders = ref([])
const redirectToOIDCLogin = () => { const redirectToOIDC = (provider) => {
window.location.href = "/auth/oidc/login" window.location.href = `/api/oidc/${provider.id}/login`
} };
const loginAction = () => { const loginAction = () => {
errorMessage.value = "" errorMessage.value = ""
@@ -114,4 +114,9 @@ onMounted(async () => {
const resp = await api.getAllOIDC() const resp = await api.getAllOIDC()
oidcProviders.value = resp.data.data oidcProviders.value = resp.data.data
}) })
const enabledOIDCProviders = computed(() => {
return oidcProviders.value.filter(provider => !provider.disabled);
});
</script> </script>

View File

@@ -635,6 +635,21 @@
dependencies: dependencies:
"@floating-ui/utils" "^0.2.0" "@floating-ui/utils" "^0.2.0"
"@floating-ui/core@^1.6.0":
version "1.6.7"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12"
integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==
dependencies:
"@floating-ui/utils" "^0.2.7"
"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.6.7":
version "1.6.10"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f"
integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==
dependencies:
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.7"
"@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.6.5": "@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.6.5":
version "1.6.5" version "1.6.5"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9"
@@ -648,6 +663,11 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5"
integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==
"@floating-ui/utils@^0.2.7":
version "0.2.7"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e"
integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
"@floating-ui/vue@^1.0.6": "@floating-ui/vue@^1.0.6":
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/@floating-ui/vue/-/vue-1.0.6.tgz#31860a12f1135d19554c232d99c5bab631c5c576" resolved "https://registry.yarnpkg.com/@floating-ui/vue/-/vue-1.0.6.tgz#31860a12f1135d19554c232d99c5bab631c5c576"
@@ -657,6 +677,15 @@
"@floating-ui/utils" "^0.2.1" "@floating-ui/utils" "^0.2.1"
vue-demi ">=0.13.0" vue-demi ">=0.13.0"
"@floating-ui/vue@^1.1.0":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@floating-ui/vue/-/vue-1.1.4.tgz#1dd6905a58baaa9a84c44c2cf28e281f715957a1"
integrity sha512-ammH7T3vyCx7pmm9OF19Wc42zrGnUw0QvLoidgypWsCLJMtGXEwY7paYIHO+K+oLC3mbWpzIHzeTVienYenlNg==
dependencies:
"@floating-ui/dom" "^1.0.0"
"@floating-ui/utils" "^0.2.7"
vue-demi ">=0.13.0"
"@formkit/auto-animate@^0.8.2": "@formkit/auto-animate@^0.8.2":
version "0.8.2" version "0.8.2"
resolved "https://registry.yarnpkg.com/@formkit/auto-animate/-/auto-animate-0.8.2.tgz#24a56e816159fd5585405dcee6bb992f81c27585" resolved "https://registry.yarnpkg.com/@formkit/auto-animate/-/auto-animate-0.8.2.tgz#24a56e816159fd5585405dcee6bb992f81c27585"
@@ -1241,6 +1270,11 @@
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.5.0.tgz#108208d0f1d75271300bc5560cf9a85a1fa01e89" resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.5.0.tgz#108208d0f1d75271300bc5560cf9a85a1fa01e89"
integrity sha512-KnPRCkQTyqhanNC0K63GBG3wA8I+D1fQuVnAvcBF8f13akOKeQp1gSbu6f77zCxhEk727iV5oQnbHLYzHrECLg== integrity sha512-KnPRCkQTyqhanNC0K63GBG3wA8I+D1fQuVnAvcBF8f13akOKeQp1gSbu6f77zCxhEk727iV5oQnbHLYzHrECLg==
"@tanstack/virtual-core@3.8.6":
version "3.8.6"
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.8.6.tgz#ccecf84099a9a0e9732b4f17bf021b82f215d15e"
integrity sha512-UJeU4SBrx3hqULNzJ3oC0kgJ5miIAg+FwomxMTlQNxob6ppTInifANHd9ukETvzdzxr6zt3CjQ0rttQpVjbt6Q==
"@tanstack/vue-table@^8.19.2": "@tanstack/vue-table@^8.19.2":
version "8.19.2" version "8.19.2"
resolved "https://registry.yarnpkg.com/@tanstack/vue-table/-/vue-table-8.19.2.tgz#909f63a225788757f62a7b7fa4568da6349b9a0f" resolved "https://registry.yarnpkg.com/@tanstack/vue-table/-/vue-table-8.19.2.tgz#909f63a225788757f62a7b7fa4568da6349b9a0f"
@@ -1255,6 +1289,13 @@
dependencies: dependencies:
"@tanstack/virtual-core" "3.5.0" "@tanstack/virtual-core" "3.5.0"
"@tanstack/vue-virtual@^3.8.1":
version "3.8.6"
resolved "https://registry.yarnpkg.com/@tanstack/vue-virtual/-/vue-virtual-3.8.6.tgz#c37a11ee8a03f04eaeaca7523eaa3963098e0728"
integrity sha512-nWwmlFuxChPM6bWEwKOyBBYVrQmvSKSArXhbvX2IyVTpuif9UZiBEvIXnftpCEGRvAGSe7lE1coXHk8g2qmwtQ==
dependencies:
"@tanstack/virtual-core" "3.8.6"
"@tiptap/core@^2.4.0": "@tiptap/core@^2.4.0":
version "2.4.0" version "2.4.0"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.4.0.tgz#6f8eee8beb5b89363582366b201ccc4798ac98a9" resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.4.0.tgz#6f8eee8beb5b89363582366b201ccc4798ac98a9"
@@ -1909,17 +1950,6 @@
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37" resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37"
integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ== integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==
"@vue/compiler-core@3.4.27":
version "3.4.27"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.27.tgz#e69060f4b61429fe57976aa5872cfa21389e4d91"
integrity sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==
dependencies:
"@babel/parser" "^7.24.4"
"@vue/shared" "3.4.27"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-core@3.4.31", "@vue/compiler-core@^3.0.0": "@vue/compiler-core@3.4.31", "@vue/compiler-core@^3.0.0":
version "3.4.31" version "3.4.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.31.tgz#b51a76f1b30e9b5eba0553264dff0f171aedb7c6" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.31.tgz#b51a76f1b30e9b5eba0553264dff0f171aedb7c6"
@@ -1931,13 +1961,16 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
source-map-js "^1.2.0" source-map-js "^1.2.0"
"@vue/compiler-dom@3.4.27": "@vue/compiler-core@3.4.37":
version "3.4.27" version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz#d51d35f40d00ce235d7afc6ad8b09dfd92b1cc1c" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.37.tgz#55db3900e09424c65c39111a05a3c6e698f371e3"
integrity sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw== integrity sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==
dependencies: dependencies:
"@vue/compiler-core" "3.4.27" "@babel/parser" "^7.24.7"
"@vue/shared" "3.4.27" "@vue/shared" "3.4.37"
entities "^5.0.0"
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-dom@3.4.31", "@vue/compiler-dom@^3.4.27": "@vue/compiler-dom@3.4.31", "@vue/compiler-dom@^3.4.27":
version "3.4.31" version "3.4.31"
@@ -1947,19 +1980,27 @@
"@vue/compiler-core" "3.4.31" "@vue/compiler-core" "3.4.31"
"@vue/shared" "3.4.31" "@vue/shared" "3.4.31"
"@vue/compiler-sfc@3.4.27": "@vue/compiler-dom@3.4.37":
version "3.4.27" version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz#399cac1b75c6737bf5440dc9cf3c385bb2959701" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.37.tgz#a1fcf79e287cb828545082ff1afa8630480a3044"
integrity sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA== integrity sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==
dependencies: dependencies:
"@babel/parser" "^7.24.4" "@vue/compiler-core" "3.4.37"
"@vue/compiler-core" "3.4.27" "@vue/shared" "3.4.37"
"@vue/compiler-dom" "3.4.27"
"@vue/compiler-ssr" "3.4.27" "@vue/compiler-sfc@3.4.37":
"@vue/shared" "3.4.27" version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.37.tgz#8afaf1a86cb849422c765d4369ba1e85fffe0234"
integrity sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==
dependencies:
"@babel/parser" "^7.24.7"
"@vue/compiler-core" "3.4.37"
"@vue/compiler-dom" "3.4.37"
"@vue/compiler-ssr" "3.4.37"
"@vue/shared" "3.4.37"
estree-walker "^2.0.2" estree-walker "^2.0.2"
magic-string "^0.30.10" magic-string "^0.30.10"
postcss "^8.4.38" postcss "^8.4.40"
source-map-js "^1.2.0" source-map-js "^1.2.0"
"@vue/compiler-sfc@^3.4", "@vue/compiler-sfc@^3.4.27": "@vue/compiler-sfc@^3.4", "@vue/compiler-sfc@^3.4.27":
@@ -1977,14 +2018,6 @@
postcss "^8.4.38" postcss "^8.4.38"
source-map-js "^1.2.0" source-map-js "^1.2.0"
"@vue/compiler-ssr@3.4.27":
version "3.4.27"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz#2a8ecfef1cf448b09be633901a9c020360472e3d"
integrity sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==
dependencies:
"@vue/compiler-dom" "3.4.27"
"@vue/shared" "3.4.27"
"@vue/compiler-ssr@3.4.31": "@vue/compiler-ssr@3.4.31":
version "3.4.31" version "3.4.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz#f62ffecdf15bacb883d0099780cf9a1e3654bfc4" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz#f62ffecdf15bacb883d0099780cf9a1e3654bfc4"
@@ -1993,6 +2026,14 @@
"@vue/compiler-dom" "3.4.31" "@vue/compiler-dom" "3.4.31"
"@vue/shared" "3.4.31" "@vue/shared" "3.4.31"
"@vue/compiler-ssr@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.37.tgz#b75e1c76c3184f86fa9f0ba4d61d13bc6afcbf8a"
integrity sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==
dependencies:
"@vue/compiler-dom" "3.4.37"
"@vue/shared" "3.4.37"
"@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1": "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.5.1":
version "6.6.1" version "6.6.1"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
@@ -2018,7 +2059,22 @@
dependencies: dependencies:
"@vue/shared" "3.4.27" "@vue/shared" "3.4.27"
"@vue/runtime-core@3.4.27", "@vue/runtime-core@^3.4.15": "@vue/reactivity@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.37.tgz#5a199563eaab51ed9f94ddf0a82f9179bcc01676"
integrity sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==
dependencies:
"@vue/shared" "3.4.37"
"@vue/runtime-core@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.37.tgz#3fe734a666db7842bea4185a13f7697a2102b719"
integrity sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==
dependencies:
"@vue/reactivity" "3.4.37"
"@vue/shared" "3.4.37"
"@vue/runtime-core@^3.4.15":
version "3.4.27" version "3.4.27"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.27.tgz#1b6e1d71e4604ba7442dd25ed22e4a1fc6adbbda" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.27.tgz#1b6e1d71e4604ba7442dd25ed22e4a1fc6adbbda"
integrity sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA== integrity sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==
@@ -2026,22 +2082,23 @@
"@vue/reactivity" "3.4.27" "@vue/reactivity" "3.4.27"
"@vue/shared" "3.4.27" "@vue/shared" "3.4.27"
"@vue/runtime-dom@3.4.27": "@vue/runtime-dom@3.4.37":
version "3.4.27" version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz#fe8d1ce9bbe8921d5dd0ad5c10df0e04ef7a5ee7" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.37.tgz#219f84577027103de6ddc71351d8237c7c16adac"
integrity sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q== integrity sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==
dependencies: dependencies:
"@vue/runtime-core" "3.4.27" "@vue/reactivity" "3.4.37"
"@vue/shared" "3.4.27" "@vue/runtime-core" "3.4.37"
"@vue/shared" "3.4.37"
csstype "^3.1.3" csstype "^3.1.3"
"@vue/server-renderer@3.4.27": "@vue/server-renderer@3.4.37":
version "3.4.27" version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.27.tgz#3306176f37e648ba665f97dda3ce705687be63d2" resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.37.tgz#d341425bb5395a3f6ed70572ea5c3edefab92f28"
integrity sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA== integrity sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==
dependencies: dependencies:
"@vue/compiler-ssr" "3.4.27" "@vue/compiler-ssr" "3.4.37"
"@vue/shared" "3.4.27" "@vue/shared" "3.4.37"
"@vue/shared@3.4.27": "@vue/shared@3.4.27":
version "3.4.27" version "3.4.27"
@@ -2053,6 +2110,11 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.31.tgz#af9981f57def2c3f080c14bf219314fc0dc808a0" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.31.tgz#af9981f57def2c3f080c14bf219314fc0dc808a0"
integrity sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA== integrity sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==
"@vue/shared@3.4.37":
version "3.4.37"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.37.tgz#4f4c08a2e73da512a77b47165cf59ffbc1b5ade8"
integrity sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==
"@vuedx/template-ast-types@0.7.1": "@vuedx/template-ast-types@0.7.1":
version "0.7.1" version "0.7.1"
resolved "https://registry.yarnpkg.com/@vuedx/template-ast-types/-/template-ast-types-0.7.1.tgz#ccc75786a4fe1d1910f6c8d93d150d44ee1dabdc" resolved "https://registry.yarnpkg.com/@vuedx/template-ast-types/-/template-ast-types-0.7.1.tgz#ccc75786a4fe1d1910f6c8d93d150d44ee1dabdc"
@@ -2060,7 +2122,7 @@
dependencies: dependencies:
"@vue/compiler-core" "^3.0.0" "@vue/compiler-core" "^3.0.0"
"@vueuse/core@^10.11.1": "@vueuse/core@^10.11.0", "@vueuse/core@^10.11.1":
version "10.11.1" version "10.11.1"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6" resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww== integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==
@@ -2090,7 +2152,7 @@
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.9.0.tgz#769a1a9db65daac15cf98084cbf7819ed3758620" resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.9.0.tgz#769a1a9db65daac15cf98084cbf7819ed3758620"
integrity sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA== integrity sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==
"@vueuse/shared@10.11.1": "@vueuse/shared@10.11.1", "@vueuse/shared@^10.11.0":
version "10.11.1" version "10.11.1"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938" resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938"
integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA== integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==
@@ -2325,7 +2387,7 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.2.3: aria-hidden@^1.2.3, aria-hidden@^1.2.4:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522" resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522"
integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A== integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==
@@ -3828,6 +3890,11 @@ entities@^4.4.0, entities@^4.5.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
entities@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-5.0.0.tgz#b2ab51fe40d995817979ec79dd621154c3c0f62b"
integrity sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==
env-paths@^2.2.0: env-paths@^2.2.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@@ -6178,7 +6245,7 @@ nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
nanoid@^5.0.6: nanoid@^5.0.6, nanoid@^5.0.7:
version "5.0.7" version "5.0.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6"
integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ== integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==
@@ -7184,6 +7251,15 @@ postcss@^8.4.23, postcss@^8.4.38:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.2.0" source-map-js "^1.2.0"
postcss@^8.4.40:
version "8.4.41"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681"
integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
dependencies:
nanoid "^3.3.7"
picocolors "^1.0.1"
source-map-js "^1.2.0"
potpack@^1.0.2: potpack@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
@@ -7615,22 +7691,22 @@ radix-vue@^1.7.3:
fast-deep-equal "^3.1.3" fast-deep-equal "^3.1.3"
nanoid "^5.0.6" nanoid "^5.0.6"
radix-vue@^1.8.0: radix-vue@latest:
version "1.8.1" version "1.9.3"
resolved "https://registry.yarnpkg.com/radix-vue/-/radix-vue-1.8.1.tgz#5e0cb08a1540b7d3ad5c21e39f5ebe5bd735f826" resolved "https://registry.yarnpkg.com/radix-vue/-/radix-vue-1.9.3.tgz#4fa171a570a366730114bbd065531b2ce6037359"
integrity sha512-DFyUt2vc/89tpSHiJvv7Qb/Qs8zVxq2g7q4kuuDV46fmDmSC3mnV3hdSAYruU7k/KvoDpS3sd99kLGRtuG63Rw== integrity sha512-9pewcgzghM+B+FO1h9mMsZa/csVH6hElpN1sqmG4/qoeieiDG0i4nhMjS7p2UOz11EEdVm7eLandHSPyx7hYhg==
dependencies: dependencies:
"@floating-ui/dom" "^1.6.5" "@floating-ui/dom" "^1.6.7"
"@floating-ui/vue" "^1.0.6" "@floating-ui/vue" "^1.1.0"
"@internationalized/date" "^3.5.4" "@internationalized/date" "^3.5.4"
"@internationalized/number" "^3.5.3" "@internationalized/number" "^3.5.3"
"@tanstack/vue-virtual" "^3.5.0" "@tanstack/vue-virtual" "^3.8.1"
"@vueuse/core" "^10.5.0" "@vueuse/core" "^10.11.0"
"@vueuse/shared" "^10.5.0" "@vueuse/shared" "^10.11.0"
aria-hidden "^1.2.3" aria-hidden "^1.2.4"
defu "^6.1.4" defu "^6.1.4"
fast-deep-equal "^3.1.3" fast-deep-equal "^3.1.3"
nanoid "^5.0.6" nanoid "^5.0.7"
rc9@^2.1.2: rc9@^2.1.2:
version "2.1.2" version "2.1.2"
@@ -9226,16 +9302,16 @@ vue-router@^4.2.5:
dependencies: dependencies:
"@vue/devtools-api" "^6.5.1" "@vue/devtools-api" "^6.5.1"
vue@^3.4.15: vue@^3.4.37:
version "3.4.27" version "3.4.37"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.27.tgz#40b7d929d3e53f427f7f5945386234d2854cc2a1" resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.37.tgz#64ce0eeb8de16a29fb74e504777ee8c0c1cf229e"
integrity sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA== integrity sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==
dependencies: dependencies:
"@vue/compiler-dom" "3.4.27" "@vue/compiler-dom" "3.4.37"
"@vue/compiler-sfc" "3.4.27" "@vue/compiler-sfc" "3.4.37"
"@vue/runtime-dom" "3.4.27" "@vue/runtime-dom" "3.4.37"
"@vue/server-renderer" "3.4.27" "@vue/server-renderer" "3.4.37"
"@vue/shared" "3.4.27" "@vue/shared" "3.4.37"
w3c-keyname@^2.2.0: w3c-keyname@^2.2.0:
version "2.2.8" version "2.2.8"

View File

@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/abhinavxd/artemis/internal/envelope"
"github.com/abhinavxd/artemis/internal/user/models" "github.com/abhinavxd/artemis/internal/user/models"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@@ -26,42 +27,51 @@ type OIDCclaim struct {
Picture string `json:"picture"` Picture string `json:"picture"`
} }
type OIDCConfig struct { type Provider struct {
Enabled bool `json:"enabled"` ID int
ProviderURL string `json:"provider_url"` Provider string
RedirectURL string `json:"redirect_url"` ProviderURL string
ClientID string `json:"client_id"` RedirectURL string
ClientSecret string `json:"client_secret"` ClientID string
ClientSecret string
} }
type Config struct { type Config struct {
OIDC OIDCConfig Providers []Provider
} }
type Auth struct { type Auth struct {
cfg Config cfg Config
oauthCfg oauth2.Config oauthCfgs map[int]oauth2.Config
verifier *oidc.IDTokenVerifier verifiers map[int]*oidc.IDTokenVerifier
sess *simplesessions.Manager sess *simplesessions.Manager
lo *logf.Logger logger *logf.Logger
} }
// New inits a OIDC configuration // New initializes an OIDC configuration for multiple providers.
func New(cfg Config, rd *redis.Client, logger *logf.Logger) (*Auth, error) { func New(cfg Config, rd *redis.Client, logger *logf.Logger) (*Auth, error) {
provider, err := oidc.NewProvider(context.Background(), cfg.OIDC.ProviderURL) oauthCfgs := make(map[int]oauth2.Config)
if err != nil { verifiers := make(map[int]*oidc.IDTokenVerifier)
return nil, err
}
oauthCfg := oauth2.Config{ for _, provider := range cfg.Providers {
ClientID: cfg.OIDC.ClientID, oidcProv, err := oidc.NewProvider(context.Background(), provider.ProviderURL)
ClientSecret: cfg.OIDC.ClientSecret, if err != nil {
Endpoint: provider.Endpoint(), return nil, fmt.Errorf("initializing `%s` oidc login: %v", provider.Provider, err)
RedirectURL: cfg.OIDC.RedirectURL, }
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
verifier := provider.Verifier(&oidc.Config{ClientID: cfg.OIDC.ClientID}) oauthCfg := oauth2.Config{
ClientID: provider.ClientID,
ClientSecret: provider.ClientSecret,
Endpoint: oidcProv.Endpoint(),
RedirectURL: provider.RedirectURL,
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
verifier := oidcProv.Verifier(&oidc.Config{ClientID: provider.ClientID})
oauthCfgs[provider.ID] = oauthCfg
verifiers[provider.ID] = verifier
}
sess := simplesessions.New(simplesessions.Options{ sess := simplesessions.New(simplesessions.Options{
EnableAutoCreate: false, EnableAutoCreate: false,
@@ -72,27 +82,42 @@ func New(cfg Config, rd *redis.Client, logger *logf.Logger) (*Auth, error) {
MaxAge: time.Hour * 12, MaxAge: time.Hour * 12,
}, },
}) })
st := sessredisstore.New(context.TODO(), rd) st := sessredisstore.New(context.TODO(), rd)
sess.UseStore(st) sess.UseStore(st)
sess.SetCookieHooks(simpleSessGetCookieCB, simpleSessSetCookieCB) sess.SetCookieHooks(simpleSessGetCookieCB, simpleSessSetCookieCB)
return &Auth{ return &Auth{
cfg: cfg, cfg: cfg,
oauthCfg: oauthCfg, oauthCfgs: oauthCfgs,
verifier: verifier, verifiers: verifiers,
lo: logger, sess: sess,
sess: sess, logger: logger,
}, nil }, nil
} }
// LoginURL // LoginURL generates a login URL for a specific provider using its ID.
func (a *Auth) LoginURL(state string) string { func (a *Auth) LoginURL(providerID int, state string) (string, error) {
return a.oauthCfg.AuthCodeURL(state) oauthCfg, ok := a.oauthCfgs[providerID]
if !ok {
return "", envelope.NewError(envelope.InputError, "Provider not found", nil)
}
return oauthCfg.AuthCodeURL(state), nil
} }
// ExchangeOIDCToken takes an OIDC authorization code, validates it, and returns an OIDC token for subsequent auth. // ExchangeOIDCToken takes an OIDC authorization code, validates it, and returns an OIDC token for subsequent auth.
func (a *Auth) ExchangeOIDCToken(ctx context.Context, code string) (string, OIDCclaim, error) { func (a *Auth) ExchangeOIDCToken(ctx context.Context, providerID int, code string) (string, OIDCclaim, error) {
tk, err := a.oauthCfg.Exchange(ctx, code) oauthCfg, ok := a.oauthCfgs[providerID]
if !ok {
return "", OIDCclaim{}, fmt.Errorf("invalid provider ID: %d", providerID)
}
verifier, ok := a.verifiers[providerID]
if !ok {
return "", OIDCclaim{}, fmt.Errorf("invalid provider ID: %d", providerID)
}
tk, err := oauthCfg.Exchange(ctx, code)
if err != nil { if err != nil {
return "", OIDCclaim{}, fmt.Errorf("error exchanging token: %v", err) return "", OIDCclaim{}, fmt.Errorf("error exchanging token: %v", err)
} }
@@ -104,7 +129,7 @@ func (a *Auth) ExchangeOIDCToken(ctx context.Context, code string) (string, OIDC
} }
// Parse and verify ID Token payload. // Parse and verify ID Token payload.
idTk, err := a.verifier.Verify(ctx, rawIDTk) idTk, err := verifier.Verify(ctx, rawIDTk)
if err != nil { if err != nil {
return "", OIDCclaim{}, fmt.Errorf("error verifying ID token: %v", err) return "", OIDCclaim{}, fmt.Errorf("error verifying ID token: %v", err)
} }
@@ -117,10 +142,10 @@ func (a *Auth) ExchangeOIDCToken(ctx context.Context, code string) (string, OIDC
} }
// SaveSession creates and sets a session (post successful login/auth). // SaveSession creates and sets a session (post successful login/auth).
func (o *Auth) SaveSession(user models.User, r *fastglue.Request) error { func (a *Auth) SaveSession(user models.User, r *fastglue.Request) error {
sess, err := o.sess.NewSession(r, r) sess, err := a.sess.NewSession(r, r)
if err != nil { if err != nil {
o.lo.Error("error creating login session", "error", err) a.logger.Error("error creating login session", "error", err)
return err return err
} }
@@ -130,15 +155,15 @@ func (o *Auth) SaveSession(user models.User, r *fastglue.Request) error {
"first_name": user.FirstName, "first_name": user.FirstName,
"last_name": user.LastName, "last_name": user.LastName,
}); err != nil { }); err != nil {
o.lo.Error("error setting login session", "error", err) a.logger.Error("error setting login session", "error", err)
return err return err
} }
return nil return nil
} }
// ValidateSession validates session and returns the user. // ValidateSession validates session and returns the user.
func (o *Auth) ValidateSession(r *fastglue.Request) (models.User, error) { func (a *Auth) ValidateSession(r *fastglue.Request) (models.User, error) {
sess, err := o.sess.Acquire(r.RequestCtx, r, r) sess, err := a.sess.Acquire(r.RequestCtx, r, r)
if err != nil { if err != nil {
return models.User{}, err return models.User{}, err
} }
@@ -158,7 +183,7 @@ func (o *Auth) ValidateSession(r *fastglue.Request) (models.User, error) {
// Logged in? // Logged in?
if userID <= 0 { if userID <= 0 {
o.lo.Error("error fetching session", "error", err) a.logger.Error("error fetching session", "error", err)
return models.User{}, err return models.User{}, err
} }
@@ -171,14 +196,14 @@ func (o *Auth) ValidateSession(r *fastglue.Request) (models.User, error) {
} }
// DestroySession destroys session // DestroySession destroys session
func (o *Auth) DestroySession(r *fastglue.Request) error { func (a *Auth) DestroySession(r *fastglue.Request) error {
sess, err := o.sess.Acquire(r.RequestCtx, r, r) sess, err := a.sess.Acquire(r.RequestCtx, r, r)
if err != nil { if err != nil {
o.lo.Error("error acquiring session", "error", err) a.logger.Error("error acquiring session", "error", err)
return err return err
} }
if err := sess.Destroy(); err != nil { if err := sess.Destroy(); err != nil {
o.lo.Error("error clearing session", "error", err) a.logger.Error("error clearing session", "error", err)
return err return err
} }
return nil return nil

View File

@@ -34,6 +34,8 @@ func (c *Client) Put(filename string, cType string, src io.ReadSeeker) (string,
// Get the directory path // Get the directory path
dir := getDir(c.opts.UploadPath) dir := getDir(c.opts.UploadPath)
fmt.Println("dir ", dir)
fmt.Println("-- ", c.opts.UploadPath)
o, err := os.OpenFile(filepath.Join(dir, filename), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664) o, err := os.OpenFile(filepath.Join(dir, filename), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664)
if err != nil { if err != nil {
return "", err return "", err

View File

@@ -1,7 +1,6 @@
package models package models
import ( import (
"strings"
"time" "time"
) )
@@ -10,35 +9,28 @@ type OIDC struct {
ID int `db:"id" json:"id"` ID int `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"` CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"-" json:"name"` Name string `db:"name" json:"name"`
ProviderLogoURL string `db:"-" json:"logo_url"` Disabled bool `db:"disabled" json:"disabled"`
ProviderURL string `db:"provider_url" json:"provider_url"`
ClientID string `db:"client_id" json:"client_id"` ClientID string `db:"client_id" json:"client_id"`
ClientSecret string `db:"client_secret" json:"client_secret"` ClientSecret string `db:"client_secret" json:"client_secret"`
RedirectURI string `db:"redirect_uri" json:"redirect_uri"` Provider string `db:"provider" json:"provider"`
ProviderURL string `db:"provider_url" json:"provider_url"`
RedirectURI string `db:"-" json:"redirect_uri"`
ProviderLogoURL string `db:"-" json:"logo_url"`
} }
// ProviderInfo holds the name and logo of a provider. // providerLogos holds known provider logos.
type ProviderInfo struct { var providerLogos = map[string]string{
Name string "Google": "/images/google-logo.png",
Logo string "Github": "/images/github-logo.png",
"Custom": "",
} }
var providerMap = map[string]ProviderInfo{ // SetProviderLogo provider logo to the OIDC model.
"accounts.google.com": {Name: "Google", Logo: "https://lh3.googleusercontent.com/COxitqgJr1sJnIDe8-jiKhxDx1FrYbtRHKJ9z_hELisAlapwE9LUPh6fcXIfb5vwpbMl4xl9H9TRFPc5NOO8Sb3VSgIBrfRYvW6cUA"}, func (oidc *OIDC) SetProviderLogo() {
"microsoftonline.com": {Name: "Microsoft", Logo: "https://logo"}, for provider, logo := range providerLogos {
"github.com": {Name: "Github", Logo: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTrg0GanJf8uJHY8yuvL6Vyk47iVTx-AchsAA&s"}, if oidc.Provider == provider {
} oidc.ProviderLogoURL = logo
// SetProviderInfo adds provider name and logo to an OIDC model.
func (oidc *OIDC) SetProviderInfo() {
for url, info := range providerMap {
if strings.Contains(oidc.ProviderURL, url) {
oidc.Name = info.Name
oidc.ProviderLogoURL = info.Logo
return
} }
} }
oidc.Name = "Custom"
oidc.ProviderLogoURL = "https://path_to_default_logo"
} }

View File

@@ -35,6 +35,7 @@ type queries struct {
UpdateOIDC *sqlx.Stmt `query:"update-oidc"` UpdateOIDC *sqlx.Stmt `query:"update-oidc"`
DeleteOIDC *sqlx.Stmt `query:"delete-oidc"` DeleteOIDC *sqlx.Stmt `query:"delete-oidc"`
} }
// New creates and returns a new instance of the oidc Manager. // New creates and returns a new instance of the oidc Manager.
func New(opts Opts) (*Manager, error) { func New(opts Opts) (*Manager, error) {
var q queries var q queries
@@ -56,7 +57,7 @@ func (o *Manager) Get(id int) (models.OIDC, error) {
o.lo.Error("error fetching oidc", "error", err) o.lo.Error("error fetching oidc", "error", err)
return oidc, envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil) return oidc, envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil)
} }
oidc.SetProviderInfo() oidc.SetProviderLogo()
return oidc, nil return oidc, nil
} }
@@ -68,23 +69,23 @@ func (o *Manager) GetAll() ([]models.OIDC, error) {
return oidc, envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil) return oidc, envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil)
} }
for i := range oidc { for i := range oidc {
oidc[i].SetProviderInfo() oidc[i].SetProviderLogo()
} }
return oidc, nil return oidc, nil
} }
// Create adds a new oidc. // Create adds a new oidc.
func (o *Manager) Create(oidc models.OIDC) error { func (o *Manager) Create(oidc models.OIDC) error {
if _, err := o.q.InsertOIDC.Exec(oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.RedirectURI); err != nil { if _, err := o.q.InsertOIDC.Exec(oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret); err != nil {
o.lo.Error("error inserting oidc", "error", err) o.lo.Error("error inserting oidc", "error", err)
return envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil) return envelope.NewError(envelope.GeneralError, "Error creating OIDC", nil)
} }
return nil return nil
} }
// Create updates a oidc by id. // Create updates a oidc by id.
func (o *Manager) Update(id int, oidc models.OIDC) error { func (o *Manager) Update(id int, oidc models.OIDC) error {
if _, err := o.q.UpdateOIDC.Exec(id, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.RedirectURI); err != nil { if _, err := o.q.UpdateOIDC.Exec(id, oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.Disabled); err != nil {
o.lo.Error("error updating oidc", "error", err) o.lo.Error("error updating oidc", "error", err)
return envelope.NewError(envelope.GeneralError, "Error updating OIDC", nil) return envelope.NewError(envelope.GeneralError, "Error updating OIDC", nil)
} }

View File

@@ -1,19 +1,17 @@
-- name: get-all-oidc -- name: get-all-oidc
SELECT id, disabled, provider_url, updated_at FROM oidc; SELECT id, name, disabled, provider_url, provider, client_id, client_secret, updated_at FROM oidc order by updated_at desc;
-- name: get-oidc -- name: get-oidc
SELECT * FROM oidc WHERE id = $1; SELECT * FROM oidc WHERE id = $1;
-- name: insert-oidc -- name: insert-oidc
INSERT INTO oidc (provider_url, client_id, client_secret, redirect_uri) INSERT INTO oidc (name, provider, provider_url, client_id, client_secret)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4, $5);
RETURNING *;
-- name: update-oidc -- name: update-oidc
UPDATE oidc UPDATE oidc
SET provider_url = $2, client_id = $3, client_secret = $4, redirect_uri = $5, updated_at = now() SET name = $2, provider = $3, provider_url = $4, client_id = $5, client_secret = $6, disabled = $7, updated_at = now()
WHERE id = $1 WHERE id = $1;
RETURNING *;
-- name: delete-oidc -- name: delete-oidc
DELETE FROM oidc WHERE id = $1; DELETE FROM oidc WHERE id = $1;