diff --git a/firebase.json b/firebase.json index 6a4a9a6..1ac189b 100644 --- a/firebase.json +++ b/firebase.json @@ -6,9 +6,13 @@ "hosting": { "public": "public", "rewrites": [ - { - "source": "/testme", - "function": "testme" + { + "source": "/publishDictionary", + "function": "/publishDictionary" + }, + { + "source": "/willError", + "function": "/willError" } ] } diff --git a/functions/src/index.ts b/functions/src/index.ts index d4f2f31..118e158 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,29 +1,24 @@ import * as functions from "firebase-functions"; -import fetch from "node-fetch"; -import cors from "cors"; -// import publish from "./publish"; -// import * as BT from "../../website/src/lib/backend-types" +import * as FT from "../../website/src/lib/functions-types"; +import auth from "./middleware/lingdocs-auth"; +import publish from "./publish"; -export const testme = functions - // .runWith({ - // timeoutSeconds: 200, - // memory: "2GB" - // }) - .https.onRequest((req, res) => { - return cors({ credentials: true, origin: /\.lingdocs\.com$/ })(req, res, () => { - const { headers: { cookie }} = req; - if (!cookie) { - return res.status(401).send({ ok: false, error: "unauthorized" }); - } - fetch("https://account.lingdocs.com/api/user", { - headers: { cookie }, - }).then(r => r.json()).then(r => { - res.send({ ok: true, r }); - }).catch((error) => res.send({ ok: false, error })); +export const publishDictionary = functions.https.onRequest( + auth((req, res: functions.Response) => { + if (req.user.level !== "editor") { + res.status(403).send({ ok: false, error: "403 forbidden" }); return; - }); -}); + } + publish().then(res.send); + }) +); +export const willError = functions.https.onRequest((req, res) => { + auth((req, res: functions.Response) => { + throw new Error("this is an error"); + }) +}) + // TODO: BETTER HANDLING OF EXPRESS MIDDLEWARE export const submissions = functions diff --git a/functions/src/middleware/lingdocs-auth.ts b/functions/src/middleware/lingdocs-auth.ts new file mode 100644 index 0000000..ad26b1c --- /dev/null +++ b/functions/src/middleware/lingdocs-auth.ts @@ -0,0 +1,43 @@ +import cors from "cors"; +import fetch from "node-fetch"; +import type { https, Response } from "firebase-functions"; +import * as FT from "../../../website/src/lib/functions-types"; +import type { LingdocsUser } from "../../../website/src/lib/account-types"; + +const useCors = cors({ credentials: true, origin: /\.lingdocs\.com$/ }); + +interface ReqWUser extends https.Request { + user: LingdocsUser; +} + +/** + * creates a handler to pass to a firebase https.onRequest function + * + */ +export default function makeHandler(toRun: (req: ReqWUser, res: Response) => any | Promise): (req: https.Request, res: Response) => void | Promise { + return function(reqPlain: https.Request, resPlain: Response) { + return useCors(reqPlain, resPlain, async () => { + const { req, res } = await lingdocsAuth(reqPlain, resPlain); + if (!req) { + res.status(401).send({ ok: false, error: "unauthorized" }); + return; + }; + toRun(req, res); + return; + }); + } +} + +async function lingdocsAuth(req: https.Request, res: Response): Promise<{ req: ReqWUser | null, res: Response }> { + const { headers: { cookie }} = req; + if (!cookie) { + return { req: null, res }; + } + const r = await fetch("https://account.lingdocs.com/api/user", { headers: { cookie }}) + const { ok, user } = await r.json(); + if (ok === true && user) { + req.user = user; + return { req: req as ReqWUser, res }; + } + return { req: null, res }; +} \ No newline at end of file diff --git a/website/src/lib/backend-calls.ts b/website/src/lib/backend-calls.ts index e34b69b..3d7be6b 100644 --- a/website/src/lib/backend-calls.ts +++ b/website/src/lib/backend-calls.ts @@ -22,12 +22,11 @@ async function accountApiFetch(url: string, method: "GET" | "POST" | "PUT" | "DE return await response.json() as AT.APIResponse; } -export async function publishDictionary(): Promise { - return { - ok: true, - // @ts-ignore - info: {}, - }; +export async function publishDictionary(): Promise { + const r = await fetch("https://functions.lingdocs.com/publishDictionary", { + credentials: "include", + }); + return (await r.json()) as FT.PublishDictionaryResponse | FT.FunctionError; } export async function upgradeAccount(password: string): Promise { diff --git a/website/src/lib/functions-types.ts b/website/src/lib/functions-types.ts index 8ca80c9..9cafc06 100644 --- a/website/src/lib/functions-types.ts +++ b/website/src/lib/functions-types.ts @@ -9,6 +9,10 @@ import { Types as T } from "@lingdocs/pashto-inflector"; import * as AT from "./account-types"; +export type FunctionResponse = PublishDictionaryResponse | SubmissionsResponse | FunctionError; + +export type FunctionError = { ok: false, error: string }; + export type PublishDictionaryResponse = { ok: true, info: T.DictionaryInfo, diff --git a/website/src/screens/Account.tsx b/website/src/screens/Account.tsx index f53080b..07de905 100644 --- a/website/src/screens/Account.tsx +++ b/website/src/screens/Account.tsx @@ -6,7 +6,7 @@ import { Modal, Button } from "react-bootstrap"; import { upgradeAccount, signOut, - // publishDictionary, + publishDictionary, } from "../lib/backend-calls"; import LoadingElipses from "../components/LoadingElipses"; import { Helmet } from "react-helmet"; @@ -24,16 +24,15 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs const [upgradePassword, setUpgradePassword] = useState(""); const [upgradeError, setUpgradeError] = useState(""); const [waiting, setWaiting] = useState(false); - // const [popup, setPopup] = useState(null); - // const [publishingStatus, setPublishingStatus] = useState(undefined); + const [publishingStatus, setPublishingStatus] = useState(undefined); useEffect(() => { setShowingUpgradePrompt(false); setUpgradeError(""); setWaiting(false); window.addEventListener("message", handleIncomingMessage); - console.log("send test func"); - fetch("https://functions.lingdocs.com/testme", { credentials: "include" }).then((res) => res.text()).then((res) => { - console.log("test func here"); + console.log("send test erroring func"); + fetch("https://functions.lingdocs.com/willError", { credentials: "include" }).then((res) => res.text()).then((res) => { + console.log("test error here"); console.log(res); }); return () => { @@ -76,15 +75,15 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs function handleOpenSignup() { popupRef = window.open("https://account.lingdocs.com"); } - // function handlePublish() { - // setPublishingStatus("publishing"); - // publishDictionary().then((response) => { - // setPublishingStatus(response); - // }).catch((err) => { - // console.error(err); - // setPublishingStatus("Offline or connection error"); - // }); - // } + function handlePublish() { + setPublishingStatus("publishing"); + publishDictionary().then((response) => { + setPublishingStatus(response); + }).catch((err) => { + console.error(err); + setPublishingStatus("Offline or connection error"); + }); + } if (!user) { return
@@ -104,7 +103,7 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs Account - LingDocs Pashto Dictionary

Account

- {/* {user.level === "editor" && + {user.level === "editor" &&

Editor Tools

{publishingStatus !== "publishing" && @@ -123,7 +122,7 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs }
- } */} + }
{/* {user.p &&
avatar