@escalated-dev/escalated 0.11.3 → 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escalated-dev/escalated",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Vue 3 + Inertia.js UI components for Escalated — the embeddable support ticket system",
5
5
  "author": "Escalated Dev <hello@escalated.dev>",
6
6
  "license": "MIT",
package/pages.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "Generated by scripts/generate-page-manifest.js from src/pages. Every name here resolves to a component; a name a backend renders that is not here is a blank screen.",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "pages": [
5
5
  "Escalated/Admin/ApiTokens/Index",
6
6
  "Escalated/Admin/AuditLog/Index",
@@ -83,6 +83,7 @@
83
83
  "Escalated/Agent/Dashboard",
84
84
  "Escalated/Agent/TicketIndex",
85
85
  "Escalated/Agent/TicketShow",
86
+ "Escalated/Auth/TwoFactorChallenge",
86
87
  "Escalated/Customer/Create",
87
88
  "Escalated/Customer/Index",
88
89
  "Escalated/Customer/KnowledgeBase/Article",
@@ -0,0 +1,117 @@
1
+ <script setup>
2
+ import { useForm } from '@inertiajs/vue3';
3
+ import { computed, ref } from 'vue';
4
+
5
+ /**
6
+ * The post-login two-factor challenge.
7
+ *
8
+ * A backend renders this after authenticating a password but before letting
9
+ * someone into the panel, so it deliberately does not use EscalatedLayout —
10
+ * that layout is the signed-in chrome, and at this point the sign-in is not
11
+ * finished. Nothing here should suggest access that has not been granted yet.
12
+ *
13
+ * Both a TOTP code and a recovery code post to the same endpoint; the backend
14
+ * reads whichever field arrived.
15
+ */
16
+ const props = defineProps({
17
+ // The route to post to. A host mounts Escalated under its own prefix, and
18
+ // this page is reached before any layout has resolved one.
19
+ action: { type: String, default: '' },
20
+ error: { type: String, default: '' },
21
+ });
22
+
23
+ const useRecoveryCode = ref(false);
24
+
25
+ const form = useForm({
26
+ code: '',
27
+ recovery_code: '',
28
+ });
29
+
30
+ const endpoint = computed(() => props.action || route('escalated.two-factor.verify'));
31
+
32
+ function submit() {
33
+ // Only the field in use is sent, so a blank one cannot be mistaken for an
34
+ // attempt with an empty code.
35
+ form.transform((data) =>
36
+ useRecoveryCode.value ? { recovery_code: data.recovery_code } : { code: data.code },
37
+ ).post(endpoint.value);
38
+ }
39
+
40
+ function toggleMode() {
41
+ useRecoveryCode.value = !useRecoveryCode.value;
42
+ form.clearErrors();
43
+ form.code = '';
44
+ form.recovery_code = '';
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <div class="flex min-h-screen items-center justify-center bg-[var(--esc-panel-bg)] px-4">
50
+ <div class="w-full max-w-sm">
51
+ <h1 class="text-lg font-semibold text-[var(--esc-panel-text-secondary)]">Two-factor authentication</h1>
52
+ <p class="mt-1 text-sm text-[var(--esc-panel-text-muted)]">
53
+ {{
54
+ useRecoveryCode
55
+ ? 'Enter one of the recovery codes you saved when you set this up.'
56
+ : 'Enter the six-digit code from your authenticator app.'
57
+ }}
58
+ </p>
59
+
60
+ <form
61
+ class="mt-6 rounded-xl border border-[var(--esc-panel-border)] bg-[var(--esc-panel-surface)] p-5"
62
+ @submit.prevent="submit"
63
+ >
64
+ <div v-if="!useRecoveryCode">
65
+ <label class="block text-xs font-medium text-[var(--esc-panel-text-muted)]" for="totp-code">
66
+ Authentication code
67
+ </label>
68
+ <input
69
+ id="totp-code"
70
+ v-model="form.code"
71
+ type="text"
72
+ inputmode="numeric"
73
+ autocomplete="one-time-code"
74
+ maxlength="6"
75
+ autofocus
76
+ class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-bg)] px-3 py-2 text-center text-lg tracking-[0.4em] text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-accent)] focus:outline-none"
77
+ />
78
+ </div>
79
+
80
+ <div v-else>
81
+ <label class="block text-xs font-medium text-[var(--esc-panel-text-muted)]" for="recovery-code">
82
+ Recovery code
83
+ </label>
84
+ <input
85
+ id="recovery-code"
86
+ v-model="form.recovery_code"
87
+ type="text"
88
+ autocomplete="off"
89
+ autofocus
90
+ class="mt-1 w-full rounded-lg border border-[var(--esc-panel-border)] bg-[var(--esc-panel-bg)] px-3 py-2 text-[var(--esc-panel-text-secondary)] focus:border-[var(--esc-panel-accent)] focus:outline-none"
91
+ />
92
+ <p class="mt-1 text-xs text-[var(--esc-panel-text-muted)]">Each recovery code works once.</p>
93
+ </div>
94
+
95
+ <p v-if="error || form.errors.code || form.errors.recovery_code" class="mt-2 text-xs text-red-400">
96
+ {{ error || form.errors.code || form.errors.recovery_code }}
97
+ </p>
98
+
99
+ <button
100
+ type="submit"
101
+ :disabled="form.processing"
102
+ class="mt-4 w-full rounded-lg bg-gradient-to-r from-[var(--esc-panel-accent)] to-[var(--esc-panel-accent-secondary)] px-4 py-2 text-sm font-medium text-white transition-all hover:from-[var(--esc-panel-accent-hover)] hover:to-[var(--esc-panel-accent-secondary-hover)] disabled:opacity-50"
103
+ >
104
+ Verify
105
+ </button>
106
+
107
+ <button
108
+ type="button"
109
+ class="mt-3 w-full text-xs text-[var(--esc-panel-text-muted)] transition-colors hover:text-[var(--esc-panel-text-secondary)]"
110
+ @click="toggleMode"
111
+ >
112
+ {{ useRecoveryCode ? 'Use an authenticator code instead' : 'Use a recovery code instead' }}
113
+ </button>
114
+ </form>
115
+ </div>
116
+ </div>
117
+ </template>