mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Neon api keys UI improvement (#398)
This commit is contained in:
parent
05e7b5fa1b
commit
77cce58f92
@ -18,6 +18,7 @@ export const POST = createSmartRouteHandler({
|
||||
body: yupObject({
|
||||
interaction_uid: yupString().defined(),
|
||||
project_id: yupString().defined(),
|
||||
neon_project_name: yupString().optional(),
|
||||
}).defined(),
|
||||
}),
|
||||
response: yupObject({
|
||||
@ -32,7 +33,7 @@ export const POST = createSmartRouteHandler({
|
||||
const set = await prismaClient.apiKeySet.create({
|
||||
data: {
|
||||
projectId: req.body.project_id,
|
||||
description: "Auto-generated for Neon",
|
||||
description: `Auto-generated for Neon${req.body.neon_project_name ? ` (${req.body.neon_project_name})` : ""}`,
|
||||
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 100),
|
||||
superSecretAdminKey: `sak_${generateSecureRandomString()}`,
|
||||
},
|
||||
|
||||
@ -373,13 +373,13 @@ export async function createOidcProvider(options: { id: string, baseUrl: string
|
||||
if (typeof state !== 'string') {
|
||||
throwErr(`state is not a string`);
|
||||
}
|
||||
let neonProjectDisplayName: string | undefined;
|
||||
let neonProjectName: string | undefined;
|
||||
try {
|
||||
const base64Decoded = new TextDecoder().decode(decodeBase64OrBase64Url(state));
|
||||
const json = JSON.parse(base64Decoded);
|
||||
neonProjectDisplayName = json?.details?.neon_project_name;
|
||||
if (typeof neonProjectDisplayName !== 'string') {
|
||||
throwErr(`neon_project_name is not a string`, { type: typeof neonProjectDisplayName, neonProjectDisplayName });
|
||||
neonProjectName = json?.details?.neon_project_name;
|
||||
if (typeof neonProjectName !== 'string') {
|
||||
throwErr(`neon_project_name is not a string`, { type: typeof neonProjectName, neonProjectName });
|
||||
}
|
||||
} catch (e) {
|
||||
// this probably shouldn't happen, because it means Neon messed up the configuration
|
||||
@ -391,8 +391,8 @@ export async function createOidcProvider(options: { id: string, baseUrl: string
|
||||
const uid = ctx.path.split('/')[2];
|
||||
const interactionUrl = new URL(`/integrations/neon/confirm`, getEnvVariable("NEXT_PUBLIC_STACK_DASHBOARD_URL"));
|
||||
interactionUrl.searchParams.set("interaction_uid", uid);
|
||||
if (neonProjectDisplayName) {
|
||||
interactionUrl.searchParams.set("neon_project_display_name", neonProjectDisplayName);
|
||||
if (neonProjectName) {
|
||||
interactionUrl.searchParams.set("neon_project_name", neonProjectName);
|
||||
}
|
||||
return ctx.redirect(interactionUrl.toString());
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ export const POST = createSmartRouteHandler({
|
||||
|
||||
const set = await createApiKeySet({
|
||||
projectId: createdProject.id,
|
||||
description: "Auto-generated for Neon",
|
||||
description: `Auto-generated for Neon (${req.body.display_name})`,
|
||||
expires_at_millis: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 100).getTime(),
|
||||
has_publishable_client_key: false,
|
||||
has_secret_server_key: false,
|
||||
|
||||
@ -8,7 +8,7 @@ import { useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import NeonLogo from "../../../../../../public/neon.png";
|
||||
|
||||
export default function NeonConfirmCard(props: { onContinue: (options: { projectId: string }) => Promise<{ error: string } | undefined> }) {
|
||||
export default function NeonConfirmCard(props: { onContinue: (options: { projectId: string, neonProjectName?: string }) => Promise<{ error: string } | undefined> }) {
|
||||
const user = useUser({ or: "redirect", projectIdMustMatch: "internal" });
|
||||
const projects = user.useOwnedProjects();
|
||||
const searchParams = useSearchParams();
|
||||
@ -64,7 +64,7 @@ export default function NeonConfirmCard(props: { onContinue: (options: { project
|
||||
<Typography className="mb-3">
|
||||
Which projects would you like to connect?
|
||||
</Typography>
|
||||
<Input type="text" disabled prefixItem={<Image src={NeonLogo} alt="Neon" width={15} />} value={searchParams.get("neon_project_display_name") || "Neon project connected!"} />
|
||||
<Input type="text" disabled prefixItem={<Image src={NeonLogo} alt="Neon" width={15} />} value={searchParams.get("neon_project_name") || "Neon project connected!"} />
|
||||
<div className="flex flex-row items-center">
|
||||
<div className={'flex self-stretch justify-center items-center text-muted-foreground pl-3 select-none bg-muted/70 pr-3 border-r border-input rounded-l-md'}>
|
||||
<Logo noLink width={15} height={15} />
|
||||
@ -75,7 +75,7 @@ export default function NeonConfirmCard(props: { onContinue: (options: { project
|
||||
if (p === "create-new") {
|
||||
const createSearchParams = new URLSearchParams();
|
||||
createSearchParams.set("redirect_to_neon_confirm_with", searchParams.toString());
|
||||
const neonDisplayName = searchParams.get("neon_project_display_name");
|
||||
const neonDisplayName = searchParams.get("neon_project_name");
|
||||
if (neonDisplayName) {
|
||||
createSearchParams.set("display_name", neonDisplayName);
|
||||
}
|
||||
@ -120,7 +120,7 @@ export default function NeonConfirmCard(props: { onContinue: (options: { project
|
||||
<Button
|
||||
disabled={!selectedProject}
|
||||
onClick={async () => {
|
||||
const error = await props.onContinue({ projectId: selectedProject!.id });
|
||||
const error = await props.onContinue({ projectId: selectedProject!.id, neonProjectName: searchParams.get("neon_project_name") ?? undefined });
|
||||
if (error) {
|
||||
throw new Error(error.error);
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export default async function NeonIntegrationConfirmPage(props: { searchParams:
|
||||
</>;
|
||||
}
|
||||
|
||||
const onContinue = async (options: { projectId: string }) => {
|
||||
const onContinue = async (options: { projectId: string, neonProjectName?: string }) => {
|
||||
"use server";
|
||||
|
||||
const user = await stackServerApp.getUser();
|
||||
@ -39,6 +39,7 @@ export default async function NeonIntegrationConfirmPage(props: { searchParams:
|
||||
body: JSON.stringify({
|
||||
project_id: options.projectId,
|
||||
interaction_uid: props.searchParams.interaction_uid,
|
||||
neon_project_name: options.neonProjectName,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
|
||||
@ -88,7 +88,12 @@ const columns: ColumnDef<ExtendedApiKey>[] = [
|
||||
{
|
||||
accessorKey: "expiresAt",
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} columnTitle="Expires At" />,
|
||||
cell: ({ row }) => <DateCell date={row.original.expiresAt} ignoreAfterYears={100} />
|
||||
cell: ({ row }) => <DateCell date={row.original.expiresAt} ignoreAfterYears={50} />
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} columnTitle="Created At" />,
|
||||
cell: ({ row }) => <DateCell date={row.original.createdAt} ignoreAfterYears={50} />
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
@ -102,10 +107,10 @@ export function ApiKeyTable(props: { apiKeys: ApiKey[] }) {
|
||||
...apiKey,
|
||||
status: ({ 'valid': 'valid', 'manually-revoked': 'revoked', 'expired': 'expired' } as const)[apiKey.whyInvalid() || 'valid'],
|
||||
} satisfies ExtendedApiKey));
|
||||
// first soft based on status, then by expiresAt
|
||||
// first soft based on status, then by createdAt
|
||||
return keys.sort((a, b) => {
|
||||
if (a.status === b.status) {
|
||||
return a.expiresAt < b.expiresAt ? 1 : -1;
|
||||
return a.createdAt < b.createdAt ? 1 : -1;
|
||||
}
|
||||
return a.status === 'valid' ? -1 : 1;
|
||||
});
|
||||
|
||||
@ -94,7 +94,7 @@ async function authorize(projectId: string) {
|
||||
"status": 307,
|
||||
"body": "http://localhost:8101/integrations/neon/confirm?interaction_uid=%3Cstripped+query+param%3E&=",
|
||||
"headers": Headers {
|
||||
"location": "http://localhost:8101/integrations/neon/confirm?interaction_uid=%3Cstripped+query+param%3E&neon_project_display_name=neon-project",
|
||||
"location": "http://localhost:8101/integrations/neon/confirm?interaction_uid=%3Cstripped+query+param%3E&neon_project_name=neon-project",
|
||||
<some fields may have been hidden>,
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user