zudoku 0.66.2 → 0.66.3
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/config/validators/validate.d.ts +32 -3
- package/dist/config/validators/validate.js +1 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/flat-config.d.ts +1 -1
- package/dist/lib/auth/issuer.js +1 -1
- package/dist/lib/auth/issuer.js.map +1 -1
- package/dist/lib/authentication/authentication.d.ts +3 -2
- package/dist/lib/authentication/hook.d.ts +2 -0
- package/dist/lib/authentication/hook.js +10 -0
- package/dist/lib/authentication/hook.js.map +1 -1
- package/dist/lib/authentication/providers/firebase.js +67 -9
- package/dist/lib/authentication/providers/firebase.js.map +1 -1
- package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
- package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
- package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
- package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
- package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
- package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
- package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
- package/dist/lib/components/index.d.ts +2 -0
- package/dist/lib/errors/ErrorMessage.d.ts +3 -0
- package/dist/lib/errors/ErrorMessage.js +16 -0
- package/dist/lib/errors/ErrorMessage.js.map +1 -0
- package/dist/lib/hooks/index.d.ts +2 -0
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.d.ts +4 -1
- package/dist/lib/plugins/api-keys/index.js +19 -14
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
- package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
- package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
- package/dist/vite/config.js +7 -0
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/plugin-api-keys.js +4 -1
- package/dist/vite/plugin-api-keys.js.map +1 -1
- package/dist/vite/zuplo.d.ts +13 -0
- package/dist/vite/zuplo.js +15 -0
- package/dist/vite/zuplo.js.map +1 -0
- package/lib/{ClaudeLogo-CGRfGTk2.js → ClaudeLogo-BZslN9XF.js} +3 -3
- package/lib/{ClaudeLogo-CGRfGTk2.js.map → ClaudeLogo-BZslN9XF.js.map} +1 -1
- package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
- package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
- package/lib/Frame-DxlznfVd.js +205 -0
- package/lib/Frame-DxlznfVd.js.map +1 -0
- package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
- package/lib/Input-D-kqEQ5M.js.map +1 -0
- package/lib/{MdxPage-Bjf72BP3.js → MdxPage-B4zZq5aR.js} +8 -8
- package/lib/{MdxPage-Bjf72BP3.js.map → MdxPage-B4zZq5aR.js.map} +1 -1
- package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
- package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
- package/lib/{OAuthErrorPage-1Ekji0PK.js → OAuthErrorPage-DRY2hlga.js} +20 -21
- package/lib/{OAuthErrorPage-1Ekji0PK.js.map → OAuthErrorPage-DRY2hlga.js.map} +1 -1
- package/lib/{OasProvider-BZxmTyMM.js → OasProvider-lMwTD76Y.js} +4 -4
- package/lib/{OasProvider-BZxmTyMM.js.map → OasProvider-lMwTD76Y.js.map} +1 -1
- package/lib/{OperationList-B7nPIFB8.js → OperationList-Bm76b4vl.js} +23 -24
- package/lib/{OperationList-B7nPIFB8.js.map → OperationList-Bm76b4vl.js.map} +1 -1
- package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-DGc32XJV.js} +4 -4
- package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-DGc32XJV.js.map} +1 -1
- package/lib/{SchemaList-16_obkku.js → SchemaList-DX4FPogg.js} +7 -7
- package/lib/{SchemaList-16_obkku.js.map → SchemaList-DX4FPogg.js.map} +1 -1
- package/lib/SchemaView-CjXvSRxy.js +434 -0
- package/lib/SchemaView-CjXvSRxy.js.map +1 -0
- package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
- package/lib/Secret-BxGpIhDP.js.map +1 -0
- package/lib/{SignUp-D54_QWFy.js → SignUp-CntxjFGS.js} +4 -4
- package/lib/{SignUp-D54_QWFy.js.map → SignUp-CntxjFGS.js.map} +1 -1
- package/lib/{SyntaxHighlight-j_HRSPCU.js → SyntaxHighlight-Dgd0AaaX.js} +2 -2
- package/lib/{SyntaxHighlight-j_HRSPCU.js.map → SyntaxHighlight-Dgd0AaaX.js.map} +1 -1
- package/lib/{Toc-z05x698-.js → Toc-L1vGGHA1.js} +2 -2
- package/lib/{Toc-z05x698-.js.map → Toc-L1vGGHA1.js.map} +1 -1
- package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
- package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
- package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
- package/lib/{circular-D5sYCIWL.js → circular-BIN_WQ0c.js} +2 -2
- package/lib/{circular-D5sYCIWL.js.map → circular-BIN_WQ0c.js.map} +1 -1
- package/lib/{createServer-BlwU7lIr.js → createServer-BEl12QFw.js} +4 -4
- package/lib/{createServer-BlwU7lIr.js.map → createServer-BEl12QFw.js.map} +1 -1
- package/lib/createVariantComponent-CQVt-H3r.js +18 -0
- package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
- package/lib/{errors-BtC4Kn2j.js → errors-CtBbD47A.js} +2 -2
- package/lib/{errors-BtC4Kn2j.js.map → errors-CtBbD47A.js.map} +1 -1
- package/lib/{firebase-Ibm_tv3G.js → firebase-CT38ugg4.js} +1588 -1342
- package/lib/firebase-CT38ugg4.js.map +1 -0
- package/lib/hook-CHw_R_xu.js +52 -0
- package/lib/hook-CHw_R_xu.js.map +1 -0
- package/lib/{ErrorAlert-BUlG32M9.js → index-CG2v-T7-.js} +5373 -4335
- package/lib/index-CG2v-T7-.js.map +1 -0
- package/lib/{index-eKVhlB94.js → index-CISwSpdT.js} +2 -2
- package/lib/{index-eKVhlB94.js.map → index-CISwSpdT.js.map} +1 -1
- package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
- package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
- package/lib/{index-CeVTNcfF.js → index-jI2Fjpy-.js} +98 -99
- package/lib/{index-CeVTNcfF.js.map → index-jI2Fjpy-.js.map} +1 -1
- package/lib/{index.esm-BoKBnRoT.js → index.esm-Bu35TNgg.js} +16 -14
- package/lib/index.esm-Bu35TNgg.js.map +1 -0
- package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
- package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
- package/lib/ui/Drawer.js +2 -2
- package/lib/ui/SyntaxHighlight.js +2 -2
- package/lib/zudoku.__internal.js +507 -479
- package/lib/zudoku.__internal.js.map +1 -1
- package/lib/zudoku.auth-auth0.js +1 -1
- package/lib/zudoku.auth-azureb2c.js +4 -4
- package/lib/zudoku.auth-clerk.js +2 -2
- package/lib/zudoku.auth-firebase.js +6 -5
- package/lib/zudoku.auth-firebase.js.map +1 -1
- package/lib/zudoku.auth-openid.js +4 -4
- package/lib/zudoku.auth-supabase.js +5 -5
- package/lib/zudoku.components.js +20 -21
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +3 -3
- package/lib/zudoku.mermaid.js +3 -3
- package/lib/zudoku.plugin-api-catalog.js +8 -9
- package/lib/zudoku.plugin-api-catalog.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +580 -543
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-pages.js +1 -1
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +3 -3
- package/lib/zudoku.plugin-redirect.js +1 -1
- package/lib/zudoku.plugin-search-pagefind.js +3 -3
- package/lib/zudoku.router.js +2 -2
- package/package.json +4 -2
- package/src/lib/auth/issuer.ts +1 -1
- package/src/lib/authentication/authentication.ts +8 -2
- package/src/lib/authentication/hook.ts +16 -0
- package/src/lib/authentication/providers/firebase.tsx +98 -6
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
- package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
- package/src/lib/errors/ErrorMessage.tsx +38 -0
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
- package/src/lib/plugins/api-keys/index.tsx +33 -18
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
- package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
- package/lib/ErrorAlert-BUlG32M9.js.map +0 -1
- package/lib/RouterError-DfTZblpv.js +0 -42
- package/lib/RouterError-DfTZblpv.js.map +0 -1
- package/lib/SchemaView-eyvR4bRt.js +0 -597
- package/lib/SchemaView-eyvR4bRt.js.map +0 -1
- package/lib/Select-CkxXP5I7.js.map +0 -1
- package/lib/createVariantComponent-B9_dVBvu.js +0 -35
- package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
- package/lib/firebase-Ibm_tv3G.js.map +0 -1
- package/lib/hook-BNxidGQq.js +0 -40
- package/lib/hook-BNxidGQq.js.map +0 -1
- package/lib/index-DSOi7zVM.js +0 -1059
- package/lib/index-DSOi7zVM.js.map +0 -1
- package/lib/index.esm-BoKBnRoT.js.map +0 -1
- package/lib/useMutation-C6RqWmTS.js.map +0 -1
|
@@ -1,181 +1,26 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
3
|
+
import { Button } from "zudoku/components";
|
|
4
|
+
import { useAuth } from "zudoku/hooks";
|
|
1
5
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CircleSlashIcon,
|
|
9
|
-
PencilLineIcon,
|
|
10
|
-
RefreshCwIcon,
|
|
11
|
-
TrashIcon,
|
|
12
|
-
XIcon,
|
|
13
|
-
} from "lucide-react";
|
|
14
|
-
import { AnimatePresence } from "motion/react";
|
|
15
|
-
import React, { useState } from "react";
|
|
16
|
-
import { Alert, AlertTitle } from "zudoku/ui/Alert.js";
|
|
17
|
-
import { Card, CardHeader } from "zudoku/ui/Card.js";
|
|
18
|
-
import {
|
|
19
|
-
Dialog,
|
|
20
|
-
DialogClose,
|
|
21
|
-
DialogContent,
|
|
22
|
-
DialogDescription,
|
|
23
|
-
DialogFooter,
|
|
24
|
-
DialogHeader,
|
|
25
|
-
DialogTitle,
|
|
26
|
-
DialogTrigger,
|
|
27
|
-
} from "zudoku/ui/Dialog.js";
|
|
28
|
-
import { Secret } from "zudoku/ui/Secret.js";
|
|
29
|
-
import { useZudoku } from "../../components/context/ZudokuContext.js";
|
|
6
|
+
Item,
|
|
7
|
+
ItemActions,
|
|
8
|
+
ItemContent,
|
|
9
|
+
ItemDescription,
|
|
10
|
+
ItemTitle,
|
|
11
|
+
} from "zudoku/ui/Item.js";
|
|
30
12
|
import { Slot } from "../../components/Slot.js";
|
|
31
|
-
import {
|
|
32
|
-
import { Input } from "../../ui/Input.js";
|
|
33
|
-
import { cn } from "../../util/cn.js";
|
|
13
|
+
import { ErrorMessage } from "../../errors/ErrorMessage.js";
|
|
34
14
|
import { CreateApiKeyDialog } from "./CreateApiKeyDialog.js";
|
|
35
|
-
import type {
|
|
15
|
+
import type { ApiKeyService } from "./index.js";
|
|
16
|
+
import { ApiKeyList } from "./settings/ApiKeyList.js";
|
|
36
17
|
|
|
37
18
|
export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
38
|
-
const context = useZudoku();
|
|
39
|
-
const queryClient = useQueryClient();
|
|
40
|
-
const [editingConsumerId, setEditingConsumerId] = useState<string | null>(
|
|
41
|
-
null,
|
|
42
|
-
);
|
|
43
|
-
const [editingLabel, setEditingLabel] = useState<string>("");
|
|
44
|
-
const { data, isFetching } = useSuspenseQuery({
|
|
45
|
-
queryFn: () => service.getConsumers(context),
|
|
46
|
-
queryKey: ["api-keys"],
|
|
47
|
-
retry: false,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
19
|
const [isCreateApiKeyOpen, setIsCreateApiKeyOpen] = useState(false);
|
|
51
|
-
|
|
52
|
-
const deleteKeyMutation = useMutation({
|
|
53
|
-
mutationFn: ({
|
|
54
|
-
consumerId,
|
|
55
|
-
keyId,
|
|
56
|
-
}: {
|
|
57
|
-
consumerId: string;
|
|
58
|
-
keyId: string;
|
|
59
|
-
}) => {
|
|
60
|
-
if (!service.deleteKey) {
|
|
61
|
-
throw new Error("deleteKey not implemented");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return service.deleteKey(consumerId, keyId, context);
|
|
65
|
-
},
|
|
66
|
-
onMutate: async ({ consumerId, keyId }) => {
|
|
67
|
-
await queryClient.cancelQueries({ queryKey: ["api-keys"] });
|
|
68
|
-
const previousData = queryClient.getQueryData<ApiConsumer[]>([
|
|
69
|
-
"api-keys",
|
|
70
|
-
]);
|
|
71
|
-
queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
|
|
72
|
-
if (!old) {
|
|
73
|
-
return old;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return old.map((consumer) => {
|
|
77
|
-
if (consumer.id === consumerId) {
|
|
78
|
-
return {
|
|
79
|
-
...consumer,
|
|
80
|
-
apiKeys: consumer.apiKeys.filter((key) => key.id !== keyId),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
return consumer;
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
return { previousData };
|
|
88
|
-
},
|
|
89
|
-
onError: (_err, _variables, context) => {
|
|
90
|
-
if (context?.previousData) {
|
|
91
|
-
queryClient.setQueryData(["api-keys"], context.previousData);
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
onSuccess: () => {
|
|
95
|
-
void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const updateConsumerMutation = useMutation({
|
|
100
|
-
mutationFn: ({
|
|
101
|
-
consumerId,
|
|
102
|
-
label,
|
|
103
|
-
}: {
|
|
104
|
-
consumerId: string;
|
|
105
|
-
label: string;
|
|
106
|
-
}) => {
|
|
107
|
-
if (!service.updateConsumer) {
|
|
108
|
-
throw new Error("updateConsumer not implemented");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return service.updateConsumer({ id: consumerId, label }, context);
|
|
112
|
-
},
|
|
113
|
-
onMutate: async ({ consumerId, label }) => {
|
|
114
|
-
await queryClient.cancelQueries({ queryKey: ["api-keys"] });
|
|
115
|
-
|
|
116
|
-
const previousData = queryClient.getQueryData(["api-keys"]);
|
|
117
|
-
queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
|
|
118
|
-
if (!old) {
|
|
119
|
-
return old;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return old.map((consumer) => {
|
|
123
|
-
if (consumer.id === consumerId) {
|
|
124
|
-
return {
|
|
125
|
-
...consumer,
|
|
126
|
-
label,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
return consumer;
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return { previousData };
|
|
134
|
-
},
|
|
135
|
-
onError: (_err, _variables, context) => {
|
|
136
|
-
if (context?.previousData) {
|
|
137
|
-
queryClient.setQueryData(["api-keys"], context.previousData);
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
onSuccess: () => {
|
|
141
|
-
void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const rollKeyMutation = useMutation({
|
|
146
|
-
mutationFn: (id: string) => {
|
|
147
|
-
if (!service.rollKey) {
|
|
148
|
-
throw new Error("rollKey not implemented");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return service.rollKey(id, context);
|
|
152
|
-
},
|
|
153
|
-
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["api-keys"] }),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const handleStartEdit = (consumerId: string, currentLabel: string) => {
|
|
157
|
-
setEditingConsumerId(consumerId);
|
|
158
|
-
setEditingLabel(currentLabel);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const handleSaveEdit = (consumerId: string) => {
|
|
162
|
-
if (editingLabel.trim()) {
|
|
163
|
-
updateConsumerMutation.mutate({
|
|
164
|
-
consumerId,
|
|
165
|
-
label: editingLabel.trim(),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
setEditingConsumerId(null);
|
|
169
|
-
setEditingLabel("");
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const handleCancelEdit = () => {
|
|
173
|
-
setEditingConsumerId(null);
|
|
174
|
-
setEditingLabel("");
|
|
175
|
-
};
|
|
20
|
+
const auth = useAuth();
|
|
176
21
|
|
|
177
22
|
return (
|
|
178
|
-
<div className="max-w-
|
|
23
|
+
<div className="max-w-3xl h-full pt-(--padding-content-top) pb-(--padding-content-bottom)">
|
|
179
24
|
<Slot.Target name="api-keys-list-page" />
|
|
180
25
|
|
|
181
26
|
<div className="flex justify-between pb-3">
|
|
@@ -192,313 +37,28 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
192
37
|
<p>Create, manage, and monitor your API keys</p>
|
|
193
38
|
|
|
194
39
|
<Slot.Target name="api-keys-list-page-before-keys" />
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
40
|
+
{auth.profile?.emailVerified === false ? (
|
|
41
|
+
<Item variant="outline">
|
|
42
|
+
<ItemContent>
|
|
43
|
+
<ItemTitle>Verified email required</ItemTitle>
|
|
44
|
+
<ItemDescription>
|
|
45
|
+
You need to verify your email to access API keys.
|
|
46
|
+
</ItemDescription>
|
|
47
|
+
</ItemContent>
|
|
48
|
+
|
|
49
|
+
<ItemActions>
|
|
50
|
+
<Button onClick={() => auth.requestEmailVerification()}>
|
|
51
|
+
Verify email
|
|
52
|
+
</Button>
|
|
53
|
+
</ItemActions>
|
|
54
|
+
</Item>
|
|
55
|
+
) : (
|
|
56
|
+
<ErrorBoundary
|
|
57
|
+
fallbackRender={({ error }) => <ErrorMessage error={error} />}
|
|
58
|
+
>
|
|
59
|
+
<ApiKeyList service={service} />
|
|
60
|
+
</ErrorBoundary>
|
|
207
61
|
)}
|
|
208
|
-
{deleteKeyMutation.isError && (
|
|
209
|
-
<Alert variant="destructive" className="mb-4">
|
|
210
|
-
<CircleSlashIcon size={16} />
|
|
211
|
-
<AlertTitle>{deleteKeyMutation.error.message}</AlertTitle>
|
|
212
|
-
</Alert>
|
|
213
|
-
)}
|
|
214
|
-
<div className="">
|
|
215
|
-
{data.length === 0 ? (
|
|
216
|
-
<div className="flex col-span-full flex-col justify-center gap-4 items-center p-8 border rounded-sm bg-muted/30 text-muted-foreground">
|
|
217
|
-
<p className="text-center">
|
|
218
|
-
You have no API keys yet.
|
|
219
|
-
<br />
|
|
220
|
-
{service.createKey && "Get started and create your first key."}
|
|
221
|
-
</p>
|
|
222
|
-
{service.createKey && (
|
|
223
|
-
<CreateApiKeyDialog
|
|
224
|
-
service={service}
|
|
225
|
-
isOpen={isCreateApiKeyOpen}
|
|
226
|
-
onOpenChange={setIsCreateApiKeyOpen}
|
|
227
|
-
/>
|
|
228
|
-
)}
|
|
229
|
-
</div>
|
|
230
|
-
) : (
|
|
231
|
-
<ul
|
|
232
|
-
className={cn(
|
|
233
|
-
"grid grid-cols-[1fr_min-content] divide-y divide-border col-span-6",
|
|
234
|
-
)}
|
|
235
|
-
>
|
|
236
|
-
{data.map((consumers) => (
|
|
237
|
-
<Card
|
|
238
|
-
className="grid grid-cols-subgrid col-span-full items-center mb-4 group"
|
|
239
|
-
key={consumers.id}
|
|
240
|
-
>
|
|
241
|
-
<CardHeader className="border-b col-span-full grid-cols-subgrid grid">
|
|
242
|
-
<div className="h-10 flex flex-col text-sm justify-center">
|
|
243
|
-
<div className="font-medium text-lg flex items-center gap-2">
|
|
244
|
-
{editingConsumerId === consumers.id ? (
|
|
245
|
-
<div className="flex items-center gap-2 w-full">
|
|
246
|
-
<Input
|
|
247
|
-
maxLength={32}
|
|
248
|
-
value={editingLabel}
|
|
249
|
-
onChange={(e) => setEditingLabel(e.target.value)}
|
|
250
|
-
onKeyDown={(e) => {
|
|
251
|
-
if (e.key === "Enter") {
|
|
252
|
-
handleSaveEdit(consumers.id);
|
|
253
|
-
} else if (e.key === "Escape") {
|
|
254
|
-
handleCancelEdit();
|
|
255
|
-
}
|
|
256
|
-
}}
|
|
257
|
-
className="text-lg font-medium"
|
|
258
|
-
autoFocus
|
|
259
|
-
/>
|
|
260
|
-
<div className="flex items-center">
|
|
261
|
-
<Button
|
|
262
|
-
size="icon"
|
|
263
|
-
variant="ghost"
|
|
264
|
-
onClick={() => handleSaveEdit(consumers.id)}
|
|
265
|
-
disabled={!editingLabel.trim()}
|
|
266
|
-
>
|
|
267
|
-
<CheckIcon size={16} />
|
|
268
|
-
</Button>
|
|
269
|
-
<Button
|
|
270
|
-
size="icon"
|
|
271
|
-
variant="ghost"
|
|
272
|
-
onClick={handleCancelEdit}
|
|
273
|
-
>
|
|
274
|
-
<XIcon size={16} />
|
|
275
|
-
</Button>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
) : (
|
|
279
|
-
consumers.label
|
|
280
|
-
)}
|
|
281
|
-
<div className="text-muted-foreground text-xs">
|
|
282
|
-
{consumers.createdOn}
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
<div className="text-muted-foreground text-xs">
|
|
286
|
-
{consumers.createdOn && (
|
|
287
|
-
<div>
|
|
288
|
-
Created on{" "}
|
|
289
|
-
{new Date(consumers.createdOn).toLocaleDateString()}
|
|
290
|
-
</div>
|
|
291
|
-
)}
|
|
292
|
-
{consumers.expiresOn && (
|
|
293
|
-
<div>
|
|
294
|
-
Expires on{" "}
|
|
295
|
-
{new Date(consumers.expiresOn).toLocaleDateString()}
|
|
296
|
-
</div>
|
|
297
|
-
)}
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
<div className="flex justify-end">
|
|
302
|
-
{service.updateConsumer && (
|
|
303
|
-
<Button
|
|
304
|
-
variant="ghost"
|
|
305
|
-
onClick={() =>
|
|
306
|
-
handleStartEdit(consumers.id, consumers.label)
|
|
307
|
-
}
|
|
308
|
-
className={cn(
|
|
309
|
-
"flex gap-2",
|
|
310
|
-
editingConsumerId === consumers.id &&
|
|
311
|
-
"opacity-0! pointer-events-none",
|
|
312
|
-
)}
|
|
313
|
-
disabled={editingConsumerId === consumers.id}
|
|
314
|
-
>
|
|
315
|
-
<PencilLineIcon size={16} />
|
|
316
|
-
<span className="hidden md:block">Edit label</span>
|
|
317
|
-
</Button>
|
|
318
|
-
)}
|
|
319
|
-
{service.rollKey && (
|
|
320
|
-
<Dialog>
|
|
321
|
-
<DialogTrigger asChild>
|
|
322
|
-
<Button
|
|
323
|
-
title="Roll this key"
|
|
324
|
-
variant="ghost"
|
|
325
|
-
disabled={rollKeyMutation.isPending}
|
|
326
|
-
className="flex items-center gap-2"
|
|
327
|
-
>
|
|
328
|
-
<RefreshCwIcon
|
|
329
|
-
size={16}
|
|
330
|
-
className={
|
|
331
|
-
rollKeyMutation.isPending
|
|
332
|
-
? "animate-spin"
|
|
333
|
-
: undefined
|
|
334
|
-
}
|
|
335
|
-
/>
|
|
336
|
-
<span className="hidden md:block">Roll key</span>
|
|
337
|
-
</Button>
|
|
338
|
-
</DialogTrigger>
|
|
339
|
-
<DialogContent>
|
|
340
|
-
<DialogHeader>
|
|
341
|
-
<DialogTitle>Roll API Key</DialogTitle>
|
|
342
|
-
<DialogDescription>
|
|
343
|
-
Are you sure you want to roll this API key?
|
|
344
|
-
</DialogDescription>
|
|
345
|
-
</DialogHeader>
|
|
346
|
-
<DialogFooter>
|
|
347
|
-
<DialogClose asChild>
|
|
348
|
-
<Button variant="outline">Cancel</Button>
|
|
349
|
-
</DialogClose>
|
|
350
|
-
<DialogClose asChild>
|
|
351
|
-
<Button
|
|
352
|
-
onClick={() => {
|
|
353
|
-
rollKeyMutation.mutate(consumers.id);
|
|
354
|
-
}}
|
|
355
|
-
>
|
|
356
|
-
Roll Key
|
|
357
|
-
</Button>
|
|
358
|
-
</DialogClose>
|
|
359
|
-
</DialogFooter>
|
|
360
|
-
</DialogContent>
|
|
361
|
-
</Dialog>
|
|
362
|
-
)}
|
|
363
|
-
</div>
|
|
364
|
-
</CardHeader>
|
|
365
|
-
<div className="col-span-full grid-cols-subgrid grid">
|
|
366
|
-
<AnimatePresence>
|
|
367
|
-
{consumers.apiKeys.map((apiKey) => (
|
|
368
|
-
<React.Fragment key={apiKey.id}>
|
|
369
|
-
<RevealApiKey
|
|
370
|
-
apiKey={apiKey}
|
|
371
|
-
onDeleteKey={() => {
|
|
372
|
-
deleteKeyMutation.mutate({
|
|
373
|
-
consumerId: consumers.id,
|
|
374
|
-
keyId: apiKey.id,
|
|
375
|
-
});
|
|
376
|
-
}}
|
|
377
|
-
className={
|
|
378
|
-
deleteKeyMutation.variables?.keyId === apiKey.id &&
|
|
379
|
-
(deleteKeyMutation.isPending || isFetching)
|
|
380
|
-
? "opacity-10!"
|
|
381
|
-
: undefined
|
|
382
|
-
}
|
|
383
|
-
/>
|
|
384
|
-
<div className="col-span-full h-px bg-border"></div>
|
|
385
|
-
</React.Fragment>
|
|
386
|
-
))}
|
|
387
|
-
</AnimatePresence>
|
|
388
|
-
</div>
|
|
389
|
-
</Card>
|
|
390
|
-
))}
|
|
391
|
-
</ul>
|
|
392
|
-
)}
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const getTimeAgo = (date: string) => {
|
|
399
|
-
const now = new Date();
|
|
400
|
-
const created = new Date(date);
|
|
401
|
-
const diffInSeconds = Math.floor((now.getTime() - created.getTime()) / 1000);
|
|
402
|
-
|
|
403
|
-
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
404
|
-
|
|
405
|
-
if (diffInSeconds < 60) return rtf.format(-diffInSeconds, "second");
|
|
406
|
-
if (diffInSeconds < 3600)
|
|
407
|
-
return rtf.format(-Math.floor(diffInSeconds / 60), "minute");
|
|
408
|
-
if (diffInSeconds < 86400)
|
|
409
|
-
return rtf.format(-Math.floor(diffInSeconds / 3600), "hour");
|
|
410
|
-
if (diffInSeconds < 2592000)
|
|
411
|
-
return rtf.format(-Math.floor(diffInSeconds / 86400), "day");
|
|
412
|
-
if (diffInSeconds < 31536000)
|
|
413
|
-
return rtf.format(-Math.floor(diffInSeconds / 2592000), "month");
|
|
414
|
-
return rtf.format(-Math.floor(diffInSeconds / 31536000), "year");
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
const RevealApiKey = ({
|
|
418
|
-
apiKey,
|
|
419
|
-
onDeleteKey,
|
|
420
|
-
className,
|
|
421
|
-
}: {
|
|
422
|
-
apiKey: ApiKey;
|
|
423
|
-
onDeleteKey: () => void;
|
|
424
|
-
className?: string;
|
|
425
|
-
}) => {
|
|
426
|
-
const [revealed, setRevealed] = useState(false);
|
|
427
|
-
|
|
428
|
-
const { key, createdOn, expiresOn } = apiKey;
|
|
429
|
-
const isExpired = expiresOn && new Date(expiresOn) < new Date();
|
|
430
|
-
const daysUntilExpiry = expiresOn
|
|
431
|
-
? Math.ceil(
|
|
432
|
-
(new Date(expiresOn).getTime() - Date.now()) / (1000 * 60 * 60 * 24),
|
|
433
|
-
)
|
|
434
|
-
: Infinity;
|
|
435
|
-
const expiresSoon = daysUntilExpiry <= 7 && !isExpired;
|
|
436
|
-
|
|
437
|
-
return (
|
|
438
|
-
<div className={cn("grid col-span-full grid-cols-subgrid p-6", className)}>
|
|
439
|
-
<div className="flex flex-col gap-1">
|
|
440
|
-
<Secret
|
|
441
|
-
className="max-w-fit w-full"
|
|
442
|
-
secret={key}
|
|
443
|
-
status={isExpired ? "expired" : expiresSoon ? "expiring" : "active"}
|
|
444
|
-
revealed={revealed}
|
|
445
|
-
onReveal={setRevealed}
|
|
446
|
-
/>
|
|
447
|
-
<div className="flex gap-1 mt-0.5 text-nowrap">
|
|
448
|
-
{createdOn && (
|
|
449
|
-
<span className="text-xs text-muted-foreground">
|
|
450
|
-
Created {getTimeAgo(createdOn)}.
|
|
451
|
-
</span>
|
|
452
|
-
)}{" "}
|
|
453
|
-
{expiresOn && expiresSoon && (
|
|
454
|
-
<span className="text-xs text-primary">
|
|
455
|
-
Expires in {daysUntilExpiry}{" "}
|
|
456
|
-
{daysUntilExpiry === 1 ? "day" : "days"}.
|
|
457
|
-
</span>
|
|
458
|
-
)}
|
|
459
|
-
{expiresOn && isExpired && (
|
|
460
|
-
<span className="text-xs text-primary">
|
|
461
|
-
Expired{" "}
|
|
462
|
-
{daysUntilExpiry === 0
|
|
463
|
-
? "today."
|
|
464
|
-
: `${daysUntilExpiry * -1} days ago.`}
|
|
465
|
-
</span>
|
|
466
|
-
)}
|
|
467
|
-
</div>
|
|
468
|
-
</div>
|
|
469
|
-
<div className="flex justify-end">
|
|
470
|
-
{expiresOn && onDeleteKey && (
|
|
471
|
-
<Dialog>
|
|
472
|
-
<DialogTrigger asChild>
|
|
473
|
-
<Button variant="ghost" size="icon">
|
|
474
|
-
<TrashIcon size={16} />
|
|
475
|
-
</Button>
|
|
476
|
-
</DialogTrigger>
|
|
477
|
-
<DialogContent>
|
|
478
|
-
<DialogHeader>
|
|
479
|
-
<DialogTitle>Delete API Key</DialogTitle>
|
|
480
|
-
<DialogDescription>
|
|
481
|
-
Are you sure you want to delete this API key?
|
|
482
|
-
</DialogDescription>
|
|
483
|
-
</DialogHeader>
|
|
484
|
-
<DialogFooter>
|
|
485
|
-
<DialogClose asChild>
|
|
486
|
-
<Button variant="outline">Cancel</Button>
|
|
487
|
-
</DialogClose>
|
|
488
|
-
<DialogClose asChild>
|
|
489
|
-
<Button
|
|
490
|
-
onClick={() => {
|
|
491
|
-
onDeleteKey();
|
|
492
|
-
}}
|
|
493
|
-
>
|
|
494
|
-
Delete
|
|
495
|
-
</Button>
|
|
496
|
-
</DialogClose>
|
|
497
|
-
</DialogFooter>
|
|
498
|
-
</DialogContent>
|
|
499
|
-
</Dialog>
|
|
500
|
-
)}
|
|
501
|
-
</div>
|
|
502
62
|
</div>
|
|
503
63
|
);
|
|
504
64
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { KeyRoundIcon } from "lucide-react";
|
|
2
2
|
import type { RouteObject } from "react-router";
|
|
3
|
+
import type { ApiKeysOptions } from "../../../config/validators/validate.js";
|
|
3
4
|
import type { UseAuthReturn } from "../../authentication/hook.js";
|
|
4
5
|
import type {
|
|
5
6
|
ApiIdentityPlugin,
|
|
@@ -7,9 +8,7 @@ import type {
|
|
|
7
8
|
ZudokuPlugin,
|
|
8
9
|
} from "../../core/plugins.js";
|
|
9
10
|
import type { ZudokuContext } from "../../core/ZudokuContext.js";
|
|
10
|
-
import { RouterError } from "../../errors/RouterError.js";
|
|
11
11
|
import invariant from "../../util/invariant.js";
|
|
12
|
-
import { ProtectedRoute } from "./ProtectedRoute.js";
|
|
13
12
|
import { SettingsApiKeys } from "./SettingsApiKeys.js";
|
|
14
13
|
|
|
15
14
|
const DEFAULT_API_KEY_ENDPOINT = "https://api.zuploedge.com/v2/client";
|
|
@@ -154,6 +153,9 @@ const createDefaultHandler = (
|
|
|
154
153
|
id: string;
|
|
155
154
|
label?: string;
|
|
156
155
|
subject?: string;
|
|
156
|
+
createdOn?: string;
|
|
157
|
+
updatedOn?: string;
|
|
158
|
+
expiresOn?: string;
|
|
157
159
|
apiKeys: {
|
|
158
160
|
data: ApiKey[];
|
|
159
161
|
};
|
|
@@ -163,6 +165,9 @@ const createDefaultHandler = (
|
|
|
163
165
|
|
|
164
166
|
return data.data.map((consumer) => ({
|
|
165
167
|
id: consumer.id,
|
|
168
|
+
createdOn: consumer.createdOn,
|
|
169
|
+
updatedOn: consumer.updatedOn,
|
|
170
|
+
expiresOn: consumer.expiresOn,
|
|
166
171
|
label: consumer.label || consumer.subject || "API Key",
|
|
167
172
|
apiKeys: consumer.apiKeys.data,
|
|
168
173
|
key: consumer.apiKeys.data.at(0),
|
|
@@ -175,13 +180,24 @@ const createDefaultHandler = (
|
|
|
175
180
|
export const createApiKeyService = <T extends ApiKeyService>(service: T): T =>
|
|
176
181
|
service;
|
|
177
182
|
|
|
178
|
-
export const apiKeyPlugin = (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
export const apiKeyPlugin = ({
|
|
184
|
+
deploymentName,
|
|
185
|
+
...options
|
|
186
|
+
}: Omit<ApiKeysOptions, "enabled"> & {
|
|
187
|
+
deploymentName?: string;
|
|
188
|
+
}): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
|
|
189
|
+
const service = deploymentName
|
|
190
|
+
? createDefaultHandler(deploymentName, { deploymentName, ...options })
|
|
191
|
+
: options;
|
|
192
|
+
|
|
193
|
+
if (!service.getConsumers) {
|
|
194
|
+
throw new Error("getConsumers is required when using the apiKeyPlugin");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const verifiedService: ApiKeyService = {
|
|
198
|
+
...service,
|
|
199
|
+
getConsumers: service.getConsumers,
|
|
200
|
+
};
|
|
185
201
|
|
|
186
202
|
return {
|
|
187
203
|
getProfileMenuItems: () => [
|
|
@@ -195,7 +211,7 @@ export const apiKeyPlugin = (
|
|
|
195
211
|
|
|
196
212
|
getIdentities: async (context) => {
|
|
197
213
|
try {
|
|
198
|
-
const consumers = await
|
|
214
|
+
const consumers = await verifiedService.getConsumers(context);
|
|
199
215
|
|
|
200
216
|
return consumers.map((consumer) => ({
|
|
201
217
|
authorizeRequest: (request) => {
|
|
@@ -212,19 +228,18 @@ export const apiKeyPlugin = (
|
|
|
212
228
|
return [];
|
|
213
229
|
}
|
|
214
230
|
},
|
|
231
|
+
|
|
215
232
|
getRoutes: (): RouteObject[] => {
|
|
216
233
|
return [
|
|
217
234
|
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
children: [
|
|
221
|
-
{
|
|
222
|
-
path: "/settings/api-keys",
|
|
223
|
-
element: <SettingsApiKeys service={service} />,
|
|
224
|
-
},
|
|
225
|
-
],
|
|
235
|
+
path: "/settings/api-keys",
|
|
236
|
+
element: <SettingsApiKeys service={verifiedService} />,
|
|
226
237
|
},
|
|
227
238
|
];
|
|
228
239
|
},
|
|
240
|
+
|
|
241
|
+
getProtectedRoutes: () => {
|
|
242
|
+
return ["/settings/api-keys"];
|
|
243
|
+
},
|
|
229
244
|
};
|
|
230
245
|
};
|