@podosoft/podokit 0.16.2 → 0.16.4
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/deploy-profile.d.ts +4 -0
- package/dist/deploy-profile.js +21 -0
- package/dist/deploy-render.js +12 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +2 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.test.ts +58 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +9 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +6 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +6 -2
- package/dist/templates/todo/apps/web/package.json +2 -1
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +2 -2
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.test.ts +58 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +9 -0
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +6 -2
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +6 -2
- package/package.json +1 -1
package/dist/deploy-profile.d.ts
CHANGED
|
@@ -30,6 +30,9 @@ export interface VerificationCheckProfile {
|
|
|
30
30
|
expectedStatus: number;
|
|
31
31
|
expectedJson?: Record<string, string | number | boolean | null>;
|
|
32
32
|
}
|
|
33
|
+
export interface MigrationProfile {
|
|
34
|
+
command: string[];
|
|
35
|
+
}
|
|
33
36
|
export interface DeployProfileV1 {
|
|
34
37
|
schemaVersion: 1;
|
|
35
38
|
driver: "kubernetes-helm";
|
|
@@ -67,6 +70,7 @@ export interface DeployProfileV1 {
|
|
|
67
70
|
web: NamedSecretProfile | null;
|
|
68
71
|
imagePull: string;
|
|
69
72
|
};
|
|
73
|
+
migration?: MigrationProfile;
|
|
70
74
|
runtimeConfig: Record<string, string>;
|
|
71
75
|
verification: {
|
|
72
76
|
baseUrl: string;
|
package/dist/deploy-profile.js
CHANGED
|
@@ -83,6 +83,20 @@ function parseRequiredKeys(value, field) {
|
|
|
83
83
|
}
|
|
84
84
|
return keys.sort();
|
|
85
85
|
}
|
|
86
|
+
function parseCommand(value, field) {
|
|
87
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
88
|
+
throw new Error(`Deployment profile field "${field}" must be a non-empty string array.`);
|
|
89
|
+
}
|
|
90
|
+
return value.map((entry, index) => {
|
|
91
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
92
|
+
throw new Error(`Deployment profile field "${field}[${index}]" must be a non-empty string.`);
|
|
93
|
+
}
|
|
94
|
+
if (/[\u0000-\u001f\u007f]/.test(entry)) {
|
|
95
|
+
throw new Error(`Deployment profile field "${field}[${index}]" must not contain control characters.`);
|
|
96
|
+
}
|
|
97
|
+
return entry;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
86
100
|
function parseNamedSecret(value, field) {
|
|
87
101
|
if (!isRecord(value)) {
|
|
88
102
|
throw new Error(`Deployment profile field "${field}" must be an object.`);
|
|
@@ -221,6 +235,7 @@ function parseProfile(value) {
|
|
|
221
235
|
"workloads",
|
|
222
236
|
"dependencies",
|
|
223
237
|
"secrets",
|
|
238
|
+
"migration",
|
|
224
239
|
"runtimeConfig",
|
|
225
240
|
"verification",
|
|
226
241
|
], "root");
|
|
@@ -236,6 +251,7 @@ function parseProfile(value) {
|
|
|
236
251
|
const workloads = requiredRecord(value, "workloads");
|
|
237
252
|
const dependencies = requiredRecord(value, "dependencies");
|
|
238
253
|
const secrets = requiredRecord(value, "secrets");
|
|
254
|
+
const migration = value.migration === undefined ? undefined : requiredRecord(value, "migration");
|
|
239
255
|
const verification = requiredRecord(value, "verification");
|
|
240
256
|
const runtimeConfig = requiredRecord(value, "runtimeConfig");
|
|
241
257
|
assertOnlyKeys(target, ["context", "clusterFingerprint", "namespace", "release", "createNamespace"], "target");
|
|
@@ -244,6 +260,8 @@ function parseProfile(value) {
|
|
|
244
260
|
assertOnlyKeys(workloads, ["api", "web", "worker"], "workloads");
|
|
245
261
|
assertOnlyKeys(dependencies, ["postgres", "redis", "objectStorage"], "dependencies");
|
|
246
262
|
assertOnlyKeys(secrets, ["api", "web", "imagePull"], "secrets");
|
|
263
|
+
if (migration)
|
|
264
|
+
assertOnlyKeys(migration, ["command"], "migration");
|
|
247
265
|
assertOnlyKeys(verification, ["baseUrl", "checks"], "verification");
|
|
248
266
|
const context = requiredString(target, "context");
|
|
249
267
|
const clusterFingerprint = requiredString(target, "clusterFingerprint");
|
|
@@ -402,6 +420,9 @@ function parseProfile(value) {
|
|
|
402
420
|
: parseNamedSecret(secrets.web, "secrets.web"),
|
|
403
421
|
imagePull: requiredString(secrets, "imagePull"),
|
|
404
422
|
},
|
|
423
|
+
...(migration
|
|
424
|
+
? { migration: { command: parseCommand(migration.command, "migration.command") } }
|
|
425
|
+
: {}),
|
|
405
426
|
runtimeConfig: parsedConfig,
|
|
406
427
|
verification: { baseUrl, checks },
|
|
407
428
|
};
|
package/dist/deploy-render.js
CHANGED
|
@@ -8,6 +8,14 @@ const deploy_profile_1 = require("./deploy-profile");
|
|
|
8
8
|
function quote(value) {
|
|
9
9
|
return JSON.stringify(value);
|
|
10
10
|
}
|
|
11
|
+
function yamlScalar(value) {
|
|
12
|
+
return /^(?!null$|true$|false$|~$)[A-Za-z_][A-Za-z0-9_./:-]*$/i.test(value)
|
|
13
|
+
? value
|
|
14
|
+
: quote(value);
|
|
15
|
+
}
|
|
16
|
+
function yamlStringSequence(values) {
|
|
17
|
+
return `[${values.map((value) => yamlScalar(value)).join(", ")}]`;
|
|
18
|
+
}
|
|
11
19
|
function digest(parts) {
|
|
12
20
|
const hash = (0, node_crypto_1.createHash)("sha256");
|
|
13
21
|
for (const part of parts)
|
|
@@ -286,7 +294,8 @@ apiVersion: batch/v1
|
|
|
286
294
|
kind: Job
|
|
287
295
|
metadata:
|
|
288
296
|
name: ${profile.target.release}-minio-init-${initializationHash}
|
|
289
|
-
|
|
297
|
+
labels:
|
|
298
|
+
${labels(`${profile.target.release}-minio-initialize`)}spec:
|
|
290
299
|
backoffLimit: 6
|
|
291
300
|
ttlSecondsAfterFinished: 600
|
|
292
301
|
template:
|
|
@@ -570,6 +579,7 @@ function migrationRuntimeEnv(profile) {
|
|
|
570
579
|
}
|
|
571
580
|
function migrationManifest(profile, release, images) {
|
|
572
581
|
const releaseSlug = release.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
582
|
+
const command = profile.migration?.command ?? ["npm", "run", "migrate:all"];
|
|
573
583
|
return `apiVersion: batch/v1
|
|
574
584
|
kind: Job
|
|
575
585
|
metadata:
|
|
@@ -595,7 +605,7 @@ ${labels(`${profile.target.release}-migration`, 8)} spec:
|
|
|
595
605
|
- name: migrate
|
|
596
606
|
image: ${quote(images.api)}
|
|
597
607
|
imagePullPolicy: IfNotPresent
|
|
598
|
-
command:
|
|
608
|
+
command: ${yamlStringSequence(command)}
|
|
599
609
|
env:
|
|
600
610
|
${migrationRuntimeEnv(profile)}
|
|
601
611
|
envFrom:
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
10
|
"lint": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "vitest run"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@internationalized/date": "^3.12.2",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"typesafe-i18n": "^5.26.2",
|
|
33
33
|
"typescript": "^6.0.3",
|
|
34
34
|
"vite": "^5.4.0",
|
|
35
|
+
"vitest": "^4.1.10",
|
|
35
36
|
"tw-animate-css": "^1.4.0"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { RequestEvent } from "@sveltejs/kit";
|
|
2
2
|
import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
|
|
3
|
-
import { backendBaseUrl,
|
|
3
|
+
import { backendBaseUrl, resolveClientIp } from "./backend-proxy";
|
|
4
4
|
|
|
5
5
|
// Server-side client for load functions and hooks: points at the internal
|
|
6
6
|
// backend URL and forwards the incoming request's cookies.
|
|
7
7
|
export function serverApiClient(event: RequestEvent): ApiClient {
|
|
8
8
|
const cookie = event.request.headers.get("cookie") ?? "";
|
|
9
|
-
const clientIp =
|
|
9
|
+
const clientIp = resolveClientIp(event.getClientAddress);
|
|
10
10
|
const fetchWithCookies: typeof fetch = (input, init) => {
|
|
11
11
|
const headers = new Headers(init?.headers);
|
|
12
12
|
if (cookie) headers.set("cookie", cookie);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { proxyRequest, resolveClientIp } from "./backend-proxy";
|
|
3
|
+
|
|
4
|
+
describe("backend proxy client address handling", () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns the normalized address from a trusted proxy chain", () => {
|
|
10
|
+
expect(resolveClientIp(() => "::ffff:203.0.113.9")).toBe("203.0.113.9");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("omits the address when the proxy header is absent", () => {
|
|
14
|
+
expect(
|
|
15
|
+
resolveClientIp(() => {
|
|
16
|
+
throw new Error("address header is absent");
|
|
17
|
+
}),
|
|
18
|
+
).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("omits the address when the proxy chain is too short", () => {
|
|
22
|
+
expect(
|
|
23
|
+
resolveClientIp(() => {
|
|
24
|
+
throw new Error("configured depth exceeds address count");
|
|
25
|
+
}),
|
|
26
|
+
).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("does not trust an incoming forwarded header as a fallback", async () => {
|
|
30
|
+
let upstreamHeaders: Headers | undefined;
|
|
31
|
+
vi.stubGlobal("fetch", async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
32
|
+
upstreamHeaders = new Headers(init?.headers);
|
|
33
|
+
return new Response(null, { status: 204 });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const request = new Request("http://app.localhost/api/health", {
|
|
37
|
+
headers: { "x-forwarded-for": "198.51.100.25" },
|
|
38
|
+
});
|
|
39
|
+
const response = await proxyRequest(request, "http://api:5002/health");
|
|
40
|
+
|
|
41
|
+
expect(response.status).toBe(204);
|
|
42
|
+
expect(upstreamHeaders?.get("x-forwarded-for")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("forwards a resolved client address", async () => {
|
|
46
|
+
let upstreamHeaders: Headers | undefined;
|
|
47
|
+
vi.stubGlobal("fetch", async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
48
|
+
upstreamHeaders = new Headers(init?.headers);
|
|
49
|
+
return new Response(null, { status: 204 });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const request = new Request("http://app.localhost/api/health");
|
|
53
|
+
const response = await proxyRequest(request, "http://api:5002/health", "203.0.113.9");
|
|
54
|
+
|
|
55
|
+
expect(response.status).toBe(204);
|
|
56
|
+
expect(upstreamHeaders?.get("x-forwarded-for")).toBe("203.0.113.9");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -19,6 +19,15 @@ export function normalizeClientIp(address: string | undefined): string | undefin
|
|
|
19
19
|
return address;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function resolveClientIp(getClientAddress: () => string): string | undefined {
|
|
23
|
+
try {
|
|
24
|
+
return normalizeClientIp(getClientAddress());
|
|
25
|
+
} catch {
|
|
26
|
+
// An absent or shorter-than-configured proxy chain is not a trusted source.
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
export async function proxyRequest(
|
|
23
32
|
request: Request,
|
|
24
33
|
targetUrl: string,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
-
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
2
|
+
import { backendBaseUrl, proxyRequest, resolveClientIp } from "$lib/server/backend-proxy";
|
|
3
3
|
|
|
4
4
|
// Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
|
|
5
5
|
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
-
proxyRequest(
|
|
6
|
+
proxyRequest(
|
|
7
|
+
request,
|
|
8
|
+
`${backendBaseUrl()}/${params.path}${url.search}`,
|
|
9
|
+
resolveClientIp(getClientAddress),
|
|
10
|
+
);
|
|
7
11
|
|
|
8
12
|
export const GET = handler;
|
|
9
13
|
export const POST = handler;
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
-
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
2
|
+
import { backendBaseUrl, proxyRequest, resolveClientIp } from "$lib/server/backend-proxy";
|
|
3
3
|
|
|
4
4
|
// Proxy the better-auth handler, keeping the /api/auth prefix.
|
|
5
5
|
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
-
proxyRequest(
|
|
6
|
+
proxyRequest(
|
|
7
|
+
request,
|
|
8
|
+
`${backendBaseUrl()}/api/auth/${params.all}${url.search}`,
|
|
9
|
+
resolveClientIp(getClientAddress),
|
|
10
|
+
);
|
|
7
11
|
|
|
8
12
|
export const GET = handler;
|
|
9
13
|
export const POST = handler;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
10
|
"lint": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "vitest run"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@internationalized/date": "^3.12.2",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"typesafe-i18n": "^5.26.2",
|
|
33
33
|
"typescript": "^6.0.3",
|
|
34
34
|
"vite": "^5.4.0",
|
|
35
|
+
"vitest": "^4.1.10",
|
|
35
36
|
"tw-animate-css": "^1.4.0"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { RequestEvent } from "@sveltejs/kit";
|
|
2
2
|
import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
|
|
3
|
-
import { backendBaseUrl,
|
|
3
|
+
import { backendBaseUrl, resolveClientIp } from "./backend-proxy";
|
|
4
4
|
|
|
5
5
|
// Server-side client for load functions and hooks: points at the internal
|
|
6
6
|
// backend URL and forwards the incoming request's cookies.
|
|
7
7
|
export function serverApiClient(event: RequestEvent): ApiClient {
|
|
8
8
|
const cookie = event.request.headers.get("cookie") ?? "";
|
|
9
|
-
const clientIp =
|
|
9
|
+
const clientIp = resolveClientIp(event.getClientAddress);
|
|
10
10
|
const fetchWithCookies: typeof fetch = (input, init) => {
|
|
11
11
|
const headers = new Headers(init?.headers);
|
|
12
12
|
if (cookie) headers.set("cookie", cookie);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { proxyRequest, resolveClientIp } from "./backend-proxy";
|
|
3
|
+
|
|
4
|
+
describe("backend proxy client address handling", () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns the normalized address from a trusted proxy chain", () => {
|
|
10
|
+
expect(resolveClientIp(() => "::ffff:203.0.113.9")).toBe("203.0.113.9");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("omits the address when the proxy header is absent", () => {
|
|
14
|
+
expect(
|
|
15
|
+
resolveClientIp(() => {
|
|
16
|
+
throw new Error("address header is absent");
|
|
17
|
+
}),
|
|
18
|
+
).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("omits the address when the proxy chain is too short", () => {
|
|
22
|
+
expect(
|
|
23
|
+
resolveClientIp(() => {
|
|
24
|
+
throw new Error("configured depth exceeds address count");
|
|
25
|
+
}),
|
|
26
|
+
).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("does not trust an incoming forwarded header as a fallback", async () => {
|
|
30
|
+
let upstreamHeaders: Headers | undefined;
|
|
31
|
+
vi.stubGlobal("fetch", async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
32
|
+
upstreamHeaders = new Headers(init?.headers);
|
|
33
|
+
return new Response(null, { status: 204 });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const request = new Request("http://app.localhost/api/health", {
|
|
37
|
+
headers: { "x-forwarded-for": "198.51.100.25" },
|
|
38
|
+
});
|
|
39
|
+
const response = await proxyRequest(request, "http://api:5002/health");
|
|
40
|
+
|
|
41
|
+
expect(response.status).toBe(204);
|
|
42
|
+
expect(upstreamHeaders?.get("x-forwarded-for")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("forwards a resolved client address", async () => {
|
|
46
|
+
let upstreamHeaders: Headers | undefined;
|
|
47
|
+
vi.stubGlobal("fetch", async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
48
|
+
upstreamHeaders = new Headers(init?.headers);
|
|
49
|
+
return new Response(null, { status: 204 });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const request = new Request("http://app.localhost/api/health");
|
|
53
|
+
const response = await proxyRequest(request, "http://api:5002/health", "203.0.113.9");
|
|
54
|
+
|
|
55
|
+
expect(response.status).toBe(204);
|
|
56
|
+
expect(upstreamHeaders?.get("x-forwarded-for")).toBe("203.0.113.9");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -19,6 +19,15 @@ export function normalizeClientIp(address: string | undefined): string | undefin
|
|
|
19
19
|
return address;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function resolveClientIp(getClientAddress: () => string): string | undefined {
|
|
23
|
+
try {
|
|
24
|
+
return normalizeClientIp(getClientAddress());
|
|
25
|
+
} catch {
|
|
26
|
+
// An absent or shorter-than-configured proxy chain is not a trusted source.
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
export async function proxyRequest(
|
|
23
32
|
request: Request,
|
|
24
33
|
targetUrl: string,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
-
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
2
|
+
import { backendBaseUrl, proxyRequest, resolveClientIp } from "$lib/server/backend-proxy";
|
|
3
3
|
|
|
4
4
|
// Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
|
|
5
5
|
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
-
proxyRequest(
|
|
6
|
+
proxyRequest(
|
|
7
|
+
request,
|
|
8
|
+
`${backendBaseUrl()}/${params.path}${url.search}`,
|
|
9
|
+
resolveClientIp(getClientAddress),
|
|
10
|
+
);
|
|
7
11
|
|
|
8
12
|
export const GET = handler;
|
|
9
13
|
export const POST = handler;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
-
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
2
|
+
import { backendBaseUrl, proxyRequest, resolveClientIp } from "$lib/server/backend-proxy";
|
|
3
3
|
|
|
4
4
|
// Proxy the better-auth handler, keeping the /api/auth prefix.
|
|
5
5
|
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
-
proxyRequest(
|
|
6
|
+
proxyRequest(
|
|
7
|
+
request,
|
|
8
|
+
`${backendBaseUrl()}/api/auth/${params.all}${url.search}`,
|
|
9
|
+
resolveClientIp(getClientAddress),
|
|
10
|
+
);
|
|
7
11
|
|
|
8
12
|
export const GET = handler;
|
|
9
13
|
export const POST = handler;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@podosoft/podokit",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.4",
|
|
4
4
|
"description": "An opinionated but extensible starter toolkit and CLI for full-stack TypeScript apps with NestJS, SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|