@frigear-nu/oauth-server 0.0.9 → 0.1.0

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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@frigear-nu/oauth-server",
3
3
  "configKey": "oauthServer",
4
- "version": "0.0.9",
4
+ "version": "0.1.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -24,11 +24,11 @@ const module$1 = defineNuxtModule({
24
24
  "@frigear-nu/mcp": { version: ">=0.0.1" },
25
25
  "@frigear-nu/users": { version: ">=0.0.1" },
26
26
  "@frigear-nu/ui": { version: ">=0.0.1" },
27
- // Provides the shared page components of the admin page (`AdminPage`, `AdminTable`, ...).
27
+ // Provides the admin shell (layout, nav groups) the admin page lives in.
28
28
  "@frigear-nu/admin": { version: ">=0.0.0", optional: true },
29
29
  "@frigear-nu/menu": { version: ">=0.0.1", optional: true }
30
30
  },
31
- async setup(options, nuxt) {
31
+ setup(options, nuxt) {
32
32
  registerModule(MODULE_NAME, nuxt);
33
33
  addAuthScope(nuxt, { name: "oauth:read", description: "See which applications you can sign in to (MCP)" });
34
34
  const resolver = createResolver(import.meta.url);
@@ -67,7 +67,7 @@ const module$1 = defineNuxtModule({
67
67
  applications: applicationsPath
68
68
  }
69
69
  };
70
- await registerDatabaseTable(nuxt, {
70
+ registerDatabaseTable(nuxt, {
71
71
  resolver,
72
72
  schema: (dialect) => `./runtime/server/db/schema.${dialect}`
73
73
  });
@@ -94,14 +94,15 @@ const module$1 = defineNuxtModule({
94
94
  if (settings.mcp === true) addMcpDefinitions(nuxt, resolver.resolve("./runtime/server/mcp"));
95
95
  addImports([
96
96
  { name: "useOAuthApplications", from: resolver.resolve("./runtime/app/composables/useOAuthApplications") },
97
- { name: "useMyApplications", from: resolver.resolve("./runtime/app/composables/useOAuthApplications") }
97
+ { name: "useMyApplications", from: resolver.resolve("./runtime/app/composables/useOAuthApplications") },
98
+ { name: "useOAuthApplicationActions", from: resolver.resolve("./runtime/app/composables/useOAuthApplicationActions") }
98
99
  ]);
99
100
  addModulePages(MODULE_NAME, resolver, [
100
101
  { name: "oauth-authorize", path: settings.pages?.authorize !== false ? authorizePath : void 0, file: "./runtime/app/pages/authorize.vue" },
101
102
  { name: "oauth-admin", path: adminPath, file: "./runtime/app/pages/admin.vue" },
102
103
  { name: "oauth-applications", path: applicationsPath, file: "./runtime/app/pages/applications.vue" }
103
104
  ]);
104
- addOptionalMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "dashboard.account" });
105
+ addOptionalMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "account" });
105
106
  }
106
107
  });
107
108
 
@@ -0,0 +1,18 @@
1
+ import type { AdminApplicationDto, ApplicationInput, CreatedApplicationDto } from '../../../types.js';
2
+ type Saved = AdminApplicationDto | CreatedApplicationDto;
3
+ /**
4
+ * Create or edit an application. Open it with `useOverlay()` (see `useOAuthApplicationActions`): it closes with the
5
+ * saved application, or `false` on cancel. `save` performs the request, so a failure is toasted and the form stays open.
6
+ */
7
+ type __VLS_Props = {
8
+ /** The application to edit; leave out to create one. */
9
+ application?: AdminApplicationDto;
10
+ save: (input: ApplicationInput) => Promise<Saved>;
11
+ };
12
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ close: (value: false | Saved) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onClose?: ((value: false | Saved) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,160 @@
1
+ <script setup>
2
+ import * as z from "zod";
3
+ defineOptions({ name: "OAuthApplicationSlideover" });
4
+ const props = defineProps({
5
+ application: { type: Object, required: false },
6
+ save: { type: Function, required: true }
7
+ });
8
+ const emit = defineEmits(["close"]);
9
+ const t = useModuleT("oauthServer");
10
+ const { busy, run } = useAction();
11
+ const schema = z.object({
12
+ name: z.string().trim().min(1, t("form.nameRequired", "Enter a name.")),
13
+ websiteUrl: z.url(t("form.websiteUrlInvalid", "Enter a full URL, for example https://app.example.com.")),
14
+ loginUrl: z.string(),
15
+ previewUrlPattern: z.string(),
16
+ description: z.string(),
17
+ icon: z.string(),
18
+ priority: z.number(),
19
+ allowedRoles: z.array(z.string()),
20
+ tags: z.array(z.string())
21
+ });
22
+ const state = reactive({
23
+ name: props.application?.name ?? "",
24
+ websiteUrl: props.application?.websiteUrl ?? "",
25
+ loginUrl: props.application?.loginUrl ?? "",
26
+ previewUrlPattern: props.application?.previewUrlPattern ?? "",
27
+ description: props.application?.description ?? "",
28
+ icon: props.application ? props.application.icon ?? "" : "i-lucide-globe",
29
+ priority: props.application?.priority ?? 0,
30
+ allowedRoles: [...props.application?.allowedRoles ?? []],
31
+ tags: [...props.application?.tags ?? []]
32
+ });
33
+ const formId = "oauth-application-form";
34
+ async function onSubmit(event) {
35
+ const value = event.data;
36
+ const saved = await run(() => props.save({
37
+ name: value.name,
38
+ websiteUrl: value.websiteUrl,
39
+ loginUrl: value.loginUrl || null,
40
+ previewUrlPattern: value.previewUrlPattern || null,
41
+ description: value.description || null,
42
+ icon: value.icon || null,
43
+ priority: Number(value.priority) || 0,
44
+ allowedRoles: value.allowedRoles,
45
+ tags: value.tags
46
+ }));
47
+ if (saved) emit("close", saved);
48
+ }
49
+ </script>
50
+
51
+ <template>
52
+ <FormSlideover
53
+ :title="application ? t('form.editTitle', 'Edit application') : t('form.createTitle', 'Add application')"
54
+ :description="application ? t('form.editDescription', 'Change how the application is registered.') : t('form.createDescription', 'Register an application that users can sign in to.')"
55
+ :form-id="formId"
56
+ :submit-label="application ? t('form.save', 'Save application') : t('form.create', 'Add application')"
57
+ :loading="busy"
58
+ @close="emit('close', $event)"
59
+ >
60
+ <UForm
61
+ :id="formId"
62
+ :schema="schema"
63
+ :state="state"
64
+ class="grid gap-4"
65
+ @submit="onSubmit"
66
+ >
67
+ <UFormField
68
+ name="name"
69
+ :label="t('form.name', 'Name')"
70
+ required
71
+ >
72
+ <UInput
73
+ v-model="state.name"
74
+ class="w-full"
75
+ />
76
+ </UFormField>
77
+ <UFormField
78
+ name="websiteUrl"
79
+ :label="t('form.websiteUrl', 'Website URL')"
80
+ :description="t('form.websiteUrlHint', 'The origin of the application, without a path. The callback is the website URL plus the configured callback path.')"
81
+ required
82
+ >
83
+ <UInput
84
+ v-model="state.websiteUrl"
85
+ class="w-full"
86
+ placeholder="https://app.example.com"
87
+ />
88
+ </UFormField>
89
+ <UFormField
90
+ name="loginUrl"
91
+ :label="t('form.loginUrl', 'Login URL')"
92
+ :description="t('form.loginUrlHint', 'Where the Open button goes. Defaults to the website URL.')"
93
+ >
94
+ <UInput
95
+ v-model="state.loginUrl"
96
+ class="w-full"
97
+ />
98
+ </UFormField>
99
+ <UFormField
100
+ name="previewUrlPattern"
101
+ :label="t('form.previewUrlPattern', 'Preview URL pattern')"
102
+ :description="t('form.previewUrlPatternHint', 'Optional, e.g. https://*.vercel.app, for preview deployments.')"
103
+ >
104
+ <UInput
105
+ v-model="state.previewUrlPattern"
106
+ class="w-full"
107
+ />
108
+ </UFormField>
109
+ <UFormField
110
+ name="allowedRoles"
111
+ :label="t('form.allowedRoles', 'Allowed roles')"
112
+ :description="t('form.allowedRolesHint', 'Leave empty to allow every signed-in user.')"
113
+ >
114
+ <UInputTags
115
+ v-model="state.allowedRoles"
116
+ class="w-full"
117
+ />
118
+ </UFormField>
119
+ <UFormField
120
+ name="tags"
121
+ :label="t('form.tags', 'Tags')"
122
+ >
123
+ <UInputTags
124
+ v-model="state.tags"
125
+ class="w-full"
126
+ />
127
+ </UFormField>
128
+ <UFormField
129
+ name="description"
130
+ :label="t('form.description', 'Description')"
131
+ >
132
+ <UTextarea
133
+ v-model="state.description"
134
+ class="w-full"
135
+ />
136
+ </UFormField>
137
+ <div class="grid grid-cols-2 gap-4">
138
+ <UFormField
139
+ name="icon"
140
+ :label="t('form.icon', 'Icon')"
141
+ >
142
+ <UInput
143
+ v-model="state.icon"
144
+ class="w-full"
145
+ />
146
+ </UFormField>
147
+ <UFormField
148
+ name="priority"
149
+ :label="t('form.priority', 'Priority')"
150
+ >
151
+ <UInput
152
+ v-model.number="state.priority"
153
+ type="number"
154
+ class="w-full"
155
+ />
156
+ </UFormField>
157
+ </div>
158
+ </UForm>
159
+ </FormSlideover>
160
+ </template>
@@ -0,0 +1,18 @@
1
+ import type { AdminApplicationDto, ApplicationInput, CreatedApplicationDto } from '../../../types.js';
2
+ type Saved = AdminApplicationDto | CreatedApplicationDto;
3
+ /**
4
+ * Create or edit an application. Open it with `useOverlay()` (see `useOAuthApplicationActions`): it closes with the
5
+ * saved application, or `false` on cancel. `save` performs the request, so a failure is toasted and the form stays open.
6
+ */
7
+ type __VLS_Props = {
8
+ /** The application to edit; leave out to create one. */
9
+ application?: AdminApplicationDto;
10
+ save: (input: ApplicationInput) => Promise<Saved>;
11
+ };
12
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ close: (value: false | Saved) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onClose?: ((value: false | Saved) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,9 @@
1
+ /** Shown once after creating an application or replacing its secret. Opened by `useOAuthApplicationActions`. */
2
+ type __VLS_Props = {
3
+ name: string;
4
+ clientId: string;
5
+ clientSecret: string;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,53 @@
1
+ <script setup>
2
+ defineOptions({ name: "OAuthCredentialsModal" });
3
+ defineProps({
4
+ name: { type: String, required: true },
5
+ clientId: { type: String, required: true },
6
+ clientSecret: { type: String, required: true }
7
+ });
8
+ const t = useModuleT("oauthServer");
9
+ const { copy } = useCopyToClipboard();
10
+ </script>
11
+
12
+ <template>
13
+ <UModal
14
+ :title="t('credentials.title', 'Credentials for {name}', { name })"
15
+ :description="t('credentials.description', 'Copy the client secret now. It is only shown once.')"
16
+ >
17
+ <template #body>
18
+ <DescriptionList
19
+ :items="[
20
+ { key: 'clientId', label: t('credentials.clientId', 'Client ID') },
21
+ { key: 'clientSecret', label: t('credentials.clientSecret', 'Client secret') }
22
+ ]"
23
+ >
24
+ <template #clientId>
25
+ <div class="flex items-center gap-2">
26
+ <code class="min-w-0 break-all">{{ clientId }}</code>
27
+ <UButton
28
+ size="xs"
29
+ color="neutral"
30
+ variant="outline"
31
+ icon="i-lucide-copy"
32
+ :label="t('credentials.copy', 'Copy')"
33
+ @click="copy(clientId, t('credentials.copiedClientId', 'Client ID copied'))"
34
+ />
35
+ </div>
36
+ </template>
37
+ <template #clientSecret>
38
+ <div class="flex items-center gap-2">
39
+ <code class="min-w-0 break-all">{{ clientSecret }}</code>
40
+ <UButton
41
+ size="xs"
42
+ color="neutral"
43
+ variant="outline"
44
+ icon="i-lucide-copy"
45
+ :label="t('credentials.copy', 'Copy')"
46
+ @click="copy(clientSecret, t('credentials.copiedClientSecret', 'Client secret copied'))"
47
+ />
48
+ </div>
49
+ </template>
50
+ </DescriptionList>
51
+ </template>
52
+ </UModal>
53
+ </template>
@@ -0,0 +1,9 @@
1
+ /** Shown once after creating an application or replacing its secret. Opened by `useOAuthApplicationActions`. */
2
+ type __VLS_Props = {
3
+ name: string;
4
+ clientId: string;
5
+ clientSecret: string;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -1 +1,2 @@
1
- export { useOAuthApplications, useMyApplications } from './useOAuthApplications.js';
1
+ export { useOAuthApplications, useMyApplications, oauthApplicationsApi, OAUTH_APPLICATIONS_KEY } from './useOAuthApplications.js';
2
+ export { useOAuthApplicationActions } from './useOAuthApplicationActions.js';
@@ -1 +1,2 @@
1
- export { useOAuthApplications, useMyApplications } from "./useOAuthApplications.js";
1
+ export { useOAuthApplications, useMyApplications, oauthApplicationsApi, OAUTH_APPLICATIONS_KEY } from "./useOAuthApplications.js";
2
+ export { useOAuthApplicationActions } from "./useOAuthApplicationActions.js";
@@ -0,0 +1,20 @@
1
+ import type { DropdownMenuItem } from '@nuxt/ui';
2
+ import type { AdminApplicationDto, CreatedApplicationDto } from '../../../types.js';
3
+ type SavedApplication = AdminApplicationDto | CreatedApplicationDto | false | undefined;
4
+ /**
5
+ * Everything you can do to an application: `create` / `edit` (a `FormSlideover`), `remove`, `toggle`, `rotateSecret`,
6
+ * `signOutEveryone`, `copyClientId` and `menu(application)` for the row menu. Toasts, confirmations and the refresh
7
+ * of the admin list are handled here.
8
+ */
9
+ export declare function useOAuthApplicationActions(): {
10
+ busy: any;
11
+ create: () => Promise<SavedApplication>;
12
+ edit: (application: AdminApplicationDto) => Promise<SavedApplication>;
13
+ remove: (application: AdminApplicationDto) => Promise<any>;
14
+ toggle: (application: AdminApplicationDto) => Promise<void>;
15
+ rotateSecret: (application: AdminApplicationDto) => Promise<void>;
16
+ signOutEveryone: (application: AdminApplicationDto) => Promise<void>;
17
+ copyClientId: (application: AdminApplicationDto) => any;
18
+ menu: (application: AdminApplicationDto) => DropdownMenuItem[][];
19
+ };
20
+ export {};
@@ -0,0 +1,123 @@
1
+ import OAuthApplicationSlideover from "../components/OAuthApplicationSlideover.vue";
2
+ import OAuthCredentialsModal from "../components/OAuthCredentialsModal.vue";
3
+ import { OAUTH_APPLICATIONS_KEY, oauthApplicationsApi } from "./useOAuthApplications.js";
4
+ export function useOAuthApplicationActions() {
5
+ const t = useModuleT("oauthServer");
6
+ const overlay = useOverlay();
7
+ const confirm = useConfirmDialog();
8
+ const toast = useToast();
9
+ const { busy, run } = useAction();
10
+ const { copy } = useCopyToClipboard();
11
+ const refresh = () => refreshNuxtData(OAUTH_APPLICATIONS_KEY);
12
+ function showCredentials(credentials) {
13
+ overlay.create(OAuthCredentialsModal, { destroyOnClose: true, props: credentials }).open();
14
+ }
15
+ async function create() {
16
+ const saved = await overlay.create(OAuthApplicationSlideover, {
17
+ destroyOnClose: true,
18
+ props: {
19
+ save: async (input) => {
20
+ const created = await oauthApplicationsApi.create(input);
21
+ await refresh();
22
+ return created;
23
+ }
24
+ }
25
+ }).open();
26
+ if (saved && "clientSecret" in saved) {
27
+ toast.add({ title: t("admin.created", "Application created"), description: saved.name, color: "success", icon: "i-lucide-check" });
28
+ showCredentials({ name: saved.name, clientId: saved.clientId, clientSecret: saved.clientSecret });
29
+ }
30
+ return saved;
31
+ }
32
+ async function edit(application) {
33
+ const saved = await overlay.create(OAuthApplicationSlideover, {
34
+ destroyOnClose: true,
35
+ props: {
36
+ application,
37
+ save: async (input) => {
38
+ const updated = await oauthApplicationsApi.update(application.id, input);
39
+ await refresh();
40
+ return updated;
41
+ }
42
+ }
43
+ }).open();
44
+ if (saved) toast.add({ title: t("admin.updated", "Application updated"), description: saved.name, color: "success", icon: "i-lucide-check" });
45
+ return saved;
46
+ }
47
+ async function remove(application) {
48
+ const confirmed = await confirm({
49
+ title: t("admin.deleteTitle", "Delete {name}?", { name: application.name }),
50
+ description: t("admin.deleteDescription", "Users can no longer sign in to it and every active sign-in ends. This cannot be undone."),
51
+ confirmText: t("admin.delete", "Delete application"),
52
+ destructive: true
53
+ });
54
+ if (!confirmed) return false;
55
+ const done = await run(async () => {
56
+ await oauthApplicationsApi.remove(application.id);
57
+ await refresh();
58
+ return true;
59
+ }, { success: t("admin.deleted", "Application deleted") });
60
+ return done ?? false;
61
+ }
62
+ async function toggle(application) {
63
+ await run(async () => {
64
+ await oauthApplicationsApi.update(application.id, { isActive: !application.isActive });
65
+ await refresh();
66
+ }, { success: application.isActive ? t("admin.disabledToast", "Application disabled") : t("admin.enabled", "Application enabled") });
67
+ }
68
+ async function rotateSecret(application) {
69
+ const confirmed = await confirm({
70
+ title: t("admin.rotateTitle", "Replace the client secret of {name}?", { name: application.name }),
71
+ description: t("admin.rotateDescription", "The old secret stops working and everyone is signed out of the application."),
72
+ confirmText: t("admin.newSecret", "New client secret"),
73
+ destructive: true
74
+ });
75
+ if (!confirmed) return;
76
+ const clientSecret = await run(async () => {
77
+ const secret = await oauthApplicationsApi.rotateSecret(application.id);
78
+ await refresh();
79
+ return secret;
80
+ }, { success: t("admin.secretReplaced", "Client secret replaced") });
81
+ if (clientSecret) showCredentials({ name: application.name, clientId: application.id, clientSecret });
82
+ }
83
+ async function signOutEveryone(application) {
84
+ const confirmed = await confirm({
85
+ title: t("admin.signOutTitle", "Sign everyone out of {name}?", { name: application.name }),
86
+ description: t("admin.signOutDescription", "Their sessions in the application end. They can sign in again."),
87
+ confirmText: t("admin.signOutEveryone", "Sign everyone out"),
88
+ destructive: true
89
+ });
90
+ if (!confirmed) return;
91
+ await run(async () => {
92
+ await oauthApplicationsApi.revokeGrants(application.id);
93
+ await refresh();
94
+ }, { success: t("admin.signedOut", "Everyone signed out") });
95
+ }
96
+ const copyClientId = (application) => copy(application.id, t("credentials.copiedClientId", "Client ID copied"));
97
+ const menu = (application) => [[
98
+ { label: t("admin.edit", "Edit application"), icon: "i-lucide-pencil", onSelect: () => {
99
+ void edit(application);
100
+ } },
101
+ { label: t("admin.copyClientId", "Copy client ID"), icon: "i-lucide-copy", onSelect: () => {
102
+ void copyClientId(application);
103
+ } },
104
+ {
105
+ label: application.isActive ? t("admin.disable", "Disable application") : t("admin.enable", "Enable application"),
106
+ icon: application.isActive ? "i-lucide-pause" : "i-lucide-play",
107
+ onSelect: () => {
108
+ void toggle(application);
109
+ }
110
+ },
111
+ { label: t("admin.newSecret", "New client secret"), icon: "i-lucide-key-round", onSelect: () => {
112
+ void rotateSecret(application);
113
+ } },
114
+ { label: t("admin.signOutEveryone", "Sign everyone out"), icon: "i-lucide-log-out", onSelect: () => {
115
+ void signOutEveryone(application);
116
+ } }
117
+ ], [
118
+ { label: t("admin.delete", "Delete application"), icon: "i-lucide-trash", color: "error", onSelect: () => {
119
+ void remove(application);
120
+ } }
121
+ ]];
122
+ return { busy, create, edit, remove, toggle, rotateSecret, signOutEveryone, copyClientId, menu };
123
+ }
@@ -1,14 +1,25 @@
1
- import type { ApplicationInput } from '../../../types.js';
1
+ import type { AdminApplicationDto, ApplicationInput, CreatedApplicationDto } from '../../../types.js';
2
+ /** The `useFetch` key of the admin list; `refreshNuxtData(OAUTH_APPLICATIONS_KEY)` reloads it from anywhere. */
3
+ export declare const OAUTH_APPLICATIONS_KEY = "oauth-server:applications";
4
+ /** The admin endpoints as plain calls (no refresh). Every call needs the host's `oauth:authorize` hook to allow the user. */
5
+ export declare const oauthApplicationsApi: {
6
+ create: (input: ApplicationInput) => Promise<CreatedApplicationDto>;
7
+ update: (id: string, patch: Partial<ApplicationInput>) => Promise<AdminApplicationDto>;
8
+ remove: (id: string) => Promise<unknown>;
9
+ /** Returns the new client secret; the old one stops working. */
10
+ rotateSecret: (id: string) => Promise<string>;
11
+ revokeGrants: (id: string) => Promise<unknown>;
12
+ };
2
13
  /** Admin CRUD for applications. Every call needs the host's `oauth:authorize` hook to allow the user. */
3
14
  export declare function useOAuthApplications(): {
4
15
  applications: any;
5
16
  status: any;
6
17
  error: any;
7
18
  refresh: any;
8
- create: (input: ApplicationInput) => Promise<any>;
9
- update: (id: string, patch: Partial<ApplicationInput>) => Promise<any>;
19
+ create: (input: ApplicationInput) => Promise<CreatedApplicationDto>;
20
+ update: (id: string, patch: Partial<ApplicationInput>) => Promise<AdminApplicationDto>;
10
21
  remove: (id: string) => Promise<void>;
11
- rotateSecret: (id: string) => Promise<any>;
22
+ rotateSecret: (id: string) => Promise<string>;
12
23
  revokeGrants: (id: string) => Promise<void>;
13
24
  };
14
25
  /** The applications the signed-in user may sign in to. */
@@ -1,30 +1,39 @@
1
1
  const BASE = "/api/oauth-server";
2
+ export const OAUTH_APPLICATIONS_KEY = "oauth-server:applications";
3
+ export const oauthApplicationsApi = {
4
+ create: (input) => $fetch(`${BASE}/admin/applications`, { method: "POST", body: input }),
5
+ update: (id, patch) => $fetch(`${BASE}/admin/applications/${id}`, { method: "PATCH", body: patch }),
6
+ remove: (id) => $fetch(`${BASE}/admin/applications/${id}`, { method: "DELETE" }),
7
+ /** Returns the new client secret; the old one stops working. */
8
+ rotateSecret: async (id) => (await $fetch(`${BASE}/admin/applications/${id}/secret`, { method: "POST" })).clientSecret,
9
+ revokeGrants: (id) => $fetch(`${BASE}/admin/applications/${id}/revoke`, { method: "POST" })
10
+ };
2
11
  export function useOAuthApplications() {
3
12
  const { data, status, error, refresh } = useFetch(`${BASE}/admin/applications`, {
4
- key: "oauth-server:applications",
13
+ key: OAUTH_APPLICATIONS_KEY,
5
14
  default: () => []
6
15
  });
7
16
  async function create(input) {
8
- const created = await $fetch(`${BASE}/admin/applications`, { method: "POST", body: input });
17
+ const created = await oauthApplicationsApi.create(input);
9
18
  await refresh();
10
19
  return created;
11
20
  }
12
21
  async function update(id, patch) {
13
- const updated = await $fetch(`${BASE}/admin/applications/${id}`, { method: "PATCH", body: patch });
22
+ const updated = await oauthApplicationsApi.update(id, patch);
14
23
  await refresh();
15
24
  return updated;
16
25
  }
17
26
  async function remove(id) {
18
- await $fetch(`${BASE}/admin/applications/${id}`, { method: "DELETE" });
27
+ await oauthApplicationsApi.remove(id);
19
28
  await refresh();
20
29
  }
21
30
  async function rotateSecret(id) {
22
- const { clientSecret } = await $fetch(`${BASE}/admin/applications/${id}/secret`, { method: "POST" });
31
+ const clientSecret = await oauthApplicationsApi.rotateSecret(id);
23
32
  await refresh();
24
33
  return clientSecret;
25
34
  }
26
35
  async function revokeGrants(id) {
27
- await $fetch(`${BASE}/admin/applications/${id}/revoke`, { method: "POST" });
36
+ await oauthApplicationsApi.revokeGrants(id);
28
37
  await refresh();
29
38
  }
30
39
  return { applications: data, status, error, refresh, create, update, remove, rotateSecret, revokeGrants };