aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/views/Account/components/oauth/SingleApplication.vue
blob: 60bad6864333c8bf9a07ebfdbdfe22717b70f7b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<script setup lang="ts">
import { toUpper } from 'lodash-es'
import { apiCall, useWait, useConfirm, usePassConfirm, useDataBuffer } from '@vnuge/vnlib.browser'
import { ref, computed, toRefs, watch } from 'vue'
import { get, set, useClipboard, useTimeAgo, useToggle } from '@vueuse/core'
import { getAppValidator } from './o2AppValidation'
import { OAuth2Application } from '../../../../store/oauthAppsPlugin'
import { useStore } from '../../../../store'

const props = defineProps<{
  application: OAuth2Application
  allowEdit: boolean
}>()

const store = useStore()
const { application, allowEdit } = toRefs(props)

//Init data buffer around application
const { data, revert, modified, buffer, update, apply } = useDataBuffer(get(application),
  async (adb) => {
    await store.oauth2.updateAppMeta(adb.buffer);
    store.oauth2.refresh();
  })

//Watch for store app changes and apply them to the buffer
watch(application, apply)

const { waiting } = useWait()
const { reveal } = useConfirm()
const { elevatedApiCall } = usePassConfirm()
const { copied, copy } = useClipboard()

const { v$, validate, reset } = getAppValidator(buffer)

const [showEdit, toggleEdit] = useToggle()
const newSecret = ref<string | null>(null);

const name = computed(() => data.name)
const clientId = computed(() => toUpper(data.client_id))
const createdTime = useTimeAgo(data.Created);
const formId = computed(() => `app-form-${data.client_id}`)

const onCancel = function () {
  revert()
  reset()
  toggleEdit(false)
}

const onSubmit = async function () {
  // Validate the new app form
  if (!await validate()) {
    return
  }
  // Create the new app
  await apiCall(async ({ toaster }) => {
    // Update does not return anything, if successful
    await update()

    toaster.general.success({
      text: 'Application successfully updated',
      title: 'Success'
    })
    reset()
    toggleEdit(false)
  })
}

const updateSecret = async function () {
  // Show a confrimation prompt
  const { isCanceled } = await reveal({
    title: 'Update Secret?',
    text: `Are you sure you want to update the secret? Any active sessions will be invalidated, and the old secret will be invalidated.`
  })
  if (isCanceled) {
    return
  }
  await elevatedApiCall(async ({ password }) => {
    // Submit the secret update with the new challenge
    newSecret.value = await store.oauth2.updateAppSecret(data, password)
    store.oauth2.refresh()
  })
}

const onDelete = async function () {
  // Show a confirmation prompt
  const { isCanceled } = await reveal({
    title: 'Delete?',
    text: 'You are about to permanently delete this application. This will invalidate any active sessions.',
    subtext: ''
  })
  if (isCanceled) {
    return
  }
  await elevatedApiCall(async ({ password, toaster }) => {
    await store.oauth2.deleteApp(data, password)
    toaster.general.success({
      text: 'Application deleted successfully',
      title: 'Success'
    })
    store.oauth2.refresh()
  })
}

const closeNewSecret = () => set(newSecret, null);

</script>

<template>
  <div :id="data.Id">
    <div class="flex flex-row">
      <div class="flex ml-0 mr-auto">
        <div class="flex w-8 h-8 rounded-full bg-primary-500">
          <div class="m-auto text-white dark:text-dark-500">
            <fa-icon icon="key"></fa-icon>
          </div>
        </div>
        <div class="inline my-auto ml-2">
          <h5 class="m-0">{{ name }}</h5>
        </div>
      </div>
      <div v-if="allowEdit && showEdit" class="button-group">
        <button class="btn primary xs" :disabled="!modified" @click="onSubmit">Update</button>
        <button class="btn xs" @click="onCancel">Cancel</button>
      </div>
      <div v-else class="">
        <button class="btn no-border xs" @click="toggleEdit(true)">Edit</button>
      </div>
    </div>
    <div class="px-3 py-1 text-color-background">
      <div class="my-1">
        <span> Client ID: </span>
        <span class="font-mono text-color-foreground">{{ clientId }}</span>
      </div>
      <div class="text-sm">
        <span> Created: </span>
        <span>{{ createdTime }}</span>
      </div>
      <div v-if="!showEdit" class="text-sm">
        <span>{{ data.description }}</span>
      </div>
    </div>
    <div v-if="newSecret" class="flex">
      <div class="py-4 mx-auto">
        <div class="pl-1 mb-2">
          New secret
        </div>
        <div class="p-4 text-sm break-all border-2 rounded dark:border-dark-500 dark:bg-dark-700">
            {{ newSecret }}
        </div>
        <div class="flex justify-end my-3">
          <button v-if="!copied" class="rounded btn" @click="copy(newSecret)">
            Copy
          </button>
          <button v-else class="rounded btn" @click="closeNewSecret">
            Done
          </button>
        </div>
      </div>
    </div>
    <div v-else-if="showEdit" class="app-form-container">
      <div class="py-4">
        <form :id="formId" class="max-w-md mx-auto">
          <fieldset :disabled="waiting" class="">
            <div class="input-container">
              <div class="pl-1 mb-1">
                App name
              </div>
              <input class="w-full input primary" :class="{ 'invalid': v$.name.$invalid }" v-model="v$.name.$model" type="text" name="name" />
            </div>
            <div class="mt-3 input-container">
              <div class="pl-1 mb-1">
                App description
                <span class="text-sm">(optional)</span>
              </div>
              <textarea class="w-full input primary" :class="{ 'invalid': v$.description.$invalid }" v-model="v$.description.$model" name="description" rows="3" />
            </div>
          </fieldset>
        </form>
      </div>
      <div class="mt-3">
        
        <div class="mx-auto w-fit">
          <div class="button-group">
            <button class="btn xs" @click.prevent="updateSecret">
              <fa-icon icon="sync" />
              <span class="pl-2">New Secret</span>
            </button>
            <button class="btn red xs" @click.prevent="onDelete">
              <fa-icon icon="minus-circle" />
              <span class="pl-2">Delete</span>
            </button>
          </div>
        </div>

      </div>
    </div>
  </div>
</template>