diff --git a/app/srv/api/auth/login.ts b/app/srv/api/auth/login.ts new file mode 100644 index 00000000..0f0d909b --- /dev/null +++ b/app/srv/api/auth/login.ts @@ -0,0 +1,72 @@ +import { apiContext } from "service-srv"; +import argon from "@node-rs/argon2"; +import { session } from "utils/session"; + +export const _ = { + url: "/_login", + async api(username: string, password: string) { + const { res, req } = apiContext(this); + + const current = session.get(req); + + if (!current) { + const user = await db.user.findFirst({ + where: { OR: [{ username }, { phone: username }] }, + include: { + org_user: { + select: { + org: { + select: { + id: true, + name: true, + }, + }, + }, + }, + org: { + select: { id: true, name: true }, + }, + }, + }); + if (user && user.org_user) { + user.org = user.org_user.map((e) => e.org); + delete (user as any).org_user; + } + + try { + if (!!user && (await argon.verify(user.password, password))) { + //@ts-ignore + delete user.password; + const sdata = await session.new({ user }); + + let setDefaultCookie = true; + const origin = req.headers.get("origin"); + if (origin) { + const url = new URL(origin); + if (url.hostname === "localhost") { + setDefaultCookie = false; + res.setHeader("set-cookie", `${session.cookieKey}=${sdata.id};`); + } + } + + if (setDefaultCookie) { + res.setHeader( + "set-cookie", + `${session.cookieKey}=${sdata.id}; SameSite=None; Secure; HttpOnly` + ); + } + return { status: "ok", session: sdata }; + } + } catch (e) { + console.error(e, user, password); + } + } else { + return { status: "ok", session: current }; + } + + return { + status: "failed", + reason: "Invalid username / password", + }; + }, +}; diff --git a/app/srv/api/session.ts b/app/srv/api/session.ts index c35adf78..9920af19 100644 --- a/app/srv/api/session.ts +++ b/app/srv/api/session.ts @@ -1,9 +1,37 @@ import { apiContext } from "service-srv"; +import { session } from "utils/session"; +import { user } from "dbgen"; export const _ = { - url: "/session)}", + url: "/session", async api() { const { req, res } = apiContext(this); - return "This is session.ts"; - } -} \ No newline at end of file + const sdata = session.get<{ + user: user & { + org: { + id: string; + name: string; + }[]; + }; + }>(req); + if (sdata) { + let setDefaultCookie = true; + const origin = req.headers.get("origin"); + if (origin) { + const url = new URL(origin); + if (url.hostname === "localhost") { + setDefaultCookie = false; + res.setHeader("set-cookie", `${session.cookieKey}=${sdata.id};`); + } + } + + if (setDefaultCookie) { + res.setHeader( + "set-cookie", + `${session.cookieKey}=${sdata.id}; SameSite=None; Secure; HttpOnly` + ); + } + } + return sdata; + }, +}; diff --git a/app/srv/exports.d.ts b/app/srv/exports.d.ts index d084e701..b8cd9aaa 100644 --- a/app/srv/exports.d.ts +++ b/app/srv/exports.d.ts @@ -1,10 +1,31 @@ +declare module "api/auth/login" { + export const _: { + url: string; + api(username: string, password: string): Promise<{ + status: string; + session: any; + reason?: undefined; + } | { + status: string; + reason: string; + session?: undefined; + }>; + }; +} declare module "api/session" { export const _: { url: string; - api(): Promise; + api(): Promise; }; } declare module "exports" { + export const login: { + name: string; + url: string; + path: string; + args: string[]; + handler: Promise; + }; export const session: { name: string; url: string; diff --git a/app/srv/exports.ts b/app/srv/exports.ts index a20e5562..1522ea0e 100644 --- a/app/srv/exports.ts +++ b/app/srv/exports.ts @@ -1,6 +1,13 @@ +export const login = { + name: "login", + url: "/_login", + path: "app/srv/api/auth/login.ts", + args: ["username","password"], + handler: import("./api/auth/login") +} export const session = { name: "session", - url: "/session)}", + url: "/session", path: "app/srv/api/session.ts", args: [], handler: import("./api/session") diff --git a/app/web/src/render/editor/panel/side/panel/background.tsx b/app/web/src/render/editor/panel/side/panel/background.tsx index bcb3e1ca..65a60919 100644 --- a/app/web/src/render/editor/panel/side/panel/background.tsx +++ b/app/web/src/render/editor/panel/side/panel/background.tsx @@ -1,7 +1,6 @@ import { FC } from "react"; import { useLocal } from "web-utils"; import { FieldColor } from "../ui/FieldColor"; -import { FieldImg } from "../ui/FieldImg"; import { dropdownProp } from "../ui/style"; import { Button } from "../ui/Button"; import { FNBackground } from "../../../../../utils/types/meta-fn"; @@ -53,81 +52,6 @@ export const PanelBackground: FC<{ /> - - -
* { - flex: 1; - } - ` - )} - > - { - update("bg", { ...bg, url }); - - if ( - value.type === "item" && - (!value.dim || - (value.dim && (!value.dim.w || value.dim.w === "fit"))) - ) { - const img = await getImgMeta(`${siteApiUrl}${url}`); - (update as any)("dim", { - w: Math.min(500, img?.width || 100), - h: Math.min(500, img?.height || 100), - }); - } - }} - /> -
-
- {bg.url ? ( - <> - -
- -
-
- - ) : ( - <> - )}
diff --git a/app/web/src/render/editor/panel/side/ui/FieldImg.tsx b/app/web/src/render/editor/panel/side/ui/FieldImg.tsx deleted file mode 100644 index 48df3802..00000000 --- a/app/web/src/render/editor/panel/side/ui/FieldImg.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { FC, useEffect } from "react"; -import { useLocal } from "web-utils"; -import { FilePicker } from "./FilePicker"; -import { Button } from "./Button"; -import { FileImageGallery } from "./FileImageGallery"; - -export const FieldImg: FC<{ - value?: string; - update: (value: string) => void; -}> = ({ value, update }) => { - const local = useLocal({ val: "", open: false }); - - useEffect(() => { - local.val = value || ""; - local.render(); - }, [value]); - - return ( - <> - - {local.open && ( - { - local.open = false; - local.render(); - }} - /> - )} - - ); -}; diff --git a/app/web/src/render/editor/panel/side/ui/FileImageGallery.tsx b/app/web/src/render/editor/panel/side/ui/FileImageGallery.tsx deleted file mode 100644 index 6cd6e176..00000000 --- a/app/web/src/render/editor/panel/side/ui/FileImageGallery.tsx +++ /dev/null @@ -1,421 +0,0 @@ -import { FC } from "react"; -import { fetchSendApi } from "web-utils/src/web/iframe-cors"; -import { useGlobal, useLocal } from "web-utils"; -import { Gallery } from "./Gallery"; -import { Loading } from "../../../../../utils/ui/loading"; -import { ToolbarBox } from "../../../../../utils/ui/box"; -import { EditorGlobal } from "../../../logic/global"; -export const FileImageGallery: FC<{ - value?: string; - update: (src: string) => void; - onClose: () => void; - accept?: string; - type?: string; - meta?: any; -}> = ({ - meta, - onClose, - update, - value, - accept = "video/mp4, image/jpeg, image/png, image/jpg, image/x-icon, image/vnd.microsoft.icon", - type = "image", -}) => { - const p = useGlobal(EditorGlobal, "EDITOR"); - const local = useLocal( - { - value: value || "", - load: true, - mode: "upload", - preview: false as boolean, - previewUrl: "" as string, - isUpload: false as boolean, - selectUrl: "" as string, - }, - async () => { - local.mode = "gallery"; - if (local.previewUrl) { - local.selectUrl = local.previewUrl; - } - local.render(); - } - ); - const onUpload: React.ChangeEventHandler = async function ( - e - ) { - local.isUpload = true; - local.render(); - const files = e.currentTarget.files; - if (files && p.page) { - const res: string[] = await fetchSendApi( - `${siteApiUrl}/_upload/${p.page.id}`, - files[0] - ); - local.previewUrl = res[0]; - local.preview = true; - local.selectUrl = local.previewUrl; - local.render(); - } - }; - - return ( - <> -
{ - meta.open = false; - meta.render(); - }} - >
-
- {false ? ( -
- -
- ) : ( -
ul { - width: 100%; - } - - .row { - display: flex; - flex-direction: column; - align-items: stretch; - } - - .dropping { - background: #efefff; - } - ` - )} - > -
-
-
- -
-
- - - - -
- Upload File -
- - ), - }, - { - onClick() { - local.mode = "gallery"; - local.render(); - }, - className: cx( - local.mode === "gallery" && - "border-b-2 border-blue-500 bg-blue-50" - ), - content: ( - <> -
-
- - - - - - - -
- Gallery -
- - ), - }, - ]} - /> - - {/* */} -
-
-
- -
- {local.mode === "gallery" ? ( - <> - - - ) : ( - <> -
-
- Upload Image - -
-
-
- {local.preview ? ( - <> -
- -
- - ) : ( - <> - {local.isUpload ? ( - <> -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - Uploading... - - {/* - 10% - */} -
-
-
- - ) : ( - <> - )} - - )} -
- - )} -
- {local.selectUrl && ( -
-
{ - // FieldImg; - update(local.selectUrl); - meta.open = false; - meta.render(); - }} - className="relative flex flex-row p-2 cursor-pointer bg-blue-500 text-white font-medium px-6" - > - Choose Image -
-
- )} -
- )} -
- - ); -}; diff --git a/app/web/src/render/editor/panel/side/ui/FilePicker.tsx b/app/web/src/render/editor/panel/side/ui/FilePicker.tsx deleted file mode 100644 index 95164901..00000000 --- a/app/web/src/render/editor/panel/side/ui/FilePicker.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { FC, useTransition } from "react"; -import { fetchSendApi } from "web-utils/src/web/iframe-cors"; -import { useLocal } from "web-utils"; -import { Button } from "./Button"; -export const FilePicker: FC<{ - value?: string; - update: (src: string) => void; - onClose: () => void; - accept?: string; - type?: string; -}> = ({ - onClose, - update, - value, - accept = "video/mp4, image/jpeg, image/png, image/jpg, image/x-icon, image/vnd.microsoft.icon", - type = "image", -}) => { - const local = useLocal({ - value: value || "", - load: true, - }); - - const [_, tx] = useTransition(); - const onUpload: React.ChangeEventHandler = async function ( - e - ) { - const files = e.currentTarget.files; - if (files) { - // const res: string[] = (await fetchSendApi( - // `${siteApiUrl}/_upload${site ? `/${site.id}` : ""}`, - // files[0] - // )) as any; - // if (res) { - // const val = res[0]; - // local.value = val; - // local.render(); - // update(val); - // } - // onClose(); - } - }; - - return null; - - return ( - <> -
-
-
- {local.load && local.value && ( -
Loading
- )} - {!!local.value ? ( - <> - {type === "image" && ( - { - local.load = false; - local.render(); - }} - /> - )} - {type === "pdf" && ( -
- -
- )} - - ) : ( -
- Please Upload Image - — or — - Fill Image URL -
- )} -
- - -
- { - local.value = e.currentTarget.value; - local.render(); - - tx(() => { - update(local.value); - }); - }} - /> -
- - -
-
- - ); -}; - -const PdfDocument = () => ( - - {" "} - - -); diff --git a/app/web/src/render/editor/panel/side/ui/Gallery.tsx b/app/web/src/render/editor/panel/side/ui/Gallery.tsx deleted file mode 100644 index c2d5085f..00000000 --- a/app/web/src/render/editor/panel/side/ui/Gallery.tsx +++ /dev/null @@ -1,268 +0,0 @@ -import { format } from "date-fns"; -import get from "lodash.get"; -import { FC, useTransition } from "react"; -import { fetchSendApi } from "web-utils/src/web/iframe-cors"; -import { useGlobal, useLocal } from "web-utils"; -import { Loading } from "../../../../../utils/ui/loading"; -import { EditorGlobal } from "../../../logic/global"; -export const Gallery: FC<{ - value?: string; - update: (src: string) => void; - onClose?: () => void; - accept?: string; - type?: string; - meta?: any; -}> = ({ - meta, - onClose, - update, - value, - accept = "video/mp4, image/jpeg, image/png, image/jpg, image/x-icon, image/vnd.microsoft.icon", - type = "image", -}) => { - const p = useGlobal(EditorGlobal, "EDITOR"); - const local = useLocal( - { - value: value || "", - load: true, - mode: "upload", - list: [] as Array, - ready: false, - isPreview: false, - hover: null as any, - preview: { - url: "" as string, - dimension: { - width: 0 as number, - height: 0 as number, - }, - details: null as any, - }, - }, - async () => { - local.isPreview = false; - local.ready = false; - local.render(); - if (p.page) { - let res = (await fetchSendApi( - `${siteApiUrl}/get-gallery/${p.page.id}`, - {} - )) as any; - local.list = res.data; - } - local.ready = true; - local.render(); - } - ); - - const [_, tx] = useTransition(); - const getSize = (size: number) => { - let hz = ""; - if (size < 1024) hz = size + " B"; - else if (size < 1024 * 1024) hz = (size / 1024).toFixed(2) + " KB"; - else if (size < 1024 * 1024 * 1024) - hz = (size / 1024 / 1024).toFixed(2) + " MB"; - else hz = (size / 1024 / 1024 / 1024).toFixed(2) + " GB"; - return hz; - }; - const loadImage: any = (imageSrc: any) => - new Promise((resolve) => { - const image = new Image(); - image.onload = () => { - const height = image.height; - const width = image.width; - resolve({ image, width, height }); - }; - image.src = imageSrc; - }); - const onUpload: React.ChangeEventHandler = async function ( - e - ) { - const files = e.currentTarget.files; - if (files) { - } - }; - - return ( - <> - {local.ready ? ( - <> -
-
-
-
- {" "} - {local.list.length ? ( - <> - {local.list.map((e, idx) => { - const bgurl = `${siteApiUrl}${get(e, "url")}`; - return ( -
{ - const { width, height } = await loadImage( - `${siteApiUrl}${get(e, "url")}` - ); - local.preview = { - url: `${siteApiUrl}${get(e, "url")}`, - dimension: { - width, - height, - }, - details: e, - }; - local.isPreview = true; - meta.selectUrl = `${siteApiUrl}${get(e, "url")}`; - meta.render(); - }} - onMouseEnter={() => { - local.hover = `${siteApiUrl}${get(e, "url")}`; - local.render(); - }} - onMouseLeave={() => { - local.hover = null; - local.render(); - }} - > -
- {local.hover === - `${siteApiUrl}${get(e, "url")}` ? ( - <> - {" "} -
{ - ev.preventDefault(); - ev.stopPropagation(); - await fetchSendApi( - `${siteApiUrl}/_delete`, - { - path: get(e, "path"), - } - ); - let list = local.list.filter( - (x) => get(x, "path") !== get(e, "path") - ); - local.list = list; - local.render(); - }} - className="relative flex flex-row p-2 cursor-pointer bg-red-500 rounded font-medium" - > -
- - - -
-
- - ) : ( - <> - )} -
-
- ); - })} - - ) : ( - <>No Image - )} -
-
-
- {local.isPreview ? ( - <> -
- -
-
-

- Dimension:{" "} - {`${local.preview.dimension.width} x ${local.preview.dimension.height}`}{" "} -

-

- File Size:{" "} - {getSize(local.preview.details.size)} -

-

- Last Modified:{" "} - - {format( - new Date(local.preview.details.detail.mtime), - "d MMMM yyyy" - )} - {" "} -

-
- - ) : ( - <> - )} -
- - ) : ( - <> -
- -
- - )} - - ); -}; diff --git a/bun.lockb b/bun.lockb index 3669b7c8..93dd0eed 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 5b5260f6..138ac395 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,5 @@ "peerDependencies": { "typescript": "^5.0.0" }, - "dependencies": {} + "dependencies": { "@node-rs/argon2": "^1.5.2" } } diff --git a/pkgs/web-utils/src/client-frame.ts b/pkgs/web-utils/src/client-frame.ts index 32f6f70f..217d809a 100644 --- a/pkgs/web-utils/src/client-frame.ts +++ b/pkgs/web-utils/src/client-frame.ts @@ -165,34 +165,25 @@ export const createFrameCors = async (url: string, win?: any) => { }; export const fetchSendApi = async ( - _url: string, + rawUrl: string, params: any, parentWindow?: any ) => { let w: any = typeof window === "object" ? window : globalThis; const win = parentWindow || w; - let url = _url; + const url = new URL(rawUrl); let frm: Awaited>; + + const base = `${url.protocol}//${url.host}`; + if (!win.frmapi) { win.frmapi = {}; - - win.frmapi[w.serverurl] = await createFrameCors(w.serverurl, win); + win.frmapi[base] = await createFrameCors(base, win); } - frm = win.frmapi[w.serverurl]; + frm = win.frmapi[base]; - if (url.startsWith("http")) { - const purl = new URL(url); - if (!win.frmapi[purl.host]) { - win.frmapi[purl.host] = await createFrameCors( - `${purl.protocol}//${purl.host}` - ); - } - - frm = win.frmapi[purl.host]; - url = url.substring(`${purl.protocol}//${purl.host}`.length); - } if (!win.apiHeaders) { win.apiHeaders = {}; } @@ -204,5 +195,5 @@ export const fetchSendApi = async ( }); } - return await frm.send(url, params, win.apiHeaders); + return await frm.send(url.pathname, params, win.apiHeaders); };