@frigear-nu/oauth-server 0.0.0-stage → 0.0.2

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.
Files changed (73) hide show
  1. package/README.md +49 -2
  2. package/dist/module.d.mts +206 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +107 -0
  5. package/dist/runtime/app/composables/index.d.ts +1 -0
  6. package/dist/runtime/app/composables/index.js +1 -0
  7. package/dist/runtime/app/composables/useOAuthApplications.d.ts +19 -0
  8. package/dist/runtime/app/composables/useOAuthApplications.js +41 -0
  9. package/dist/runtime/app/pages/admin.d.vue.ts +3 -0
  10. package/dist/runtime/app/pages/admin.vue +384 -0
  11. package/dist/runtime/app/pages/admin.vue.d.ts +3 -0
  12. package/dist/runtime/app/pages/applications.d.vue.ts +3 -0
  13. package/dist/runtime/app/pages/applications.vue +50 -0
  14. package/dist/runtime/app/pages/applications.vue.d.ts +3 -0
  15. package/dist/runtime/app/pages/authorize.d.vue.ts +3 -0
  16. package/dist/runtime/app/pages/authorize.vue +108 -0
  17. package/dist/runtime/app/pages/authorize.vue.d.ts +3 -0
  18. package/dist/runtime/menus.d.ts +2 -0
  19. package/dist/runtime/menus.js +27 -0
  20. package/dist/runtime/server/db/schema.sqlite.d.ts +682 -0
  21. package/dist/runtime/server/db/schema.sqlite.js +60 -0
  22. package/dist/runtime/server/routes/api/admin/application-revoke.post.d.ts +2 -0
  23. package/dist/runtime/server/routes/api/admin/application-revoke.post.js +7 -0
  24. package/dist/runtime/server/routes/api/admin/application-secret.post.d.ts +2 -0
  25. package/dist/runtime/server/routes/api/admin/application-secret.post.js +5 -0
  26. package/dist/runtime/server/routes/api/admin/application.delete.d.ts +2 -0
  27. package/dist/runtime/server/routes/api/admin/application.delete.js +6 -0
  28. package/dist/runtime/server/routes/api/admin/application.get.d.ts +2 -0
  29. package/dist/runtime/server/routes/api/admin/application.get.js +6 -0
  30. package/dist/runtime/server/routes/api/admin/application.patch.d.ts +2 -0
  31. package/dist/runtime/server/routes/api/admin/application.patch.js +9 -0
  32. package/dist/runtime/server/routes/api/admin/applications.get.d.ts +2 -0
  33. package/dist/runtime/server/routes/api/admin/applications.get.js +11 -0
  34. package/dist/runtime/server/routes/api/admin/applications.post.d.ts +2 -0
  35. package/dist/runtime/server/routes/api/admin/applications.post.js +8 -0
  36. package/dist/runtime/server/routes/api/consent.get.d.ts +2 -0
  37. package/dist/runtime/server/routes/api/consent.get.js +19 -0
  38. package/dist/runtime/server/routes/api/me-applications.get.d.ts +2 -0
  39. package/dist/runtime/server/routes/api/me-applications.get.js +4 -0
  40. package/dist/runtime/server/routes/oidc/authorize.get.d.ts +2 -0
  41. package/dist/runtime/server/routes/oidc/authorize.get.js +15 -0
  42. package/dist/runtime/server/routes/oidc/authorize.post.d.ts +2 -0
  43. package/dist/runtime/server/routes/oidc/authorize.post.js +27 -0
  44. package/dist/runtime/server/routes/oidc/jwks.get.d.ts +2 -0
  45. package/dist/runtime/server/routes/oidc/jwks.get.js +4 -0
  46. package/dist/runtime/server/routes/oidc/openid-configuration.get.d.ts +2 -0
  47. package/dist/runtime/server/routes/oidc/openid-configuration.get.js +21 -0
  48. package/dist/runtime/server/routes/oidc/revoke.post.d.ts +2 -0
  49. package/dist/runtime/server/routes/oidc/revoke.post.js +13 -0
  50. package/dist/runtime/server/routes/oidc/token.post.d.ts +2 -0
  51. package/dist/runtime/server/routes/oidc/token.post.js +56 -0
  52. package/dist/runtime/server/routes/oidc/userinfo.get.d.ts +2 -0
  53. package/dist/runtime/server/routes/oidc/userinfo.get.js +18 -0
  54. package/dist/runtime/server/utils/oauth-access.d.ts +34 -0
  55. package/dist/runtime/server/utils/oauth-access.js +49 -0
  56. package/dist/runtime/server/utils/oauth-applications.d.ts +65 -0
  57. package/dist/runtime/server/utils/oauth-applications.js +126 -0
  58. package/dist/runtime/server/utils/oauth-authorize.d.ts +29 -0
  59. package/dist/runtime/server/utils/oauth-authorize.js +33 -0
  60. package/dist/runtime/server/utils/oauth-config.d.ts +16 -0
  61. package/dist/runtime/server/utils/oauth-config.js +20 -0
  62. package/dist/runtime/server/utils/oauth-crypto.d.ts +18 -0
  63. package/dist/runtime/server/utils/oauth-crypto.js +48 -0
  64. package/dist/runtime/server/utils/oauth-grants.d.ts +60 -0
  65. package/dist/runtime/server/utils/oauth-grants.js +70 -0
  66. package/dist/runtime/server/utils/oauth-keys.d.ts +41 -0
  67. package/dist/runtime/server/utils/oauth-keys.js +55 -0
  68. package/dist/runtime/server/utils/oauth-tokens.d.ts +25 -0
  69. package/dist/runtime/server/utils/oauth-tokens.js +34 -0
  70. package/dist/runtime/utils/index.d.ts +24 -0
  71. package/dist/runtime/utils/index.js +51 -0
  72. package/dist/types.d.mts +3 -0
  73. package/package.json +92 -3
@@ -0,0 +1,384 @@
1
+ <script setup>
2
+ defineOptions({ name: "OAuthAdminPage" });
3
+ const { applications, status, error, create, update, remove, rotateSecret, revokeGrants } = useOAuthApplications();
4
+ const emptyForm = () => ({
5
+ name: "",
6
+ websiteUrl: "",
7
+ loginUrl: "",
8
+ previewUrlPattern: "",
9
+ description: "",
10
+ icon: "i-lucide-globe",
11
+ priority: 0,
12
+ allowedRoles: [],
13
+ tags: []
14
+ });
15
+ const formOpen = ref(false);
16
+ const editingId = ref(null);
17
+ const form = ref(emptyForm());
18
+ const busy = ref(false);
19
+ const failure = ref(null);
20
+ const secret = ref(null);
21
+ const forbidden = computed(() => error.value?.statusCode === 401 || error.value?.statusCode === 403);
22
+ function openCreate() {
23
+ editingId.value = null;
24
+ form.value = emptyForm();
25
+ failure.value = null;
26
+ formOpen.value = true;
27
+ }
28
+ function openEdit(application) {
29
+ editingId.value = application.id;
30
+ form.value = {
31
+ name: application.name,
32
+ websiteUrl: application.websiteUrl,
33
+ loginUrl: application.loginUrl ?? "",
34
+ previewUrlPattern: application.previewUrlPattern ?? "",
35
+ description: application.description ?? "",
36
+ icon: application.icon ?? "",
37
+ priority: application.priority,
38
+ allowedRoles: [...application.allowedRoles],
39
+ tags: [...application.tags]
40
+ };
41
+ failure.value = null;
42
+ formOpen.value = true;
43
+ }
44
+ const message = (err) => err.statusMessage || err.data?.message || "Something went wrong.";
45
+ async function run(task) {
46
+ busy.value = true;
47
+ failure.value = null;
48
+ try {
49
+ await task();
50
+ } catch (err) {
51
+ failure.value = message(err);
52
+ } finally {
53
+ busy.value = false;
54
+ }
55
+ }
56
+ function submit() {
57
+ const value = form.value;
58
+ const input = {
59
+ name: value.name,
60
+ websiteUrl: value.websiteUrl,
61
+ loginUrl: value.loginUrl || null,
62
+ previewUrlPattern: value.previewUrlPattern || null,
63
+ description: value.description || null,
64
+ icon: value.icon || null,
65
+ priority: Number(value.priority) || 0,
66
+ allowedRoles: value.allowedRoles,
67
+ tags: value.tags
68
+ };
69
+ return run(async () => {
70
+ if (editingId.value) {
71
+ await update(editingId.value, input);
72
+ } else {
73
+ const created = await create(input);
74
+ secret.value = { name: created.name, clientId: created.clientId, clientSecret: created.clientSecret };
75
+ }
76
+ formOpen.value = false;
77
+ });
78
+ }
79
+ const rotate = (application) => run(async () => {
80
+ if (!confirm(`Replace the client secret of ${application.name}? The old secret stops working and users are signed out.`)) return;
81
+ const clientSecret = await rotateSecret(application.id);
82
+ secret.value = { name: application.name, clientId: application.id, clientSecret };
83
+ });
84
+ const toggle = (application) => run(async () => {
85
+ await update(application.id, { isActive: !application.isActive });
86
+ });
87
+ const signOutEveryone = (application) => run(async () => {
88
+ if (confirm(`Sign everyone out of ${application.name}?`)) await revokeGrants(application.id);
89
+ });
90
+ const destroy = (application) => run(async () => {
91
+ if (confirm(`Delete ${application.name}? This cannot be undone.`)) await remove(application.id);
92
+ });
93
+ const copy = (value) => navigator.clipboard?.writeText(value);
94
+ </script>
95
+
96
+ <template>
97
+ <UContainer class="py-10">
98
+ <div class="mb-6 flex items-center justify-between gap-4">
99
+ <h1 class="text-2xl font-semibold">
100
+ OAuth applications
101
+ </h1>
102
+ <UButton
103
+ v-if="!forbidden"
104
+ icon="i-lucide-plus"
105
+ @click="openCreate"
106
+ >
107
+ New application
108
+ </UButton>
109
+ </div>
110
+
111
+ <UAlert
112
+ v-if="forbidden"
113
+ color="error"
114
+ variant="subtle"
115
+ title="You are not allowed to manage OAuth applications."
116
+ />
117
+ <UAlert
118
+ v-else-if="failure && !formOpen"
119
+ class="mb-4"
120
+ color="error"
121
+ variant="subtle"
122
+ :title="failure"
123
+ />
124
+
125
+ <p
126
+ v-if="!forbidden && status !== 'pending' && !applications.length"
127
+ class="text-muted"
128
+ >
129
+ No applications yet.
130
+ </p>
131
+
132
+ <div class="grid gap-4">
133
+ <UCard
134
+ v-for="application in applications"
135
+ :key="application.id"
136
+ >
137
+ <div class="flex flex-wrap items-start justify-between gap-4">
138
+ <div class="flex min-w-0 items-start gap-3">
139
+ <UIcon
140
+ :name="application.icon || 'i-lucide-globe'"
141
+ class="mt-1 size-6 shrink-0"
142
+ />
143
+ <div class="min-w-0">
144
+ <h2 class="flex items-center gap-2 font-medium">
145
+ {{ application.name }}
146
+ <UBadge
147
+ v-if="!application.isActive"
148
+ color="neutral"
149
+ variant="subtle"
150
+ >
151
+ Disabled
152
+ </UBadge>
153
+ </h2>
154
+ <p class="truncate text-sm text-muted">
155
+ {{ application.websiteUrl }} · {{ application.activeGrants ?? 0 }} signed in
156
+ </p>
157
+ <p class="truncate text-xs text-muted">
158
+ Callback: {{ application.callbackUrl }}
159
+ </p>
160
+ <div class="mt-2 flex flex-wrap gap-1">
161
+ <UBadge
162
+ v-for="role in application.allowedRoles"
163
+ :key="role"
164
+ variant="subtle"
165
+ >
166
+ {{ role }}
167
+ </UBadge>
168
+ <UBadge
169
+ v-if="!application.allowedRoles.length"
170
+ color="neutral"
171
+ variant="subtle"
172
+ >
173
+ Every signed-in user
174
+ </UBadge>
175
+ </div>
176
+ </div>
177
+ </div>
178
+ <div class="flex flex-wrap gap-2">
179
+ <UButton
180
+ size="sm"
181
+ color="neutral"
182
+ variant="outline"
183
+ :disabled="busy"
184
+ @click="openEdit(application)"
185
+ >
186
+ Edit
187
+ </UButton>
188
+ <UButton
189
+ size="sm"
190
+ color="neutral"
191
+ variant="outline"
192
+ :disabled="busy"
193
+ @click="toggle(application)"
194
+ >
195
+ {{ application.isActive ? "Disable" : "Enable" }}
196
+ </UButton>
197
+ <UButton
198
+ size="sm"
199
+ color="neutral"
200
+ variant="outline"
201
+ :disabled="busy"
202
+ @click="rotate(application)"
203
+ >
204
+ New secret
205
+ </UButton>
206
+ <UButton
207
+ size="sm"
208
+ color="neutral"
209
+ variant="outline"
210
+ :disabled="busy"
211
+ @click="signOutEveryone(application)"
212
+ >
213
+ Sign everyone out
214
+ </UButton>
215
+ <UButton
216
+ size="sm"
217
+ color="error"
218
+ variant="outline"
219
+ :disabled="busy"
220
+ @click="destroy(application)"
221
+ >
222
+ Delete
223
+ </UButton>
224
+ </div>
225
+ </div>
226
+ </UCard>
227
+ </div>
228
+
229
+ <UModal
230
+ v-model:open="formOpen"
231
+ :title="editingId ? 'Edit application' : 'New application'"
232
+ >
233
+ <template #body>
234
+ <form
235
+ id="oauth-application-form"
236
+ class="grid gap-4"
237
+ @submit.prevent="submit"
238
+ >
239
+ <UFormField
240
+ label="Name"
241
+ required
242
+ >
243
+ <UInput
244
+ v-model="form.name"
245
+ class="w-full"
246
+ required
247
+ />
248
+ </UFormField>
249
+ <UFormField
250
+ label="Website URL"
251
+ help="The origin of the application, without a path. The callback is the website URL plus the configured callback path."
252
+ required
253
+ >
254
+ <UInput
255
+ v-model="form.websiteUrl"
256
+ class="w-full"
257
+ placeholder="https://app.example.com"
258
+ required
259
+ />
260
+ </UFormField>
261
+ <UFormField
262
+ label="Login URL"
263
+ help="Where the Open button goes. Defaults to the website URL."
264
+ >
265
+ <UInput
266
+ v-model="form.loginUrl"
267
+ class="w-full"
268
+ />
269
+ </UFormField>
270
+ <UFormField
271
+ label="Preview URL pattern"
272
+ help="Optional, e.g. https://*.vercel.app, for preview deployments."
273
+ >
274
+ <UInput
275
+ v-model="form.previewUrlPattern"
276
+ class="w-full"
277
+ />
278
+ </UFormField>
279
+ <UFormField
280
+ label="Allowed roles"
281
+ help="Leave empty to allow every signed-in user."
282
+ >
283
+ <UInputTags
284
+ v-model="form.allowedRoles"
285
+ class="w-full"
286
+ />
287
+ </UFormField>
288
+ <UFormField label="Tags">
289
+ <UInputTags
290
+ v-model="form.tags"
291
+ class="w-full"
292
+ />
293
+ </UFormField>
294
+ <UFormField label="Description">
295
+ <UTextarea
296
+ v-model="form.description"
297
+ class="w-full"
298
+ />
299
+ </UFormField>
300
+ <div class="grid grid-cols-2 gap-4">
301
+ <UFormField label="Icon">
302
+ <UInput
303
+ v-model="form.icon"
304
+ class="w-full"
305
+ />
306
+ </UFormField>
307
+ <UFormField label="Priority">
308
+ <UInput
309
+ v-model.number="form.priority"
310
+ type="number"
311
+ class="w-full"
312
+ />
313
+ </UFormField>
314
+ </div>
315
+ <UAlert
316
+ v-if="failure"
317
+ color="error"
318
+ variant="subtle"
319
+ :title="failure"
320
+ />
321
+ </form>
322
+ </template>
323
+ <template #footer>
324
+ <div class="flex w-full justify-end gap-2">
325
+ <UButton
326
+ color="neutral"
327
+ variant="outline"
328
+ @click="formOpen = false"
329
+ >
330
+ Cancel
331
+ </UButton>
332
+ <UButton
333
+ type="submit"
334
+ form="oauth-application-form"
335
+ :loading="busy"
336
+ >
337
+ {{ editingId ? "Save" : "Create" }}
338
+ </UButton>
339
+ </div>
340
+ </template>
341
+ </UModal>
342
+
343
+ <UModal
344
+ :open="!!secret"
345
+ :title="secret ? `Credentials for ${secret.name}` : ''"
346
+ description="Copy the client secret now. It is only shown once."
347
+ @update:open="secret = null"
348
+ >
349
+ <template #body>
350
+ <div
351
+ v-if="secret"
352
+ class="grid gap-3 text-sm"
353
+ >
354
+ <div>
355
+ <div class="text-muted">
356
+ Client ID
357
+ </div>
358
+ <code class="break-all">{{ secret.clientId }}</code>
359
+ <UButton
360
+ size="xs"
361
+ variant="link"
362
+ @click="copy(secret.clientId)"
363
+ >
364
+ Copy
365
+ </UButton>
366
+ </div>
367
+ <div>
368
+ <div class="text-muted">
369
+ Client secret
370
+ </div>
371
+ <code class="break-all">{{ secret.clientSecret }}</code>
372
+ <UButton
373
+ size="xs"
374
+ variant="link"
375
+ @click="copy(secret.clientSecret)"
376
+ >
377
+ Copy
378
+ </UButton>
379
+ </div>
380
+ </div>
381
+ </template>
382
+ </UModal>
383
+ </UContainer>
384
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,50 @@
1
+ <script setup>
2
+ defineOptions({ name: "OAuthApplicationsPage" });
3
+ const { applications, status } = useMyApplications();
4
+ </script>
5
+
6
+ <template>
7
+ <UContainer class="py-10">
8
+ <h1 class="mb-6 text-2xl font-semibold">
9
+ Applications
10
+ </h1>
11
+ <p
12
+ v-if="status !== 'pending' && !applications.length"
13
+ class="text-muted"
14
+ >
15
+ There are no applications you can sign in to yet.
16
+ </p>
17
+ <div class="grid gap-4 md:grid-cols-2">
18
+ <UCard
19
+ v-for="application in applications"
20
+ :key="application.id"
21
+ >
22
+ <div class="flex items-start gap-3">
23
+ <UIcon
24
+ :name="application.icon || 'i-lucide-globe'"
25
+ class="mt-1 size-6 shrink-0"
26
+ />
27
+ <div class="min-w-0 flex-1">
28
+ <h2 class="font-medium">
29
+ {{ application.name }}
30
+ </h2>
31
+ <p
32
+ v-if="application.description"
33
+ class="text-sm text-muted"
34
+ >
35
+ {{ application.description }}
36
+ </p>
37
+ </div>
38
+ <UButton
39
+ :to="application.loginUrl"
40
+ external
41
+ trailing-icon="i-lucide-arrow-up-right"
42
+ size="sm"
43
+ >
44
+ Open
45
+ </UButton>
46
+ </div>
47
+ </UCard>
48
+ </div>
49
+ </UContainer>
50
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,108 @@
1
+ <script setup>
2
+ defineOptions({ name: "OAuthAuthorizePage" });
3
+ const route = useRoute();
4
+ const signInPath = useRuntimeConfig().public.users?.pages?.login || "/login";
5
+ const { data: consent, error } = await useFetch("/api/oauth-server/consent", {
6
+ query: route.query,
7
+ key: `oauth-server:consent:${route.fullPath}`
8
+ });
9
+ if (error.value?.statusCode === 401) {
10
+ await navigateTo(`${signInPath}?redirect=${encodeURIComponent(route.fullPath)}`);
11
+ }
12
+ const scopeLabels = {
13
+ openid: "Verify your identity",
14
+ profile: "Your name and profile picture",
15
+ email: "Your email address"
16
+ };
17
+ const pending = ref(null);
18
+ const failure = ref(null);
19
+ async function decide(approved) {
20
+ pending.value = approved ? "approve" : "deny";
21
+ failure.value = null;
22
+ try {
23
+ const { redirectUrl } = await $fetch("/oauth/authorize", {
24
+ method: "POST",
25
+ body: { ...route.query, approved }
26
+ });
27
+ await navigateTo(redirectUrl, { external: true });
28
+ } catch (err) {
29
+ failure.value = err.statusMessage || "Something went wrong. Please try again.";
30
+ pending.value = null;
31
+ }
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <UContainer class="py-10">
37
+ <UCard
38
+ v-if="consent"
39
+ class="mx-auto max-w-md"
40
+ >
41
+ <template #header>
42
+ <div class="flex items-center gap-3">
43
+ <UIcon
44
+ :name="consent.application.icon || 'i-lucide-globe'"
45
+ class="size-8"
46
+ />
47
+ <div>
48
+ <h1 class="text-lg font-semibold">
49
+ {{ consent.application.name }}
50
+ </h1>
51
+ <p class="text-sm text-muted">
52
+ {{ consent.application.websiteUrl }}
53
+ </p>
54
+ </div>
55
+ </div>
56
+ </template>
57
+
58
+ <p class="mb-3 text-sm">
59
+ Signed in as <strong>{{ consent.user.name }}</strong> ({{ consent.user.email }}).
60
+ <strong>{{ consent.application.name }}</strong> wants to:
61
+ </p>
62
+ <ul class="mb-4 list-inside list-disc space-y-1 text-sm">
63
+ <li
64
+ v-for="scope in consent.scope"
65
+ :key="scope"
66
+ >
67
+ {{ scopeLabels[scope] ?? scope }}
68
+ </li>
69
+ </ul>
70
+ <UAlert
71
+ v-if="failure"
72
+ color="error"
73
+ variant="subtle"
74
+ :title="failure"
75
+ />
76
+
77
+ <template #footer>
78
+ <div class="flex justify-end gap-2">
79
+ <UButton
80
+ color="neutral"
81
+ variant="outline"
82
+ :loading="pending === 'deny'"
83
+ :disabled="!!pending"
84
+ @click="decide(false)"
85
+ >
86
+ Deny
87
+ </UButton>
88
+ <UButton
89
+ :loading="pending === 'approve'"
90
+ :disabled="!!pending"
91
+ @click="decide(true)"
92
+ >
93
+ Allow
94
+ </UButton>
95
+ </div>
96
+ </template>
97
+ </UCard>
98
+
99
+ <UAlert
100
+ v-else
101
+ class="mx-auto max-w-md"
102
+ color="error"
103
+ variant="subtle"
104
+ title="This sign-in request cannot be completed"
105
+ :description="error?.statusMessage || 'The request is invalid.'"
106
+ />
107
+ </UContainer>
108
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@frigear-nu/menu/types").MenuSource;
2
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { defineMenuItem, defineMenuSource, extendMenuItem } from "@frigear-nu/menu/utils";
2
+ export default defineMenuSource(() => {
3
+ const config = useRuntimeConfig().public.oauthServer;
4
+ if (config?.pages?.applications) {
5
+ const item = defineMenuItem({
6
+ id: "oauth-applications",
7
+ label: "Applications",
8
+ description: "Applications you can sign in to.",
9
+ icon: "i-lucide-layout-grid",
10
+ to: config.pages.applications,
11
+ order: 50
12
+ });
13
+ extendMenuItem("account", item);
14
+ extendMenuItem("dashboard.account", item);
15
+ }
16
+ if (config?.pages?.admin) {
17
+ extendMenuItem("dashboard.account", defineMenuItem({
18
+ id: "oauth-admin",
19
+ label: "OAuth applications",
20
+ description: "Manage the applications users can sign in to.",
21
+ icon: "i-lucide-key-round",
22
+ to: config.pages.admin,
23
+ order: 90,
24
+ tags: ["admin"]
25
+ }));
26
+ }
27
+ });