diff --git a/app/client/src/modules/account/components/header/header.component.tsx b/app/client/src/modules/account/components/header/header.component.tsx new file mode 100644 index 0000000..489ce23 --- /dev/null +++ b/app/client/src/modules/account/components/header/header.component.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { useAppSession } from "shared/hooks"; +import { ButtonComponent } from "@openhotel/web-components"; +import styles from "./header.module.scss"; + +export const HeaderComponent: React.FC = () => { + const { account, login, logout } = useAppSession(); + + if (account === undefined) return; + + if (!account) + return ( + <> +
+ + Login + + + ); + + return ( + <> +
+
+ +
+ logout +
+
+ + ); +}; diff --git a/app/client/src/modules/account/components/header/header.module.scss b/app/client/src/modules/account/components/header/header.module.scss new file mode 100644 index 0000000..081dea1 --- /dev/null +++ b/app/client/src/modules/account/components/header/header.module.scss @@ -0,0 +1,11 @@ +.header { + display: flex; + gap: 1rem; + + .username { + } + .logout { + cursor: pointer; + opacity: 0.35; + } +} diff --git a/app/client/src/modules/account/components/header/index.ts b/app/client/src/modules/account/components/header/index.ts new file mode 100644 index 0000000..c2d38d3 --- /dev/null +++ b/app/client/src/modules/account/components/header/index.ts @@ -0,0 +1 @@ +export * from "./header.component"; diff --git a/app/client/src/modules/account/components/index.ts b/app/client/src/modules/account/components/index.ts new file mode 100644 index 0000000..49ac70f --- /dev/null +++ b/app/client/src/modules/account/components/index.ts @@ -0,0 +1 @@ +export * from "./header"; diff --git a/app/client/src/modules/account/index.ts b/app/client/src/modules/account/index.ts new file mode 100644 index 0000000..40b494c --- /dev/null +++ b/app/client/src/modules/account/index.ts @@ -0,0 +1 @@ +export * from "./components"; diff --git a/app/client/src/modules/application/components/providers/providers.component.tsx b/app/client/src/modules/application/components/providers/providers.component.tsx index 5bddd2b..c2ab40b 100644 --- a/app/client/src/modules/application/components/providers/providers.component.tsx +++ b/app/client/src/modules/application/components/providers/providers.component.tsx @@ -1,14 +1,16 @@ import { Outlet } from "react-router"; -import { TitleProvider } from "shared/hooks"; +import { TitleProvider, AppSessionProvider } from "shared/hooks"; import React from "react"; import { ModalProvider } from "@openhotel/web-components"; export const ProvidersComponent = () => { return ( - - - - - + + + + + + + ); }; diff --git a/app/client/src/modules/application/components/wrapper-layout/wrapper-layout.component.tsx b/app/client/src/modules/application/components/wrapper-layout/wrapper-layout.component.tsx index ec184b4..4db7b2b 100644 --- a/app/client/src/modules/application/components/wrapper-layout/wrapper-layout.component.tsx +++ b/app/client/src/modules/application/components/wrapper-layout/wrapper-layout.component.tsx @@ -1,10 +1,16 @@ import React from "react"; import { MainLayoutComponent } from "@openhotel/web-components"; +import { HeaderComponent } from "modules/account"; type Props = { children: React.ReactNode; }; export const WrapperLayoutComponent = ({ children }: Props) => { - return ; + return ( + } + /> + ); }; diff --git a/app/client/src/modules/home/home.component.tsx b/app/client/src/modules/home/home.component.tsx index 8c0b428..e9e9a62 100644 --- a/app/client/src/modules/home/home.component.tsx +++ b/app/client/src/modules/home/home.component.tsx @@ -5,7 +5,11 @@ export const HomeComponent: React.FC = () => { return (

Home

- +

{ > Play! +

); }; diff --git a/app/client/src/shared/enums/index.ts b/app/client/src/shared/enums/index.ts new file mode 100644 index 0000000..af7d66a --- /dev/null +++ b/app/client/src/shared/enums/index.ts @@ -0,0 +1 @@ +export * from "./request.enums"; diff --git a/app/client/src/shared/enums/request.enums.ts b/app/client/src/shared/enums/request.enums.ts new file mode 100644 index 0000000..030677b --- /dev/null +++ b/app/client/src/shared/enums/request.enums.ts @@ -0,0 +1,11 @@ +export enum RequestMethod { + OPTIONS = "OPTIONS", + GET = "GET", + HEAD = "HEAD", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE", + TRACE = "TRACE", + CONNECT = "CONNECT", + PATCH = "PATCH", +} diff --git a/app/client/src/shared/hooks/index.ts b/app/client/src/shared/hooks/index.ts index 19097ee..afd4144 100644 --- a/app/client/src/shared/hooks/index.ts +++ b/app/client/src/shared/hooks/index.ts @@ -1 +1,4 @@ export * from "./useTitle"; +export * from "./useApi"; +export * from "./useAppSession"; +export * from "./useCookies"; diff --git a/app/client/src/shared/hooks/useApi.ts b/app/client/src/shared/hooks/useApi.ts new file mode 100644 index 0000000..48aa43f --- /dev/null +++ b/app/client/src/shared/hooks/useApi.ts @@ -0,0 +1,81 @@ +import { Request } from "shared/types"; +import { RequestMethod } from "../enums"; +import { useCallback } from "react"; + +export const useApi = () => { + const $fetch = useCallback( + async ({ + method = RequestMethod.GET, + pathname, + body, + headers = {}, + cache = true, + rawResponse = false, + preventReload = false, + }: Request) => { + const response = await fetch(`/api/${pathname}`, { + method, + headers: new Headers({ + ...headers, + }), + body, + credentials: "include", + cache: cache ? "default" : "no-store", + }).then(async (data) => { + if (rawResponse) return data; + + const contentType = data.headers.get("content-type"); + + // Check that the response is JSON before calling `.json()`, + // otherwise there is no way to recover the response_text in case of an error + if (contentType && contentType.indexOf("application/json") !== -1) { + try { + return await data.json(); + } catch (e) { + throw { + status: data.status, + message: data.statusText, + response_text: null, + }; + } + } + // If the response is not JSON, throw an error + const response_text = await data.text(); + throw { + status: data.status, + message: response_text.length + ? `${data.status}: Invalid JSON response` + : `${data.status}: Empty response`, + response_text, + }; + }); + + if (rawResponse) return response; + + if (response.status === 403 && !preventReload) { + globalThis.location.reload(); + return; + } + + if (response.status !== 200) throw response; + + return response; + }, + [], + ); + + const getVersion = useCallback(async (): Promise => { + const { + data: { version }, + } = await $fetch({ + method: RequestMethod.GET, + pathname: "/version", + }); + return version; + }, [$fetch]); + + return { + fetch: $fetch, + getVersion, + }; +}; diff --git a/app/client/src/shared/hooks/useAppSession.tsx b/app/client/src/shared/hooks/useAppSession.tsx new file mode 100644 index 0000000..8c0f547 --- /dev/null +++ b/app/client/src/shared/hooks/useAppSession.tsx @@ -0,0 +1,125 @@ +import React, { + ReactNode, + useCallback, + useContext, + useEffect, + useState, +} from "react"; +import { useApi, useCookies } from "shared/hooks"; +import { Account } from "shared/types"; +import { ulid } from "ulidx"; + +type AppSessionState = { + getHeaders: () => Record; + account: Account | null | undefined; + login: () => Promise; + logout: () => void; +}; + +const AppSessionContext = React.createContext(undefined); + +type ProviderProps = { + children: ReactNode; +}; + +export const AppSessionProvider: React.FunctionComponent = ({ + children, +}) => { + const { get, set, remove } = useCookies(); + const { fetch } = useApi(); + + const [account, setAccount] = useState(undefined); + + const params = new URLSearchParams(window.location.hash.replace("#", "?")); + const $accountId = params.get("accountId"); + const $accountToken = params.get("accountToken"); + + const getHeaders = useCallback( + () => ({ + "account-id": $accountId ?? get("account-id"), + "account-token": $accountToken ?? get("account-token"), + }), + [$accountId, $accountToken, get], + ); + + useEffect(() => { + const accountId = $accountId ?? get("account-id"); + const accountToken = $accountToken ?? get("account-token"); + + if (window.location.hash) + window.history.replaceState("", document.title, window.location.pathname); + + (async () => { + const { + data: { enabled }, + } = await fetch({ + pathname: "auth", + }); + + if (!enabled) { + setAccount({ + accountId: ulid(), + username: "development", + admin: true, + languages: ["en"], + }); + return; + } + + if (accountId && accountToken) { + try { + const { status, data } = await fetch({ + pathname: `auth/user`, + headers: getHeaders(), + preventReload: true, + }); + + if (status === 200) { + set("account-id", accountId, 1); + set("account-token", accountToken, 1); + setAccount(data); + return; + } + } catch (e) { + remove("account-id"); + remove("account-token"); + } + } + + setAccount(null); + })(); + }, [fetch, getHeaders, setAccount, remove, set, ulid]); + + const login = useCallback(async () => { + if (account) return; + + const { data } = await fetch({ + pathname: "auth/redirect", + }); + window.location.replace(data.url); + }, []); + + const logout = useCallback(() => { + console.log(account); + if (!account) return; + + remove("account-id"); + remove("account-token"); + setAccount(null); + }, [remove, setAccount, account]); + + return ( + + ); +}; + +export const useAppSession = (): AppSessionState => + useContext(AppSessionContext); diff --git a/app/client/src/shared/hooks/useCookies.ts b/app/client/src/shared/hooks/useCookies.ts new file mode 100644 index 0000000..c02b51b --- /dev/null +++ b/app/client/src/shared/hooks/useCookies.ts @@ -0,0 +1,21 @@ +import Cookies from "js-cookie"; +import { useCallback, useMemo } from "react"; + +export const useCookies = () => { + const set = useCallback((key: string, value: string, expires?: number) => { + Cookies.set(key, value, { + expires, + sameSite: "strict", + secure: true, + }); + }, []); + + const get = useMemo(() => Cookies.get, []); + const remove = useMemo(() => Cookies.remove, []); + + return { + set, + get, + remove, + }; +}; diff --git a/app/client/src/shared/types/account.types.ts b/app/client/src/shared/types/account.types.ts new file mode 100644 index 0000000..a80e669 --- /dev/null +++ b/app/client/src/shared/types/account.types.ts @@ -0,0 +1,6 @@ +export type Account = { + accountId: string; + username: string; + languages: string[]; + admin?: boolean; +}; diff --git a/app/client/src/shared/types/index.ts b/app/client/src/shared/types/index.ts new file mode 100644 index 0000000..b66aa04 --- /dev/null +++ b/app/client/src/shared/types/index.ts @@ -0,0 +1,2 @@ +export * from "./request.types"; +export * from "./account.types"; diff --git a/app/client/src/shared/types/request.types.ts b/app/client/src/shared/types/request.types.ts new file mode 100644 index 0000000..57054c0 --- /dev/null +++ b/app/client/src/shared/types/request.types.ts @@ -0,0 +1,11 @@ +import { RequestMethod } from "shared/enums"; + +export type Request = { + method?: RequestMethod; + pathname: string; + body?: any; + headers?: Record; + cache?: boolean; + rawResponse?: boolean; + preventReload?: boolean; +}; diff --git a/app/server/deno.json b/app/server/deno.json index 1a726bf..9d3e371 100644 --- a/app/server/deno.json +++ b/app/server/deno.json @@ -21,6 +21,7 @@ "@oh/utils": "jsr:@oh/utils@1.5.2", "shared/": "./src/shared/", + "system/": "./src/system/", "modules/": "./src/modules/", "deno/": "https://deno.land/std@0.221.0/", diff --git a/app/server/mod.ts b/app/server/mod.ts index 19d69bc..5012b0a 100644 --- a/app/server/mod.ts +++ b/app/server/mod.ts @@ -1,4 +1,4 @@ -import { System } from "modules/system/main.ts"; +import { System } from "system/main.ts"; import { getProcessedEnvs } from "shared/utils/main.ts"; const envs = getProcessedEnvs({ diff --git a/app/server/src/main.ts b/app/server/src/main.ts new file mode 100644 index 0000000..ec02433 --- /dev/null +++ b/app/server/src/main.ts @@ -0,0 +1 @@ +export * from "./system/main.ts"; diff --git a/app/server/src/modules/api/auth/main.request.ts b/app/server/src/modules/api/auth/main.request.ts new file mode 100644 index 0000000..ab3ca42 --- /dev/null +++ b/app/server/src/modules/api/auth/main.request.ts @@ -0,0 +1,22 @@ +import { RequestType, RequestMethod } from "@oh/utils"; +import { System } from "system/main.ts"; + +export const getRequest: RequestType = { + method: RequestMethod.GET, + pathname: "", + func: (request) => { + const { + auth: { enabled }, + } = System.getConfig(); + + return Response.json( + { + status: 200, + data: { + enabled, + }, + }, + { status: 200 }, + ); + }, +}; diff --git a/app/server/src/modules/api/auth/main.ts b/app/server/src/modules/api/auth/main.ts new file mode 100644 index 0000000..dc21f76 --- /dev/null +++ b/app/server/src/modules/api/auth/main.ts @@ -0,0 +1,10 @@ +import { RequestType, getPathRequestList } from "@oh/utils"; + +import { redirectRequest } from "./redirect.request.ts"; +import { userRequest } from "./user.request.ts"; +import { getRequest } from "./main.request.ts"; + +export const authList: RequestType[] = getPathRequestList({ + requestList: [getRequest, redirectRequest, userRequest], + pathname: "/auth", +}); diff --git a/app/server/src/modules/api/auth/redirect.request.ts b/app/server/src/modules/api/auth/redirect.request.ts new file mode 100644 index 0000000..ef0dd8d --- /dev/null +++ b/app/server/src/modules/api/auth/redirect.request.ts @@ -0,0 +1,26 @@ +import { RequestType, RequestMethod } from "@oh/utils"; +import { System } from "system/main.ts"; +import { getTokenData } from "@oh/utils"; + +export const redirectRequest: RequestType = { + method: RequestMethod.GET, + pathname: "/redirect", + func: (request) => { + const { + auth: { url: authUrl, appToken }, + } = System.getConfig(); + const { id } = getTokenData(appToken); + + const $url = new URL(authUrl + "/apps"); + $url.searchParams.append("appId", id); + return Response.json( + { + status: 200, + data: { + url: $url, + }, + }, + { status: 200 }, + ); + }, +}; diff --git a/app/server/src/modules/api/auth/user.request.ts b/app/server/src/modules/api/auth/user.request.ts new file mode 100644 index 0000000..e4294bf --- /dev/null +++ b/app/server/src/modules/api/auth/user.request.ts @@ -0,0 +1,27 @@ +import { RequestType, RequestMethod, RequestKind } from "@oh/utils"; +import { System } from "system/main.ts"; + +export const userRequest: RequestType = { + method: RequestMethod.GET, + pathname: "/user", + kind: RequestKind.ACCOUNT, + func: async (request: Request) => { + const accountId = request.headers.get("account-id"); + const accountToken = request.headers.get("account-token"); + const { + auth: { url: authUrl, appToken }, + } = System.getConfig(); + + const data = await fetch(`${authUrl}/api/v3/user/@me`, { + headers: { + "app-token": appToken, + "account-id": accountId, + "account-token": accountToken, + }, + }).then((response) => response.json()); + + return Response.json(data, { + status: data.status, + }); + }, +}; diff --git a/app/server/src/modules/api/v1/main.ts b/app/server/src/modules/api/main.ts similarity index 61% rename from app/server/src/modules/api/v1/main.ts rename to app/server/src/modules/api/main.ts index b6b02e3..4cd3336 100644 --- a/app/server/src/modules/api/v1/main.ts +++ b/app/server/src/modules/api/main.ts @@ -1,8 +1,9 @@ import { RequestType, getPathRequestList } from "@oh/utils"; import { miscRequestList } from "./misc/main.ts"; +import { authList } from "./auth/main.ts"; export const requestV1List: RequestType[] = getPathRequestList({ - requestList: [...miscRequestList], - pathname: "/api/v1", + requestList: [...miscRequestList, ...authList], + pathname: "/api", }); diff --git a/app/server/src/modules/api/v1/misc/main.ts b/app/server/src/modules/api/misc/main.ts similarity index 100% rename from app/server/src/modules/api/v1/misc/main.ts rename to app/server/src/modules/api/misc/main.ts diff --git a/app/server/src/modules/api/v1/misc/version.http b/app/server/src/modules/api/misc/version.http similarity index 100% rename from app/server/src/modules/api/v1/misc/version.http rename to app/server/src/modules/api/misc/version.http diff --git a/app/server/src/modules/api/v1/misc/version.request.ts b/app/server/src/modules/api/misc/version.request.ts similarity index 88% rename from app/server/src/modules/api/v1/misc/version.request.ts rename to app/server/src/modules/api/misc/version.request.ts index 27c03c9..6993a5f 100644 --- a/app/server/src/modules/api/v1/misc/version.request.ts +++ b/app/server/src/modules/api/misc/version.request.ts @@ -5,7 +5,7 @@ import { HttpStatusCode, RequestKind, } from "@oh/utils"; -import { System } from "modules/system/main.ts"; +import { System } from "system/main.ts"; export const versionRequest: RequestType = { method: RequestMethod.GET, diff --git a/app/server/src/modules/system/api.ts b/app/server/src/modules/system/api.ts deleted file mode 100644 index daa8d9c..0000000 --- a/app/server/src/modules/system/api.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - getApiHandler, - getContentType, - getCORSHeaders, - getResponse, - HttpStatusCode, - RequestMethod, - RequestKind, -} from "@oh/utils"; -import { System } from "./main.ts"; -import { requestV1List } from "modules/api/v1/main.ts"; - -export const api = () => { - const load = (testMode: boolean = false) => { - const $apiHandler = getApiHandler({ - testMode, - requests: requestV1List, - checkAccess: checkAccess, - }); - - $apiHandler.overview(); - - const { version, port } = System.getConfig(); - const isDevelopment = version === "development"; - Deno.serve( - { - port: port * (isDevelopment ? 10 : 1), - }, - async ($request: Request, connInfo) => { - const headers = new Headers($request.headers); - headers.set("remote-address", connInfo.remoteAddr.hostname); - const request = new Request($request, { headers }); - - try { - const { url, method } = request; - if (method === RequestMethod.OPTIONS) { - return new Response(null, { - headers: getCORSHeaders(), - status: 204, - }); - } - - const parsedUrl = new URL(url); - - if (!parsedUrl.pathname.startsWith("/api")) { - try { - const fileData = await Deno.readFile( - "./client" + parsedUrl.pathname, - ); - - return new Response(fileData, { - headers: { - "Content-Type": getContentType(parsedUrl.pathname), - }, - }); - } catch (e) { - return new Response( - await Deno.readTextFile(`./client/index.html`), - { - headers: { - "content-type": "text/html", - }, - }, - ); - } - } - - return await $apiHandler.on(request); - } catch (e) { - console.log(e); - } - return getResponse(HttpStatusCode.INTERNAL_SERVER_ERROR); - }, - ); - }; - - const checkAccess = async ({ - request, - kind, - }: { - request: Request; - kind: RequestKind | RequestKind[]; - }): Promise => { - const check = async (kind: RequestKind) => { - // const accountId = request.headers.get("account-id"); - // const licenseToken = request.headers.get("license-token"); - // let account: AccountMutable; - - switch (kind) { - case RequestKind.PUBLIC: - return true; - // case RequestKind.ACCOUNT_REFRESH: - // if (!accountId) return false; - // - // account = await System.accounts.getAccount({ accountId }); - // if (!account) return false; - // - // return await account.checkRefreshToken(request); - // case RequestKind.ACCOUNT: - // if (!accountId) return false; - // - // account = await System.accounts.getAccount({ accountId }); - // if (!account) return false; - // - // return await account.checkToken(request); - // case RequestKind.LICENSE: { - // if (!licenseToken) return false; - // - // const licenseHotel = await System.hotels.getHotel({ licenseToken }); - // return Boolean(licenseHotel); - // } - // case RequestKind.CONNECTION: { - // if (!licenseToken) return false; - // - // const hotel = await System.hotels.getHotel({ licenseToken }); - // if (!hotel) return false; - // - // account = await System.accounts.getAccount({ request }); - // if (!account) return false; - // - // const connection = await account.connections.active.get(); - // if (!connection) return false; - // - // const hotelData = hotel.getObject(); - // if (hotelData.hotelId !== connection.hotelId) return false; - // - // const hotelIntegration = hotel.integrations.getIntegration( - // connection.integrationId, - // ); - // return Boolean(hotelIntegration); - // } - // case RequestKind.ADMIN: { - // if (!accountId) return false; - // - // account = await System.accounts.getAccount({ accountId }); - // if (!account || !(await account.checkToken(request))) return false; - // - // const { version } = System.getConfig(); - // const isDevelopment = version === "development"; - // - // const isAdmin = await account.isAdmin(); - // const isVerified = await account.otp.isVerified(); - // - // return isAdmin && (isDevelopment || isVerified); - // } - // case RequestKind.TOKEN: { - // const appToken = request.headers.get("app-token"); - // return appToken && (await System.tokens.verify(appToken)); - // } - // case RequestKind.APPS: { - // const $appToken = request.headers.get("app-token"); - // return $appToken && (await System.apps.verify($appToken)); - // } - default: - return false; - } - }; - - return Array.isArray(kind) - ? (await Promise.all(kind.map(check))).includes(true) - : check(kind); - }; - - return { - load, - }; -}; diff --git a/app/server/src/shared/consts/config.consts.ts b/app/server/src/shared/consts/config.consts.ts index 6e5138e..4b902a7 100644 --- a/app/server/src/shared/consts/config.consts.ts +++ b/app/server/src/shared/consts/config.consts.ts @@ -21,4 +21,9 @@ export const CONFIG_DEFAULT: ConfigTypes = { database: { filename: "database", }, + auth: { + enabled: false, + appToken: "", + url: "http://localhost:2024", + }, }; diff --git a/app/server/src/shared/enums/main.ts b/app/server/src/shared/enums/main.ts index e69de29..c07b17f 100644 --- a/app/server/src/shared/enums/main.ts +++ b/app/server/src/shared/enums/main.ts @@ -0,0 +1 @@ +export * from "./scopes.enums.ts"; diff --git a/app/server/src/shared/enums/scopes.enums.ts b/app/server/src/shared/enums/scopes.enums.ts new file mode 100644 index 0000000..cc097c9 --- /dev/null +++ b/app/server/src/shared/enums/scopes.enums.ts @@ -0,0 +1,9 @@ +export enum Scope { + ONET_MESSAGES_READ = "onet.messages.read", + ONET_MESSAGES_WRITE = "onet.messages.write", + + ONET_FRIENDS_READ = "onet.friends.read", + ONET_FRIENDS_WRITE = "onet.friends.write", + + ONET_TELEPORTS_LINK = "onet.teleports.link", +} diff --git a/app/server/src/shared/types/config.types.ts b/app/server/src/shared/types/config.types.ts index 6a56710..316cfcf 100644 --- a/app/server/src/shared/types/config.types.ts +++ b/app/server/src/shared/types/config.types.ts @@ -19,4 +19,9 @@ export type ConfigTypes = { database: { filename: string; }; + auth: { + enabled: boolean; + appToken: string; + url: string; + }; }; diff --git a/app/server/src/system/accounts.ts b/app/server/src/system/accounts.ts new file mode 100644 index 0000000..555f952 --- /dev/null +++ b/app/server/src/system/accounts.ts @@ -0,0 +1,34 @@ +import { getRandomString } from "@oh/utils"; + +type Account = { + accountId: string; + username: string; + admin: boolean; + languages: string[]; + + token?: string; +}; + +export const accounts = () => { + let accountMap: Record = {}; + + const set = (fingerprint: string, account: Account) => { + account.token = getRandomString(64); + accountMap[fingerprint] = account; + return account.token; + }; + + const get = (fingerprint: string, token: string) => { + const account = { ...accountMap[fingerprint] }; + + if (account.token !== token) return null; + + delete account.token; + return account; + }; + + return { + set, + get, + }; +}; diff --git a/app/server/src/system/api.ts b/app/server/src/system/api.ts new file mode 100644 index 0000000..00ad3a0 --- /dev/null +++ b/app/server/src/system/api.ts @@ -0,0 +1,109 @@ +import { + getApiHandler, + getContentType, + getCORSHeaders, + getResponse, + HttpStatusCode, + RequestMethod, + RequestKind, +} from "@oh/utils"; +import { System } from "./main.ts"; +import { requestV1List } from "modules/api/main.ts"; + +export const api = () => { + const load = (testMode: boolean = false) => { + const $apiHandler = getApiHandler({ + testMode, + requests: requestV1List, + checkAccess: checkAccess, + }); + + $apiHandler.overview(); + + const { version, port } = System.getConfig(); + const isDevelopment = version === "development"; + Deno.serve( + { + port: port * (isDevelopment ? 10 : 1), + }, + async ($request: Request, connInfo) => { + const headers = new Headers($request.headers); + headers.set("remote-address", connInfo.remoteAddr.hostname); + const request = new Request($request, { headers }); + + try { + const { url, method } = request; + if (method === RequestMethod.OPTIONS) { + return new Response(null, { + headers: getCORSHeaders(), + status: 204, + }); + } + + const parsedUrl = new URL(url); + + if (!parsedUrl.pathname.startsWith("/api")) { + try { + const fileData = await Deno.readFile( + "./client" + parsedUrl.pathname, + ); + + return new Response(fileData, { + headers: { + "Content-Type": getContentType(parsedUrl.pathname), + }, + }); + } catch (e) { + return new Response( + await Deno.readTextFile(`./client/index.html`), + { + headers: { + "content-type": "text/html", + }, + }, + ); + } + } + + return await $apiHandler.on(request); + } catch (e) { + console.log(e); + } + return getResponse(HttpStatusCode.INTERNAL_SERVER_ERROR); + }, + ); + }; + + const checkAccess = async ({ + request, + kind, + }: { + request: Request; + kind: RequestKind | RequestKind[]; + }): Promise => { + const check = async (kind: RequestKind) => { + const accountId = request.headers.get("account-id"); + const accountToken = request.headers.get("account-token"); + + if (!System.getConfig().auth.enabled) return true; + + switch (kind) { + case RequestKind.PUBLIC: + return true; + case RequestKind.ACCOUNT: + if (!accountId || !accountToken) return false; + return true; + default: + return false; + } + }; + + return Array.isArray(kind) + ? (await Promise.all(kind.map(check))).includes(true) + : check(kind); + }; + + return { + load, + }; +}; diff --git a/app/server/src/modules/system/backups.ts b/app/server/src/system/backups.ts similarity index 90% rename from app/server/src/modules/system/backups.ts rename to app/server/src/system/backups.ts index e42c8c3..a676c21 100644 --- a/app/server/src/modules/system/backups.ts +++ b/app/server/src/system/backups.ts @@ -1,4 +1,4 @@ -import { System } from "modules/system/main.ts"; +import { System } from "./main.ts"; export const backups = () => { let abortCronController: AbortController = new AbortController(); diff --git a/app/server/src/modules/system/main.ts b/app/server/src/system/main.ts similarity index 91% rename from app/server/src/modules/system/main.ts rename to app/server/src/system/main.ts index e93928e..4422329 100644 --- a/app/server/src/modules/system/main.ts +++ b/app/server/src/system/main.ts @@ -2,8 +2,9 @@ import { api } from "./api.ts"; import { ConfigTypes, Envs } from "shared/types/main.ts"; import { getConfig as $getConfig, getDb, update, DbMutable } from "@oh/utils"; import { CONFIG_DEFAULT } from "shared/consts/config.consts.ts"; -import { Migrations } from "modules/migrations/main.ts"; -import { backups } from "modules/system/backups.ts"; +import { Migrations } from "./migrations/main.ts"; +import { backups } from "./backups.ts"; +import { accounts } from "./accounts.ts"; export const System = (() => { let $config: ConfigTypes; @@ -13,6 +14,7 @@ export const System = (() => { const $api = api(); const $backups = backups(); + const $accounts = accounts(); let $db: DbMutable; const load = async (envs: Envs, testMode: boolean = false) => { @@ -87,5 +89,6 @@ export const System = (() => { }, api: $api, backups: $backups, + accounts: $accounts, }; })(); diff --git a/app/server/src/modules/migrations/9999-test.migration.ts b/app/server/src/system/migrations/9999-test.migration.ts similarity index 100% rename from app/server/src/modules/migrations/9999-test.migration.ts rename to app/server/src/system/migrations/9999-test.migration.ts diff --git a/app/server/src/modules/migrations/main.ts b/app/server/src/system/migrations/main.ts similarity index 100% rename from app/server/src/modules/migrations/main.ts rename to app/server/src/system/migrations/main.ts