mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
fixed domain and handler
This commit is contained in:
parent
5840bcfa10
commit
32c16f0704
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { IconButton, List, ListItem, ListDivider, Input, FormControl, FormLabel, Checkbox, Typography, Box } from "@mui/joy";
|
||||
import React, { use, useState } from "react";
|
||||
import { IconButton, List, ListItem, ListDivider, Input, FormControl, FormLabel, Checkbox, Typography, Box, FormHelperText } from "@mui/joy";
|
||||
import React, { use, useEffect, useState } from "react";
|
||||
import { Paragraph } from "@/components/paragraph";
|
||||
import { Icon } from "@/components/icon";
|
||||
import { Dialog } from "@/components/dialog";
|
||||
@ -9,6 +9,139 @@ import { AsyncButton } from "@/components/async-button";
|
||||
import { SimpleCard } from "@/components/simple-card";
|
||||
import { useAdminApp } from "../../use-admin-app";
|
||||
import { SmartSwitch } from "@/components/smart-switch";
|
||||
import { Project } from "@stackframe/stack/dist/lib/stack-app";
|
||||
import { DomainConfigJson } from "@stackframe/stack-shared/dist/interface/clientInterface";
|
||||
|
||||
function isValidUrl(urlString: string) {
|
||||
try {
|
||||
return Boolean(new URL(urlString));
|
||||
}
|
||||
catch(e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function EditDialog(props: {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
domains: Set<DomainConfigJson>,
|
||||
project: Project,
|
||||
type: 'update' | 'create',
|
||||
editIndex?: number,
|
||||
}) {
|
||||
const [newDomain, setNewDomain] = useState("");
|
||||
const [newDomainError, setNewDomainError] = useState<string | null>(null);
|
||||
|
||||
const [newHandlerPath, setNewHandlerPath] = useState("/handler");
|
||||
const [newHandlerPathError, setNewHandlerPathError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(props.editIndex, props.domains);
|
||||
if (props.editIndex !== undefined) {
|
||||
const domain = [...props.domains][props.editIndex];
|
||||
setNewDomain(domain.domain);
|
||||
setNewHandlerPath(domain.handlerPath);
|
||||
}
|
||||
}, [props.editIndex, props.domains]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={(props.type === 'create' ? "Create" : "Update") + " domain and handler"}
|
||||
open={props.open}
|
||||
onClose={() => {
|
||||
setNewDomain("");
|
||||
setNewDomainError(null);
|
||||
props.onClose();
|
||||
}}
|
||||
okButton={{
|
||||
label: props.type === 'create' ? "Create" : "Update",
|
||||
onClick: async () => {
|
||||
if (!newDomain.startsWith("http://") && !newDomain.startsWith("https://")) {
|
||||
setNewDomainError("Domain must start with http:// or https://");
|
||||
return "prevent-close";
|
||||
}
|
||||
if (!newHandlerPath.startsWith("/")) {
|
||||
setNewHandlerPathError("Handler path must start with /");
|
||||
return "prevent-close";
|
||||
}
|
||||
if (!isValidUrl(newDomain)) {
|
||||
setNewDomainError("Invalid domain");
|
||||
return "prevent-close";
|
||||
}
|
||||
if (!isValidUrl(newDomain + newHandlerPath)) {
|
||||
setNewHandlerPathError("Invalid handler path");
|
||||
return "prevent-close";
|
||||
}
|
||||
|
||||
const domainAlreadyExists = [...props.domains].some(({ domain }) => domain === newDomain);
|
||||
if (props.type === 'create' && domainAlreadyExists ) {
|
||||
setNewDomainError("Domain already exists");
|
||||
return "prevent-close";
|
||||
}
|
||||
|
||||
if (props.type === 'create') {
|
||||
await props.project.update({
|
||||
config: {
|
||||
domains: [...props.domains, {
|
||||
domain: newDomain,
|
||||
handlerPath: newHandlerPath,
|
||||
}],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await props.project.update({
|
||||
config: {
|
||||
domains: [...props.domains].map((domain, i) => {
|
||||
if (i === props.editIndex) {
|
||||
return {
|
||||
domain: newDomain,
|
||||
handlerPath: newHandlerPath,
|
||||
};
|
||||
}
|
||||
return domain;
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}}
|
||||
cancelButton
|
||||
>
|
||||
<Paragraph body>
|
||||
<b>Warning:</b> Make sure this is a trusted domain or a URL that you control.
|
||||
</Paragraph>
|
||||
|
||||
<FormControl required error={!!newDomainError} sx={{ mt: 2}}>
|
||||
<FormLabel>
|
||||
Domain (http:// or https://)
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
value={newDomain}
|
||||
onChange={(e) => setNewDomain(e.target.value)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{newDomainError}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl required error={!!newHandlerPathError} sx={{ mt: 2}}>
|
||||
<FormLabel>
|
||||
Handler path
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
value={newHandlerPath}
|
||||
onChange={(e) => setNewHandlerPath(e.target.value)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{newHandlerPathError}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UrlsAndCallbacksClient() {
|
||||
const stackAdminApp = useAdminApp();
|
||||
@ -16,19 +149,15 @@ export default function UrlsAndCallbacksClient() {
|
||||
|
||||
const domains = new Set(project.evaluatedConfig.domains);
|
||||
|
||||
const [deleteDialogPrefix, setDeleteDialogDomain] = useState<string | null>(null);
|
||||
|
||||
const [deleteDialogDomain, setDeleteDialogDomain] = useState<string | null>(null);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [addNewDialogOpen, setAddNewDialogOpen] = useState(false);
|
||||
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
|
||||
|
||||
const [newDomain, setNewDomain] = useState("");
|
||||
const [newDomainError, setNewDomainError] = useState(false);
|
||||
|
||||
const [newHandlerPath, setNewHandlerPath] = useState("/handler");
|
||||
const [newHandlerPathError, setNewHandlerPathError] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SimpleCard title="Your Domains and Handler">
|
||||
<SimpleCard title="Domains and Handler">
|
||||
<Box sx={{ my: 2 }}>
|
||||
<SmartSwitch
|
||||
checked={project.evaluatedConfig.allowLocalhost}
|
||||
@ -44,29 +173,42 @@ export default function UrlsAndCallbacksClient() {
|
||||
</SmartSwitch>
|
||||
</Box>
|
||||
|
||||
{domains.size >= 0 || (
|
||||
{domains.size >= 0 && (
|
||||
<List
|
||||
variant="soft"
|
||||
sx={{
|
||||
"--List-radius": "9px"
|
||||
"--List-radius": "9px",
|
||||
}}
|
||||
>
|
||||
{[...domains].map(({ domain }, i) => (
|
||||
{[...domains].map(({ domain, handlerPath }, i) => (
|
||||
<React.Fragment key={domain}>
|
||||
{i !== 0 && <ListDivider />}
|
||||
<ListItem
|
||||
endAction={
|
||||
<IconButton
|
||||
aria-label="Delete"
|
||||
size="sm"
|
||||
color="danger"
|
||||
onClick={() => setDeleteDialogDomain(domain)}
|
||||
>
|
||||
<Icon icon="delete" />
|
||||
</IconButton>
|
||||
<>
|
||||
<IconButton
|
||||
aria-label="Edit"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setEditIndex(i);
|
||||
setEditDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Icon icon="edit" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="Delete"
|
||||
size="sm"
|
||||
color="danger"
|
||||
onClick={() => setDeleteDialogDomain(domain)}
|
||||
>
|
||||
<Icon icon="delete" />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{domain}
|
||||
<Typography paddingRight={1}>{domain}</Typography>
|
||||
<Typography>{handlerPath}</Typography>
|
||||
</ListItem>
|
||||
</React.Fragment>
|
||||
))}
|
||||
@ -83,9 +225,26 @@ export default function UrlsAndCallbacksClient() {
|
||||
</AsyncButton>
|
||||
</SimpleCard>
|
||||
|
||||
<EditDialog
|
||||
type='create'
|
||||
open={addNewDialogOpen}
|
||||
onClose={() => setAddNewDialogOpen(false)}
|
||||
domains={domains}
|
||||
project={project}
|
||||
/>
|
||||
|
||||
<EditDialog
|
||||
type='update'
|
||||
open={editDialogOpen}
|
||||
onClose={() => setEditDialogOpen(false)}
|
||||
editIndex={editIndex}
|
||||
domains={domains}
|
||||
project={project}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
title
|
||||
open={deleteDialogPrefix !== null}
|
||||
open={deleteDialogDomain !== null}
|
||||
onClose={() => setDeleteDialogDomain(null)}
|
||||
danger
|
||||
okButton={{
|
||||
@ -93,7 +252,7 @@ export default function UrlsAndCallbacksClient() {
|
||||
onClick: async () => {
|
||||
await project.update({
|
||||
config: {
|
||||
domains: [...domains].filter(({ domain }) => domain !== deleteDialogPrefix),
|
||||
domains: [...domains].filter(({ domain }) => domain !== deleteDialogDomain),
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -101,66 +260,12 @@ export default function UrlsAndCallbacksClient() {
|
||||
cancelButton
|
||||
>
|
||||
<Paragraph body sx={{ mt: 0 }}>
|
||||
Do you really want to delete the callback prefix <b>{deleteDialogPrefix}</b>?
|
||||
Do you really want to remove <b>{deleteDialogDomain}</b> from the allow list ?
|
||||
</Paragraph>
|
||||
<Paragraph body sx={{ mb: 0 }}>
|
||||
All callback URLs that start with this prefix will be disallowed.
|
||||
Your project will no longer be able to receive callbacks from this domain.
|
||||
</Paragraph>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
title="Add new prefix"
|
||||
open={addNewDialogOpen}
|
||||
onClose={() => {
|
||||
setAddNewDialogOpen(false);
|
||||
setNewDomain("");
|
||||
setNewDomainError(false);
|
||||
}}
|
||||
okButton={{
|
||||
label: "Add",
|
||||
onClick: async () => {
|
||||
if (!newDomain.startsWith("http://") && !newDomain.startsWith("https://")) {
|
||||
setNewDomainError(true);
|
||||
return "prevent-close";
|
||||
}
|
||||
await project.update({
|
||||
config: {
|
||||
domains: [...domains, {
|
||||
domain: newDomain,
|
||||
handlerPath: newHandlerPath,
|
||||
}],
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
cancelButton
|
||||
>
|
||||
<Paragraph body>
|
||||
<b>Warning:</b> Make sure this is a trusted domain or a URL that you control.
|
||||
</Paragraph>
|
||||
|
||||
<FormControl required error={newDomainError} sx={{ mt: 2}}>
|
||||
<FormLabel>
|
||||
Domain (http:// or https://)
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
value={newDomain}
|
||||
onChange={(e) => setNewDomain(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl required error={newHandlerPathError} sx={{ mt: 2}}>
|
||||
<FormLabel>
|
||||
Handler path
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
value={newHandlerPath}
|
||||
onChange={(e) => setNewHandlerPath(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -234,14 +234,14 @@ export async function updateProject(
|
||||
|
||||
// delete existing domains
|
||||
transaction.push(prismaClient.projectDomain.deleteMany({
|
||||
where: { projectConfigId: projectId },
|
||||
where: { projectConfigId: project.config.id },
|
||||
}));
|
||||
|
||||
// create new domains
|
||||
newDomains.forEach(domainConfig => {
|
||||
transaction.push(prismaClient.projectDomain.create({
|
||||
data: {
|
||||
projectConfigId: projectId,
|
||||
projectConfigId: project.config.id,
|
||||
domain: domainConfig.domain,
|
||||
handlerPath: domainConfig.handlerPath,
|
||||
},
|
||||
|
||||
@ -1,2 +1,11 @@
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
export const prismaClient = new PrismaClient();
|
||||
|
||||
const globalForPrisma = global as unknown as { prisma: PrismaClient };
|
||||
|
||||
export const prismaClient = globalForPrisma.prisma || new PrismaClient();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalForPrisma.prisma = prismaClient;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user