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