Compare commits
234 commits
cbe1e4decb
...
19e7f66135
| Author | SHA1 | Date | |
|---|---|---|---|
|
19e7f66135 |
|||
|
|
d10fdac670 |
||
|
|
21f0cd6e3f |
||
|
|
b2ee8ef7de |
||
|
|
1e066cbabd | ||
|
|
4cc38d44e0 | ||
|
|
dcf7393259 | ||
|
|
bab070b09c |
||
|
|
2bd4ad5770 |
||
|
|
61ecebf911 | ||
|
|
33c8663a5b |
||
|
|
1f9f3fdede |
||
|
|
a778109214 |
||
|
|
cb7fa9375b |
||
|
|
515ecb09e7 |
||
|
|
a12a620697 |
||
|
|
0c3b2bc2f5 |
||
|
|
78ba27dc63 |
||
|
|
dc20b863ed |
||
|
|
c9a211d5cf |
||
|
|
95f94cffd2 |
||
|
|
0da95cbdb8 |
||
|
|
dadd1e3101 |
||
|
|
d523ae3ffa |
||
|
|
9a41cac6e1 |
||
|
|
5d3c5ab7cc |
||
|
|
e94ded920b |
||
|
|
c882fbd59a | ||
|
|
46b50a042e |
||
|
|
fda9e95786 |
||
|
|
ea1ad23bff |
||
|
|
7ffc5e0212 |
||
|
|
acba9444f4 |
||
|
|
f7e3671801 |
||
|
|
a1b2e36a5d |
||
|
|
44e96942b3 |
||
|
|
f2efa760ff |
||
|
|
256df9042b |
||
|
|
6d7091fb5c |
||
|
|
0d1f88a368 |
||
|
|
2ae601717d |
||
|
|
c7c8b463b4 |
||
|
|
282f839211 |
||
|
|
b2eb846b69 |
||
|
|
62cf925dcf |
||
|
|
e699f84c4d | ||
|
|
c1189dadc5 | ||
|
|
76bc080a6d |
||
|
|
7f989f77ac |
||
|
|
b916f768fe |
||
|
|
e4509c5714 |
||
|
|
ddb6893a64 |
||
|
|
248751ba1d | ||
|
|
b4b74ed53a | ||
|
|
76903cd67f | ||
|
|
e4f2eac703 | ||
|
|
3aa45007a7 | ||
|
|
f452892c88 |
||
|
|
a0fece8a0e |
||
|
|
e3d493209b |
||
|
|
2e8b63553d | ||
|
|
fb8f4b95b7 |
||
|
|
83e107c713 |
||
|
|
e97642a790 |
||
|
|
426d8684bf |
||
|
|
5e7409a4f0 |
||
|
|
c225a4cd48 |
||
|
|
24df9e1ce6 |
||
|
|
eab1fd3722 | ||
|
|
93bd041693 | ||
|
|
665ebe993c | ||
|
|
4086130371 | ||
|
|
29aacf5238 | ||
|
|
497e6a8422 | ||
|
|
af8572add9 | ||
|
|
d6aea96400 | ||
|
|
17e26ff1a6 | ||
|
|
f5f223348d | ||
|
|
e4f90fd7ea | ||
|
|
96dff20760 | ||
|
|
d639f7f6de | ||
|
|
5b35ec2ea2 | ||
|
|
bc78b95265 |
||
|
|
97f22eccbb |
||
|
|
a4fe86e38a |
||
|
|
4bc1e10ecb |
||
|
|
5b840d73bb |
||
|
|
afa9acfb1e |
||
|
|
7b7f65da39 |
||
|
|
806da59f47 |
||
|
|
9a009a4ea3 |
||
|
|
083d890053 |
||
|
|
e693a8aeb8 |
||
|
|
831b46d7b5 |
||
|
|
8dd3022b94 |
||
|
|
b278eb7110 |
||
|
|
7a66163216 |
||
|
|
dda2043401 |
||
|
|
08d6183c9b |
||
|
|
eea0b86d6d |
||
|
|
58c04fd196 |
||
|
|
09de6f6b5f | ||
|
|
d0bbd2b539 | ||
|
|
134595a6b7 | ||
|
|
4ff46f1650 |
||
|
|
4779201d4c | ||
|
|
3a8643d83c |
||
|
|
806a49b822 |
||
|
|
95d74825ee |
||
|
|
e4960909ed |
||
|
|
6cb36aaf13 |
||
|
|
cb06e93650 |
||
|
|
e3a2f7a514 |
||
|
|
01b1e817d8 |
||
|
|
c3a5195575 |
||
|
|
99765c7bd5 |
||
|
|
8929f389f4 |
||
|
|
4141d91f1b | ||
|
|
90272c84d2 | ||
|
|
3006a8e58c | ||
|
|
52a9dbd45d | ||
|
|
b2fb55d2c1 | ||
|
|
a1c16d22d8 | ||
|
|
0e9504ee4d | ||
|
|
e8cad6fc20 | ||
|
|
bc261f7739 | ||
|
|
0b8983a86b | ||
|
|
5a61da3c53 | ||
|
|
800fe6244c | ||
|
|
9e1fec812c | ||
|
|
61632f9c97 | ||
|
|
f5e44129d8 |
||
|
|
3eaca924da |
||
|
|
da1c706334 |
||
|
|
3b726dfb1e |
||
|
|
d51e7f7e40 |
||
|
|
2551e0c291 |
||
|
|
2efd5c31ab |
||
|
|
1eacb8ff36 |
||
|
|
612446c3c9 |
||
|
|
e121e16ad9 |
||
|
|
23616b41be |
||
|
|
1778ba49b2 | ||
|
|
b6f2bd4703 | ||
|
|
5fd67224f6 | ||
|
|
c9d21dde0c | ||
|
|
de2c5aa068 | ||
|
|
ad01cecae6 | ||
|
|
75ef14c75b | ||
|
|
03a5a0eddb | ||
|
|
66befd35eb | ||
|
|
3cbad16c30 | ||
|
|
3bba7c5956 | ||
|
|
0daa84c583 | ||
|
|
92358a52c0 | ||
|
|
faf17e9e86 | ||
|
|
ef6efe94b4 |
||
|
|
819d7ea23e |
||
|
|
61ff192cfd |
||
|
|
ceb1b07ce2 |
||
|
|
90188d4358 |
||
|
|
35aa0ab4e7 |
||
|
|
14dd76db8b |
||
|
|
fb26dfad65 |
||
|
|
bedc5adb75 |
||
|
|
800b1f1520 |
||
|
|
a4571a80ae |
||
|
|
a0a612618e |
||
|
|
db94728a5b |
||
|
|
04352a670a |
||
|
|
fe6e3b013e |
||
|
|
a947a74194 |
||
|
|
06055ff62b |
||
|
|
45cb1562e5 |
||
|
|
2f89a16852 | ||
|
|
86956b8cac | ||
|
|
84fb3add33 | ||
|
|
56ee68d9f3 |
||
|
|
e81fd3bb31 |
||
|
|
938ca29777 |
||
|
|
122902968f |
||
|
|
b55c30065f |
||
|
|
92ac2dbac2 |
||
|
|
d3e6decef9 |
||
|
|
579cd9d338 |
||
|
|
9e2a58dd46 | ||
|
|
64722617c1 |
||
|
|
5845ddbdda |
||
|
|
bf9ce0df9b | ||
|
|
8aee2ec3a1 |
||
|
|
3292eafe4a |
||
|
|
9ad31b2c81 |
||
|
|
374ed79a18 |
||
|
|
3d5f73e344 |
||
|
|
6761428a96 |
||
|
|
0a9b463eaa | ||
|
|
c219256fff | ||
|
|
7e48803dc5 | ||
|
|
d496b8a414 | ||
|
|
4825129560 | ||
|
|
adc54b2582 | ||
|
|
863567c9b6 | ||
|
|
102555023b | ||
|
|
f1a9eef531 | ||
|
|
5f007a5b0f | ||
|
|
9455141262 | ||
|
|
37e1379c88 | ||
|
|
55d597e519 | ||
|
|
da5ee5c951 | ||
|
|
b0bd9279fc |
||
|
|
90456339ca |
||
|
|
a653c8bad7 |
||
|
|
c4fa6cf458 |
||
|
|
268fc7b923 |
||
|
|
02604f5290 |
||
|
|
1dad7e86a0 |
||
|
|
838e3efbca |
||
|
|
3e353717f5 |
||
|
|
0a4b74b91a |
||
|
|
e69fbf3ccf |
||
|
|
0b2349d6bf |
||
|
|
3a8f04cf14 |
||
|
|
e941cf956f |
||
|
|
29f7bcf6f5 | ||
|
|
1cf1e0dc57 | ||
|
|
175283805e | ||
|
|
063c0405e8 | ||
|
|
947cb77753 | ||
|
|
28b3b305ea |
||
|
|
df85f13aea |
||
|
|
042e2c1390 |
||
|
|
e6314bee35 |
||
|
|
4292d3262e |
||
|
|
35d070ad29 |
48 changed files with 300 additions and 184 deletions
|
|
@ -41,7 +41,7 @@ _Pangolin tunnels your services to the internet so you can access anything from
|
||||||
[](https://hub.docker.com/r/fosrl/pangolin)
|
[](https://hub.docker.com/r/fosrl/pangolin)
|
||||||

|

|
||||||
[](https://discord.gg/HCJR8Xhme4)
|
[](https://discord.gg/HCJR8Xhme4)
|
||||||
[](https://www.youtube.com/@fossorial-app)
|
[](https://www.youtube.com/@fossorial-app)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ import base64
|
||||||
YAML_FILE_PATH = 'blueprint.yaml'
|
YAML_FILE_PATH = 'blueprint.yaml'
|
||||||
|
|
||||||
# The API endpoint and headers from the curl request
|
# The API endpoint and headers from the curl request
|
||||||
API_URL = 'http://localhost:3004/v1/org/test/blueprint'
|
API_URL = 'http://api.pangolin.fossorial.io/v1/org/test/blueprint'
|
||||||
HEADERS = {
|
HEADERS = {
|
||||||
'accept': '*/*',
|
'accept': '*/*',
|
||||||
'Authorization': 'Bearer v7ix7xha1bmq2on.tzsden374mtmkeczm3tx44uzxsljnrst7nmg7ccr',
|
'Authorization': 'Bearer <your_token_here>',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -10,7 +10,7 @@
|
||||||
"license": "SEE LICENSE IN LICENSE AND README.md",
|
"license": "SEE LICENSE IN LICENSE AND README.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
||||||
"@hookform/resolvers": "4.1.3",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
"@oslojs/crypto": "1.0.1",
|
"@oslojs/crypto": "1.0.1",
|
||||||
"@oslojs/encoding": "1.1.0",
|
"@oslojs/encoding": "1.1.0",
|
||||||
|
|
@ -2232,15 +2232,14 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@hookform/resolvers": {
|
"node_modules/@hookform/resolvers": {
|
||||||
"version": "4.1.3",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
|
||||||
"integrity": "sha512-Jsv6UOWYTrEFJ/01ZrnwVXs7KDvP8XIo115i++5PWvNkNvkrsTfGiLS6w+eJ57CYtUtDQalUWovCZDHFJ8u1VQ==",
|
"integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/utils": "^0.3.0"
|
"@standard-schema/utils": "^0.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react-hook-form": "^7.0.0"
|
"react-hook-form": "^7.55.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
||||||
"@hookform/resolvers": "4.1.3",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
"@oslojs/crypto": "1.0.1",
|
"@oslojs/crypto": "1.0.1",
|
||||||
"@oslojs/encoding": "1.1.0",
|
"@oslojs/encoding": "1.1.0",
|
||||||
|
|
|
||||||
|
|
@ -138,12 +138,8 @@ export async function updateProxyResources(
|
||||||
? true
|
? true
|
||||||
: resourceData.ssl;
|
: resourceData.ssl;
|
||||||
let headers = "";
|
let headers = "";
|
||||||
for (const header of resourceData.headers || []) {
|
if (resourceData.headers) {
|
||||||
headers += `${header.name}: ${header.value},`;
|
headers = JSON.stringify(resourceData.headers);
|
||||||
}
|
|
||||||
// if there are headers, remove the trailing comma
|
|
||||||
if (headers.endsWith(",")) {
|
|
||||||
headers = headers.slice(0, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingResource) {
|
if (existingResource) {
|
||||||
|
|
@ -169,7 +165,7 @@ export async function updateProxyResources(
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set({
|
.set({
|
||||||
name: resourceData.name || "Unnamed Resource",
|
name: resourceData.name || "Unnamed Resource",
|
||||||
protocol: protocol || "http",
|
protocol: protocol || "tcp",
|
||||||
http: http,
|
http: http,
|
||||||
proxyPort: http ? null : resourceData["proxy-port"],
|
proxyPort: http ? null : resourceData["proxy-port"],
|
||||||
fullDomain: http ? resourceData["full-domain"] : null,
|
fullDomain: http ? resourceData["full-domain"] : null,
|
||||||
|
|
@ -461,7 +457,7 @@ export async function updateProxyResources(
|
||||||
orgId,
|
orgId,
|
||||||
niceId: resourceNiceId,
|
niceId: resourceNiceId,
|
||||||
name: resourceData.name || "Unnamed Resource",
|
name: resourceData.name || "Unnamed Resource",
|
||||||
protocol: resourceData.protocol || "http",
|
protocol: protocol || "tcp",
|
||||||
http: http,
|
http: http,
|
||||||
proxyPort: http ? null : resourceData["proxy-port"],
|
proxyPort: http ? null : resourceData["proxy-port"],
|
||||||
fullDomain: http ? resourceData["full-domain"] : null,
|
fullDomain: http ? resourceData["full-domain"] : null,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// This is a placeholder value replaced by the build process
|
||||||
export const APP_VERSION = "1.10.1";
|
export const APP_VERSION = "1.10.2";
|
||||||
|
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,13 @@ export async function oidcAutoProvision({
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(eq(userOrgs.userId, userId));
|
.where(eq(userOrgs.userId, userId));
|
||||||
|
|
||||||
|
// Filter to only auto-provisioned orgs for CRUD operations
|
||||||
|
const autoProvisionedOrgs = currentUserOrgs.filter(
|
||||||
|
(org) => org.autoProvisioned === true
|
||||||
|
);
|
||||||
|
|
||||||
// Delete orgs that are no longer valid
|
// Delete orgs that are no longer valid
|
||||||
const orgsToDelete = currentUserOrgs
|
const orgsToDelete = autoProvisionedOrgs
|
||||||
.filter(
|
.filter(
|
||||||
(currentOrg) =>
|
(currentOrg) =>
|
||||||
!userOrgInfo.some(
|
!userOrgInfo.some(
|
||||||
|
|
@ -195,7 +200,9 @@ export async function oidcAutoProvision({
|
||||||
orgsToAdd.map((org) => ({
|
orgsToAdd.map((org) => ({
|
||||||
userId: userId!,
|
userId: userId!,
|
||||||
orgId: org.orgId,
|
orgId: org.orgId,
|
||||||
roleId: org.roleId
|
roleId: org.roleId,
|
||||||
|
autoProvisioned: true,
|
||||||
|
dateCreated: new Date().toISOString()
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export async function addTargets(
|
||||||
}:${target.port}`;
|
}:${target.port}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
sendToClient(newtId, {
|
await sendToClient(newtId, {
|
||||||
type: `newt/${protocol}/add`,
|
type: `newt/${protocol}/add`,
|
||||||
data: {
|
data: {
|
||||||
targets: payloadTargets
|
targets: payloadTargets
|
||||||
|
|
|
||||||
|
|
@ -319,26 +319,6 @@ async function createRawResource(
|
||||||
|
|
||||||
const { name, http, protocol, proxyPort } = parsedBody.data;
|
const { name, http, protocol, proxyPort } = parsedBody.data;
|
||||||
|
|
||||||
// if http is false check to see if there is already a resource with the same port and protocol
|
|
||||||
const existingResource = await db
|
|
||||||
.select()
|
|
||||||
.from(resources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(resources.protocol, protocol),
|
|
||||||
eq(resources.proxyPort, proxyPort!)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingResource.length > 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.CONFLICT,
|
|
||||||
"Resource with that protocol and port already exists"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let resource: Resource | undefined;
|
let resource: Resource | undefined;
|
||||||
|
|
||||||
const niceId = await getUniqueResourceName(orgId);
|
const niceId = await getUniqueResourceName(orgId);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,9 @@ async function query(resourceId?: number, niceId?: string, orgId?: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetResourceResponse = NonNullable<Awaited<ReturnType<typeof query>>>;
|
export type GetResourceResponse = Omit<NonNullable<Awaited<ReturnType<typeof query>>>, 'headers'> & {
|
||||||
|
headers: { name: string; value: string }[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "get",
|
method: "get",
|
||||||
|
|
@ -99,7 +101,10 @@ export async function getResource(
|
||||||
}
|
}
|
||||||
|
|
||||||
return response<GetResourceResponse>(res, {
|
return response<GetResourceResponse>(res, {
|
||||||
data: resource,
|
data: {
|
||||||
|
...resource,
|
||||||
|
headers: resource.headers ? JSON.parse(resource.headers) : resource.headers
|
||||||
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource retrieved successfully",
|
message: "Resource retrieved successfully",
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const updateHttpResourceBodySchema = z
|
||||||
tlsServerName: z.string().nullable().optional(),
|
tlsServerName: z.string().nullable().optional(),
|
||||||
setHostHeader: z.string().nullable().optional(),
|
setHostHeader: z.string().nullable().optional(),
|
||||||
skipToIdpId: z.number().int().positive().nullable().optional(),
|
skipToIdpId: z.number().int().positive().nullable().optional(),
|
||||||
headers: z.string().nullable().optional()
|
headers: z.array(z.object({ name: z.string(), value: z.string() })).nullable().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
|
|
@ -85,18 +85,6 @@ const updateHttpResourceBodySchema = z
|
||||||
message:
|
message:
|
||||||
"Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
|
"Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.refine(
|
|
||||||
(data) => {
|
|
||||||
if (data.headers) {
|
|
||||||
return validateHeaders(data.headers);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message:
|
|
||||||
"Invalid headers format. Use comma-separated format: 'Header-Name: value, Another-Header: another-value'. Header values cannot contain colons."
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type UpdateResourceResponse = Resource;
|
export type UpdateResourceResponse = Resource;
|
||||||
|
|
@ -247,7 +235,7 @@ async function updateHttpResource(
|
||||||
|
|
||||||
// Validate domain and construct full domain
|
// Validate domain and construct full domain
|
||||||
const domainResult = await validateAndConstructDomain(domainId, resource.orgId, updateData.subdomain);
|
const domainResult = await validateAndConstructDomain(domainId, resource.orgId, updateData.subdomain);
|
||||||
|
|
||||||
if (!domainResult.success) {
|
if (!domainResult.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
|
@ -292,9 +280,14 @@ async function updateHttpResource(
|
||||||
updateData.subdomain = finalSubdomain;
|
updateData.subdomain = finalSubdomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let headers = null;
|
||||||
|
if (updateData.headers) {
|
||||||
|
headers = JSON.stringify(updateData.headers);
|
||||||
|
}
|
||||||
|
|
||||||
const updatedResource = await db
|
const updatedResource = await db
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set({ ...updateData })
|
.set({ ...updateData, headers })
|
||||||
.where(eq(resources.resourceId, resource.resourceId))
|
.where(eq(resources.resourceId, resource.resourceId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|
@ -342,31 +335,6 @@ async function updateRawResource(
|
||||||
|
|
||||||
const updateData = parsedBody.data;
|
const updateData = parsedBody.data;
|
||||||
|
|
||||||
if (updateData.proxyPort) {
|
|
||||||
const proxyPort = updateData.proxyPort;
|
|
||||||
const existingResource = await db
|
|
||||||
.select()
|
|
||||||
.from(resources)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(resources.protocol, resource.protocol),
|
|
||||||
eq(resources.proxyPort, proxyPort!)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
existingResource.length > 0 &&
|
|
||||||
existingResource[0].resourceId !== resource.resourceId
|
|
||||||
) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.CONFLICT,
|
|
||||||
"Resource with that protocol and port already exists"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedResource = await db
|
const updatedResource = await db
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set(updateData)
|
.set(updateData)
|
||||||
|
|
|
||||||
|
|
@ -306,17 +306,25 @@ export async function getTraefikConfig(
|
||||||
...additionalMiddlewares
|
...additionalMiddlewares
|
||||||
];
|
];
|
||||||
|
|
||||||
if (resource.headers && resource.headers.length > 0) {
|
if (resource.headers || resource.setHostHeader) {
|
||||||
// if there are headers, parse them into an object
|
// if there are headers, parse them into an object
|
||||||
const headersObj: { [key: string]: string } = {};
|
const headersObj: { [key: string]: string } = {};
|
||||||
const headersArr = resource.headers.split(",");
|
if (resource.headers) {
|
||||||
for (const header of headersArr) {
|
let headersArr: { name: string; value: string }[] = [];
|
||||||
const [key, value] = header
|
try {
|
||||||
.split(":")
|
headersArr = JSON.parse(resource.headers) as {
|
||||||
.map((s: string) => s.trim());
|
name: string;
|
||||||
if (key && value) {
|
value: string;
|
||||||
headersObj[key] = value;
|
}[];
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(
|
||||||
|
`Failed to parse headers for resource ${resource.resourceId}: ${e}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headersArr.forEach((header) => {
|
||||||
|
headersObj[header.name] = header.value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resource.setHostHeader) {
|
if (resource.setHostHeader) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import m2 from "./scriptsPg/1.7.0";
|
||||||
import m3 from "./scriptsPg/1.8.0";
|
import m3 from "./scriptsPg/1.8.0";
|
||||||
import m4 from "./scriptsPg/1.9.0";
|
import m4 from "./scriptsPg/1.9.0";
|
||||||
import m5 from "./scriptsPg/1.10.0";
|
import m5 from "./scriptsPg/1.10.0";
|
||||||
|
import m6 from "./scriptsPg/1.10.2";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
|
|
@ -21,6 +22,7 @@ const migrations = [
|
||||||
{ version: "1.8.0", run: m3 },
|
{ version: "1.8.0", run: m3 },
|
||||||
{ version: "1.9.0", run: m4 },
|
{ version: "1.9.0", run: m4 },
|
||||||
{ version: "1.10.0", run: m5 },
|
{ version: "1.10.0", run: m5 },
|
||||||
|
{ version: "1.10.2", run: m6 },
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as {
|
] as {
|
||||||
version: string;
|
version: string;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import m23 from "./scriptsSqlite/1.8.0";
|
||||||
import m24 from "./scriptsSqlite/1.9.0";
|
import m24 from "./scriptsSqlite/1.9.0";
|
||||||
import m25 from "./scriptsSqlite/1.10.0";
|
import m25 from "./scriptsSqlite/1.10.0";
|
||||||
import m26 from "./scriptsSqlite/1.10.1";
|
import m26 from "./scriptsSqlite/1.10.1";
|
||||||
|
import m27 from "./scriptsSqlite/1.10.2";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
|
|
@ -55,6 +56,7 @@ const migrations = [
|
||||||
{ version: "1.9.0", run: m24 },
|
{ version: "1.9.0", run: m24 },
|
||||||
{ version: "1.10.0", run: m25 },
|
{ version: "1.10.0", run: m25 },
|
||||||
{ version: "1.10.1", run: m26 },
|
{ version: "1.10.1", run: m26 },
|
||||||
|
{ version: "1.10.2", run: m27 },
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
|
||||||
47
server/setup/scriptsPg/1.10.2.ts
Normal file
47
server/setup/scriptsPg/1.10.2.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { db } from "@server/db/pg/driver";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { __DIRNAME, APP_PATH } from "@server/lib/consts";
|
||||||
|
|
||||||
|
const version = "1.10.2";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resources = await db.execute(sql`
|
||||||
|
SELECT * FROM "resources"
|
||||||
|
`);
|
||||||
|
|
||||||
|
await db.execute(sql`BEGIN`);
|
||||||
|
|
||||||
|
for (const resource of resources.rows) {
|
||||||
|
const headers = resource.headers as string | null;
|
||||||
|
if (headers && headers !== "") {
|
||||||
|
// lets convert it to json
|
||||||
|
// fist split at commas
|
||||||
|
const headersArray = headers
|
||||||
|
.split(",")
|
||||||
|
.map((header: string) => {
|
||||||
|
const [name, ...valueParts] = header.split(":");
|
||||||
|
const value = valueParts.join(":").trim();
|
||||||
|
return { name: name.trim(), value };
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.execute(sql`
|
||||||
|
UPDATE "resources" SET "headers" = ${JSON.stringify(headersArray)} WHERE "resourceId" = ${resource.resourceId}
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Updated resource ${resource.resourceId} headers to JSON format`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.execute(sql`COMMIT`);
|
||||||
|
console.log(`Migrated database`);
|
||||||
|
} catch (e) {
|
||||||
|
await db.execute(sql`ROLLBACK`);
|
||||||
|
console.log("Failed to migrate db:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
server/setup/scriptsSqlite/1.10.2.ts
Normal file
54
server/setup/scriptsSqlite/1.10.2.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { APP_PATH } from "@server/lib/consts";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const version = "1.10.2";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
|
const db = new Database(location);
|
||||||
|
|
||||||
|
const resources = db.prepare("SELECT * FROM resources").all() as Array<{
|
||||||
|
resourceId: number;
|
||||||
|
headers: string | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.pragma("foreign_keys = OFF");
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
for (const resource of resources) {
|
||||||
|
const headers = resource.headers;
|
||||||
|
if (headers && headers !== "") {
|
||||||
|
// lets convert it to json
|
||||||
|
// fist split at commas
|
||||||
|
const headersArray = headers
|
||||||
|
.split(",")
|
||||||
|
.map((header: string) => {
|
||||||
|
const [name, ...valueParts] = header.split(":");
|
||||||
|
const value = valueParts.join(":").trim();
|
||||||
|
return { name: name.trim(), value };
|
||||||
|
});
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
UPDATE "resources" SET "headers" = ? WHERE "resourceId" = ?`
|
||||||
|
).run(JSON.stringify(headersArray), resource.resourceId);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Updated resource ${resource.resourceId} headers to JSON format`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
db.pragma("foreign_keys = ON");
|
||||||
|
|
||||||
|
console.log(`Migrated database`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to migrate db:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ export default function AccessControlsPage() {
|
||||||
autoProvisioned: z.boolean()
|
autoProvisioned: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: user.username!,
|
username: user.username!,
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ export default function Page() {
|
||||||
{ hours: 168, name: t("day", { count: 7 }) }
|
{ hours: 168, name: t("day", { count: 7 }) }
|
||||||
];
|
];
|
||||||
|
|
||||||
const internalForm = useForm<z.infer<typeof internalFormSchema>>({
|
const internalForm = useForm({
|
||||||
resolver: zodResolver(internalFormSchema),
|
resolver: zodResolver(internalFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
|
|
@ -170,7 +170,7 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const googleAzureForm = useForm<z.infer<typeof googleAzureFormSchema>>({
|
const googleAzureForm = useForm({
|
||||||
resolver: zodResolver(googleAzureFormSchema),
|
resolver: zodResolver(googleAzureFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
|
|
@ -179,7 +179,7 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const genericOidcForm = useForm<z.infer<typeof genericOidcFormSchema>>({
|
const genericOidcForm = useForm({
|
||||||
resolver: zodResolver(genericOidcFormSchema),
|
resolver: zodResolver(genericOidcFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: "",
|
username: "",
|
||||||
|
|
|
||||||
|
|
@ -91,14 +91,14 @@ export default function Page() {
|
||||||
|
|
||||||
type CopiedFormValues = z.infer<typeof copiedFormSchema>;
|
type CopiedFormValues = z.infer<typeof copiedFormSchema>;
|
||||||
|
|
||||||
const form = useForm<CreateFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(createFormSchema),
|
resolver: zodResolver(createFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: ""
|
name: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const copiedForm = useForm<CopiedFormValues>({
|
const copiedForm = useForm({
|
||||||
resolver: zodResolver(copiedFormSchema),
|
resolver: zodResolver(copiedFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
copied: true
|
copied: true
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export default function GeneralPage() {
|
||||||
const [clientSites, setClientSites] = useState<Tag[]>([]);
|
const [clientSites, setClientSites] = useState<Tag[]>([]);
|
||||||
const [activeSitesTagIndex, setActiveSitesTagIndex] = useState<number | null>(null);
|
const [activeSitesTagIndex, setActiveSitesTagIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
const form = useForm<GeneralFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: client?.name,
|
name: client?.name,
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<CreateClientFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(createClientFormSchema),
|
resolver: zodResolver(createClientFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export default function GeneralPage() {
|
||||||
const [loadingDelete, setLoadingDelete] = useState(false);
|
const [loadingDelete, setLoadingDelete] = useState(false);
|
||||||
const [loadingSave, setLoadingSave] = useState(false);
|
const [loadingSave, setLoadingSave] = useState(false);
|
||||||
|
|
||||||
const form = useForm<GeneralFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: org?.org.name,
|
name: org?.org.name,
|
||||||
|
|
|
||||||
|
|
@ -138,12 +138,12 @@ export default function ResourceAuthenticationPage() {
|
||||||
const [isSetPasswordOpen, setIsSetPasswordOpen] = useState(false);
|
const [isSetPasswordOpen, setIsSetPasswordOpen] = useState(false);
|
||||||
const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false);
|
const [isSetPincodeOpen, setIsSetPincodeOpen] = useState(false);
|
||||||
|
|
||||||
const usersRolesForm = useForm<z.infer<typeof UsersRolesFormSchema>>({
|
const usersRolesForm = useForm({
|
||||||
resolver: zodResolver(UsersRolesFormSchema),
|
resolver: zodResolver(UsersRolesFormSchema),
|
||||||
defaultValues: { roles: [], users: [] }
|
defaultValues: { roles: [], users: [] }
|
||||||
});
|
});
|
||||||
|
|
||||||
const whitelistForm = useForm<z.infer<typeof whitelistSchema>>({
|
const whitelistForm = useForm({
|
||||||
resolver: zodResolver(whitelistSchema),
|
resolver: zodResolver(whitelistSchema),
|
||||||
defaultValues: { emails: [] }
|
defaultValues: { emails: [] }
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ export default function GeneralForm() {
|
||||||
|
|
||||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||||
|
|
||||||
const form = useForm<GeneralFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
enabled: resource.enabled,
|
enabled: resource.enabled,
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
message: t("proxyErrorInvalidHeader")
|
message: t("proxyErrorInvalidHeader")
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
headers: z.string().optional()
|
headers: z.array(z.object({ name: z.string(), value: z.string() })).nullable()
|
||||||
});
|
});
|
||||||
|
|
||||||
const tlsSettingsSchema = z.object({
|
const tlsSettingsSchema = z.object({
|
||||||
|
|
@ -260,7 +260,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
port: "" as any as number,
|
port: "" as any as number,
|
||||||
path: null,
|
path: null,
|
||||||
pathMatchType: null
|
pathMatchType: null
|
||||||
} as z.infer<typeof addTargetSchema>
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchedIp = addTargetForm.watch("ip");
|
const watchedIp = addTargetForm.watch("ip");
|
||||||
|
|
@ -274,7 +274,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tlsSettingsForm = useForm<TlsSettingsValues>({
|
const tlsSettingsForm = useForm({
|
||||||
resolver: zodResolver(tlsSettingsSchema),
|
resolver: zodResolver(tlsSettingsSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
ssl: resource.ssl,
|
ssl: resource.ssl,
|
||||||
|
|
@ -282,15 +282,15 @@ export default function ReverseProxyTargets(props: {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const proxySettingsForm = useForm<ProxySettingsValues>({
|
const proxySettingsForm = useForm({
|
||||||
resolver: zodResolver(proxySettingsSchema),
|
resolver: zodResolver(proxySettingsSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
setHostHeader: resource.setHostHeader || "",
|
setHostHeader: resource.setHostHeader || "",
|
||||||
headers: resource.headers || ""
|
headers: resource.headers
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const targetsSettingsForm = useForm<TargetsSettingsValues>({
|
const targetsSettingsForm = useForm({
|
||||||
resolver: zodResolver(targetsSettingsSchema),
|
resolver: zodResolver(targetsSettingsSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
stickySession: resource.stickySession
|
stickySession: resource.stickySession
|
||||||
|
|
@ -1479,7 +1479,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<HeadersInput
|
<HeadersInput
|
||||||
value={
|
value={
|
||||||
field.value || ""
|
field.value
|
||||||
}
|
}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
field.onChange(
|
field.onChange(
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ export default function ResourceRules(props: {
|
||||||
CIDR: t('ipAddressRange')
|
CIDR: t('ipAddressRange')
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const addRuleForm = useForm<z.infer<typeof addRuleSchema>>({
|
const addRuleForm = useForm({
|
||||||
resolver: zodResolver(addRuleSchema),
|
resolver: zodResolver(addRuleSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
action: "ACCEPT",
|
action: "ACCEPT",
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ export default function Page() {
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|
||||||
const baseForm = useForm<BaseResourceFormValues>({
|
const baseForm = useForm({
|
||||||
resolver: zodResolver(baseResourceFormSchema),
|
resolver: zodResolver(baseResourceFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
@ -219,12 +219,12 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const httpForm = useForm<HttpResourceFormValues>({
|
const httpForm = useForm({
|
||||||
resolver: zodResolver(httpResourceFormSchema),
|
resolver: zodResolver(httpResourceFormSchema),
|
||||||
defaultValues: {}
|
defaultValues: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const tcpUdpForm = useForm<TcpUdpResourceFormValues>({
|
const tcpUdpForm = useForm({
|
||||||
resolver: zodResolver(tcpUdpResourceFormSchema),
|
resolver: zodResolver(tcpUdpResourceFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
protocol: "tcp",
|
protocol: "tcp",
|
||||||
|
|
@ -241,7 +241,7 @@ export default function Page() {
|
||||||
port: "" as any as number,
|
port: "" as any as number,
|
||||||
path: null,
|
path: null,
|
||||||
pathMatchType: null
|
pathMatchType: null
|
||||||
} as z.infer<typeof addTargetSchema>
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchedIp = addTargetForm.watch("ip");
|
const watchedIp = addTargetForm.watch("ip");
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ export default function GeneralPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
const form = useForm<GeneralFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: site?.name,
|
name: site?.name,
|
||||||
|
|
|
||||||
|
|
@ -425,7 +425,7 @@ WantedBy=default.target`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<CreateSiteFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(createSiteFormSchema),
|
resolver: zodResolver(createSiteFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
|
||||||
|
|
@ -89,14 +89,14 @@ export default function Page() {
|
||||||
|
|
||||||
type CopiedFormValues = z.infer<typeof copiedFormSchema>;
|
type CopiedFormValues = z.infer<typeof copiedFormSchema>;
|
||||||
|
|
||||||
const form = useForm<CreateFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(createFormSchema),
|
resolver: zodResolver(createFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: ""
|
name: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const copiedForm = useForm<CopiedFormValues>({
|
const copiedForm = useForm({
|
||||||
resolver: zodResolver(copiedFormSchema),
|
resolver: zodResolver(copiedFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
copied: true
|
copied: true
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export default function GeneralPage() {
|
||||||
|
|
||||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||||
|
|
||||||
const form = useForm<GeneralFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default function PoliciesPage() {
|
||||||
type PolicyFormValues = z.infer<typeof policyFormSchema>;
|
type PolicyFormValues = z.infer<typeof policyFormSchema>;
|
||||||
type DefaultMappingsValues = z.infer<typeof defaultMappingsSchema>;
|
type DefaultMappingsValues = z.infer<typeof defaultMappingsSchema>;
|
||||||
|
|
||||||
const form = useForm<PolicyFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(policyFormSchema),
|
resolver: zodResolver(policyFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
orgId: "",
|
orgId: "",
|
||||||
|
|
@ -111,7 +111,7 @@ export default function PoliciesPage() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultMappingsForm = useForm<DefaultMappingsValues>({
|
const defaultMappingsForm = useForm({
|
||||||
resolver: zodResolver(defaultMappingsSchema),
|
resolver: zodResolver(defaultMappingsSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
defaultRoleMapping: "",
|
defaultRoleMapping: "",
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export default function Page() {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const form = useForm<CreateIdpFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(createIdpFormSchema),
|
resolver: zodResolver(createIdpFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export default function InitialSetupPage() {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [checking, setChecking] = useState(true);
|
const [checking, setChecking] = useState(true);
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
setupToken: "",
|
setupToken: "",
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default function ResetPasswordForm({
|
||||||
code: z.string().length(6, { message: t('pincodeInvalid') })
|
code: z.string().length(6, { message: t('pincodeInvalid') })
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: emailParam || "",
|
email: emailParam || "",
|
||||||
|
|
@ -112,14 +112,14 @@ export default function ResetPasswordForm({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mfaForm = useForm<z.infer<typeof mfaSchema>>({
|
const mfaForm = useForm({
|
||||||
resolver: zodResolver(mfaSchema),
|
resolver: zodResolver(mfaSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: ""
|
code: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestForm = useForm<z.infer<typeof requestSchema>>({
|
const requestForm = useForm({
|
||||||
resolver: zodResolver(requestSchema),
|
resolver: zodResolver(requestSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: emailParam || ""
|
email: emailParam || ""
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export default function StepperForm() {
|
||||||
subnet: z.string().min(1, { message: t("subnetRequired") })
|
subnet: z.string().min(1, { message: t("subnetRequired") })
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgForm = useForm<z.infer<typeof orgSchema>>({
|
const orgForm = useForm({
|
||||||
resolver: zodResolver(orgSchema),
|
resolver: zodResolver(orgSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
orgName: "",
|
orgName: "",
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,118 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
|
|
||||||
interface HeadersInputProps {
|
interface HeadersInputProps {
|
||||||
value?: string;
|
value?: { name: string, value: string }[] | null;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: { name: string, value: string }[] | null) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HeadersInput({
|
export function HeadersInput({
|
||||||
value = "",
|
value = [],
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = `X-Example-Header: example-value
|
placeholder = `X-Example-Header: example-value
|
||||||
X-Another-Header: another-value`,
|
X-Another-Header: another-value`,
|
||||||
rows = 4,
|
rows = 4,
|
||||||
className
|
className
|
||||||
}: HeadersInputProps) {
|
}: HeadersInputProps) {
|
||||||
const [internalValue, setInternalValue] = useState("");
|
const [internalValue, setInternalValue] = useState("");
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const isUserEditingRef = useRef(false);
|
||||||
|
|
||||||
// Convert comma-separated to newline-separated for display
|
// Convert header objects array to newline-separated string for display
|
||||||
const convertToNewlineSeparated = (commaSeparated: string): string => {
|
const convertToNewlineSeparated = (headers: { name: string, value: string }[] | null): string => {
|
||||||
if (!commaSeparated || commaSeparated.trim() === "") return "";
|
if (!headers || headers.length === 0) return "";
|
||||||
|
|
||||||
return commaSeparated
|
return headers
|
||||||
.split(',')
|
.map(header => `${header.name}: ${header.value}`)
|
||||||
.map(header => header.trim())
|
|
||||||
.filter(header => header.length > 0)
|
|
||||||
.join('\n');
|
.join('\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert newline-separated to comma-separated for output
|
// Convert newline-separated string to header objects array
|
||||||
const convertToCommaSeparated = (newlineSeparated: string): string => {
|
const convertToHeadersArray = (newlineSeparated: string): { name: string, value: string }[] | null => {
|
||||||
if (!newlineSeparated || newlineSeparated.trim() === "") return "";
|
if (!newlineSeparated || newlineSeparated.trim() === "") return [];
|
||||||
|
|
||||||
return newlineSeparated
|
return newlineSeparated
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(header => header.trim())
|
.map(line => line.trim())
|
||||||
.filter(header => header.length > 0)
|
.filter(line => line.length > 0 && line.includes(':'))
|
||||||
.join(', ');
|
.map(line => {
|
||||||
|
const colonIndex = line.indexOf(':');
|
||||||
|
const name = line.substring(0, colonIndex).trim();
|
||||||
|
const value = line.substring(colonIndex + 1).trim();
|
||||||
|
|
||||||
|
// Ensure header name conforms to HTTP header requirements
|
||||||
|
// Header names should be case-insensitive, contain only ASCII letters, digits, and hyphens
|
||||||
|
const normalizedName = name.replace(/[^a-zA-Z0-9\-]/g, '').toLowerCase();
|
||||||
|
|
||||||
|
return { name: normalizedName, value };
|
||||||
|
})
|
||||||
|
.filter(header => header.name.length > 0); // Filter out headers with invalid names
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update internal value when external value changes
|
// Update internal value when external value changes
|
||||||
|
// But only if the user is not currently editing (textarea not focused)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInternalValue(convertToNewlineSeparated(value));
|
if (!isUserEditingRef.current) {
|
||||||
|
setInternalValue(convertToNewlineSeparated(value));
|
||||||
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
const newValue = e.target.value;
|
const newValue = e.target.value;
|
||||||
setInternalValue(newValue);
|
setInternalValue(newValue);
|
||||||
|
|
||||||
// Convert back to comma-separated format for the parent
|
// Mark that user is actively editing
|
||||||
const commaSeparatedValue = convertToCommaSeparated(newValue);
|
isUserEditingRef.current = true;
|
||||||
onChange(commaSeparatedValue);
|
|
||||||
|
// Only update parent if the input is in a valid state
|
||||||
|
// Valid states: empty/whitespace only, or contains properly formatted headers
|
||||||
|
|
||||||
|
if (newValue.trim() === "") {
|
||||||
|
// Empty input is valid - represents no headers
|
||||||
|
onChange([]);
|
||||||
|
} else {
|
||||||
|
// Check if all non-empty lines are properly formatted (contain ':')
|
||||||
|
const lines = newValue.split('\n');
|
||||||
|
const nonEmptyLines = lines
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(line => line.length > 0);
|
||||||
|
|
||||||
|
// If there are no non-empty lines, or all non-empty lines contain ':', it's valid
|
||||||
|
const isValid = nonEmptyLines.length === 0 || nonEmptyLines.every(line => line.includes(':'));
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
// Safe to convert and update parent
|
||||||
|
const headersArray = convertToHeadersArray(newValue);
|
||||||
|
onChange(headersArray);
|
||||||
|
}
|
||||||
|
// If not valid, don't call onChange - let user continue typing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
isUserEditingRef.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
// Small delay to allow any final change events to process
|
||||||
|
setTimeout(() => {
|
||||||
|
isUserEditingRef.current = false;
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Textarea
|
<Textarea
|
||||||
|
ref={textareaRef}
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
className={className}
|
className={className}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export function IdpCreateWizard({ onSubmit, defaultValues, loading = false }: Id
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const form = useForm<CreateIdpFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(createIdpFormSchema),
|
resolver: zodResolver(createIdpFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
||||||
code: z.string().length(6, { message: t("pincodeInvalid") })
|
code: z.string().length(6, { message: t("pincodeInvalid") })
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
|
|
@ -88,7 +88,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mfaForm = useForm<z.infer<typeof mfaSchema>>({
|
const mfaForm = useForm({
|
||||||
resolver: zodResolver(mfaSchema),
|
resolver: zodResolver(mfaSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: ""
|
code: ""
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ArrowUpDown,
|
ArrowUpDown,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { PolicyDataTable } from "./PolicyDataTable";
|
import { PolicyDataTable } from "@app/components/PolicyDataTable";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export default function ResetPasswordForm({
|
||||||
code: z.string().length(6, { message: t('pincodeInvalid') })
|
code: z.string().length(6, { message: t('pincodeInvalid') })
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: emailParam || "",
|
email: emailParam || "",
|
||||||
|
|
@ -112,14 +112,14 @@ export default function ResetPasswordForm({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mfaForm = useForm<z.infer<typeof mfaSchema>>({
|
const mfaForm = useForm({
|
||||||
resolver: zodResolver(mfaSchema),
|
resolver: zodResolver(mfaSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: ""
|
code: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestForm = useForm<z.infer<typeof requestSchema>>({
|
const requestForm = useForm({
|
||||||
resolver: zodResolver(requestSchema),
|
resolver: zodResolver(requestSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: emailParam || ""
|
email: emailParam || ""
|
||||||
|
|
|
||||||
|
|
@ -132,28 +132,28 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(getDefaultSelectedMethod());
|
const [activeTab, setActiveTab] = useState(getDefaultSelectedMethod());
|
||||||
|
|
||||||
const pinForm = useForm<z.infer<typeof pinSchema>>({
|
const pinForm = useForm({
|
||||||
resolver: zodResolver(pinSchema),
|
resolver: zodResolver(pinSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
pin: ""
|
pin: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const passwordForm = useForm<z.infer<typeof passwordSchema>>({
|
const passwordForm = useForm({
|
||||||
resolver: zodResolver(passwordSchema),
|
resolver: zodResolver(passwordSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
password: ""
|
password: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestOtpForm = useForm<z.infer<typeof requestOtpSchema>>({
|
const requestOtpForm = useForm({
|
||||||
resolver: zodResolver(requestOtpSchema),
|
resolver: zodResolver(requestOtpSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: ""
|
email: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitOtpForm = useForm<z.infer<typeof submitOtpSchema>>({
|
const submitOtpForm = useForm({
|
||||||
resolver: zodResolver(submitOtpSchema),
|
resolver: zodResolver(submitOtpSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ export default function SecurityKeyForm({
|
||||||
code: z.string().optional()
|
code: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const registerForm = useForm<RegisterFormValues>({
|
const registerForm = useForm({
|
||||||
resolver: zodResolver(registerSchema),
|
resolver: zodResolver(registerSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
@ -128,7 +128,7 @@ export default function SecurityKeyForm({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteForm = useForm<DeleteFormValues>({
|
const deleteForm = useForm({
|
||||||
resolver: zodResolver(deleteSchema),
|
resolver: zodResolver(deleteSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
password: "",
|
password: "",
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,6 @@ const setPasswordFormSchema = z.object({
|
||||||
|
|
||||||
type SetPasswordFormValues = z.infer<typeof setPasswordFormSchema>;
|
type SetPasswordFormValues = z.infer<typeof setPasswordFormSchema>;
|
||||||
|
|
||||||
const defaultValues: Partial<SetPasswordFormValues> = {
|
|
||||||
password: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
type SetPasswordFormProps = {
|
type SetPasswordFormProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
|
@ -61,9 +57,11 @@ export default function SetResourcePasswordForm({
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const form = useForm<SetPasswordFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(setPasswordFormSchema),
|
resolver: zodResolver(setPasswordFormSchema),
|
||||||
defaultValues
|
defaultValues: {
|
||||||
|
password: ""
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,6 @@ const setPincodeFormSchema = z.object({
|
||||||
|
|
||||||
type SetPincodeFormValues = z.infer<typeof setPincodeFormSchema>;
|
type SetPincodeFormValues = z.infer<typeof setPincodeFormSchema>;
|
||||||
|
|
||||||
const defaultValues: Partial<SetPincodeFormValues> = {
|
|
||||||
pincode: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
type SetPincodeFormProps = {
|
type SetPincodeFormProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
|
@ -65,9 +61,11 @@ export default function SetResourcePincodeForm({
|
||||||
|
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
const form = useForm<SetPincodeFormValues>({
|
const form = useForm({
|
||||||
resolver: zodResolver(setPincodeFormSchema),
|
resolver: zodResolver(setPincodeFormSchema),
|
||||||
defaultValues
|
defaultValues: {
|
||||||
|
pincode: ""
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ export default function SignupForm({
|
||||||
const [passwordValue, setPasswordValue] = useState("");
|
const [passwordValue, setPasswordValue] = useState("");
|
||||||
const [confirmPasswordValue, setConfirmPasswordValue] = useState("");
|
const [confirmPasswordValue, setConfirmPasswordValue] = useState("");
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: emailParam || "",
|
email: emailParam || "",
|
||||||
|
|
|
||||||
|
|
@ -91,14 +91,14 @@ const TwoFactorSetupForm = forwardRef<
|
||||||
code: z.string().length(6, { message: t("pincodeInvalid") })
|
code: z.string().length(6, { message: t("pincodeInvalid") })
|
||||||
});
|
});
|
||||||
|
|
||||||
const enableForm = useForm<z.infer<typeof enableSchema>>({
|
const enableForm = useForm({
|
||||||
resolver: zodResolver(enableSchema),
|
resolver: zodResolver(enableSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
password: initialPassword || ""
|
password: initialPassword || ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const confirmForm = useForm<z.infer<typeof confirmSchema>>({
|
const confirmForm = useForm({
|
||||||
resolver: zodResolver(confirmSchema),
|
resolver: zodResolver(confirmSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: ""
|
code: ""
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export default function VerifyEmailForm({
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: email,
|
email: email,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue