Skip to content

Commit

Permalink
Perf: Multi-object support
Browse files Browse the repository at this point in the history
  • Loading branch information
hodgef committed Dec 22, 2023
1 parent 6a8d9a1 commit fe43017
Show file tree
Hide file tree
Showing 19 changed files with 374 additions and 282 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [12.x]
node-version: [20.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v1
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ on:

jobs:
publish:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [20.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: ${{ matrix.node_version }}
- run: npm install
- run: npm run test

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
if: ${{ github.actor != 'dependabot[bot]' }}
strategy:
matrix:
node-version: [12.x]
node-version: [20.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull_request_dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
if: ${{ github.actor == 'dependabot[bot]' }}
strategy:
matrix:
node-version: [12.x]
node-version: [20.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
Expand Down
44 changes: 36 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"react-dom": "^17.0.2",
"react-is": "^18.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.70.1",
"rollup": "^2.74.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-dts": "^4.2.1",
"rollup-plugin-node-polyfills": "^0.2.1",
Expand Down
7 changes: 6 additions & 1 deletion src/components/Admin/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Handler } from '../Request';
import React from "react";
import { getFlagEmoji, wrapReactPage } from '../Page';
import { getCurrentUser, getSignedIp } from '../Auth';
import { getCurrentUser, getSignedIp, isUserAdmin } from '../Auth';
import { adminPanelLogin } from './Login';
import { PanelHeader } from './Header';
import { getAllLogEntries, getLogEntries, LogObject } from '../Logging';
import { getBannedSignedIPs } from '../Bans';
import { FIREWALL_RATELIMIT_PREFIX } from '../Firewall';
import { OBN } from '../ObjectBase';
import { apiker } from '../Apiker';
import { res_401 } from '../Response';

export const adminPanelDashboard: Handler = async (params) => {
const user = await getCurrentUser();
Expand All @@ -17,6 +18,10 @@ export const adminPanelDashboard: Handler = async (params) => {
return adminPanelLogin(params);
}

if(!isUserAdmin(user.id)){
return res_401();
}

const latestBans = (await getBannedSignedIPs()).sort((a, b) => b.time - a.time);
const latestVisitors = (await getLogEntries(FIREWALL_RATELIMIT_PREFIX, 5, OBN.RATELIMIT)).sort((a, b) => b.time - a.time);
const allVisitorsCount = (await getAllLogEntries(OBN.RATELIMIT)).length;
Expand Down
12 changes: 5 additions & 7 deletions src/components/Admin/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { Handler } from '../Request';
import React from "react";
import { wrapReactPage } from '../Page';
import { User } from '../Auth';
import { OBN } from '../ObjectBase';

export const adminPanelLogin: Handler = async ({ state }) => {
const latestRequests = await state(OBN.USERS).list({ limit: 1 });
const firstUser = Object.values(latestRequests)[0] as User;
const hasUsers = !!firstUser?.id;
const props = { hasUsers };
const adminIds = await state(OBN.COMMON).get("adminIds");
const hasAdmins = !!adminIds?.length;
const props = { hasAdmins };

return wrapReactPage('AdminPanelLogin', AdminPanelLogin, props);
}

export const AdminPanelLogin: React.FC = ({ hasUsers }) => {
export const AdminPanelLogin: React.FC = ({ hasAdmins }) => {
return (
<div className="m-3">
{hasUsers ? (
{hasAdmins ? (
<React.Fragment>
<h1 className="display-6 mb-3">Login</h1>
<form className="row g-3" method="post" action="/admp">
Expand Down
13 changes: 6 additions & 7 deletions src/components/Admin/Setup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Handler } from '../Request';
import React from "react";
import { getFlagEmoji, wrapReactPage } from '../Page';
import { checkUser, getCurrentUser, getSignedIp, getTokens, registerUserAction, User } from '../Auth';
import { checkUser, getCurrentUser, getSignedIp, getTokens, isUserAdmin, registerUserAction, User } from '../Auth';
import { OBN } from '../ObjectBase';
import cookie from "cookie"
import { adminPanelLogin } from './Login';
Expand All @@ -19,17 +19,16 @@ export const adminPanelSetup: Handler = async (params) => {
* If there's an user, it's a login, otherwise it's a registration
*/
if(email && password){
const latestRequests = await state(OBN.USERS).list({ limit: 1 });
const firstUser = Object.values(latestRequests)[0] as User;
const hasUsers = !!firstUser?.id;
const adminIds = await state(OBN.COMMON).get("adminIds");
const hasAdmins = !!adminIds?.length;

if(hasUsers) {
if(hasAdmins) {
user = await checkUser(email, password);
} else {
user = await registerUserAction(email, password, { role: "admin" });
}

if(user){
if(user && await isUserAdmin(user.id)){
const { token } = getTokens(user?.id, 60);
apiker.responseHeaders.set('Set-Cookie', cookie.serialize('apikerToken', `Bearer ${token}`, {
sameSite: true,
Expand All @@ -51,7 +50,7 @@ export const adminPanelSetup: Handler = async (params) => {
return adminPanelLogin(params);
}

if(user?.role !== "admin"){
if(!await isUserAdmin(user.id)){
return res_401();
}

Expand Down
2 changes: 2 additions & 0 deletions src/components/Apiker/Apiker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OBMT } from "../ObjectBase";
import ObjectBase from "../ObjectBase/ObjectBase";
import { handleEntryRequest, RequestParams } from "../Request";
import type { Controllers, EmailOptions, Firewall, ObjectStateMapping, Options, Routes, Timings } from "./interfaces";
import { ResponseParams } from "./utils";

class Apiker {
name = "Apiker";
Expand All @@ -16,6 +17,7 @@ class Apiker {
authRoutes!: boolean;
env: any;
requestParams!: RequestParams;
responseParams = new ResponseParams();
responseHeaders!: Headers;
firewall!: Firewall | boolean;
adminPanel!: boolean;
Expand Down
3 changes: 2 additions & 1 deletion src/components/Apiker/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./Apiker";
export * from "./interfaces";
export * from "./interfaces";
export * from "./utils";
19 changes: 19 additions & 0 deletions src/components/Apiker/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export class ResponseParams {
errors: string[] = [];

/**
* Sets an error response
*/
setError = (errorMessage: string) => {
this.errors.push(errorMessage);
}

/**
* Gets the last error response
*/
getLastError = (): string | null => {
return this.errors.length ?
[...this.errors].pop() as string
: null;
}
}
7 changes: 6 additions & 1 deletion src/components/Auth/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export const AUTH_TOKEN_DURATION_MINS_DEFAULT = 30;
export const REGISTER_REQUEST_LIMIT_AMOUNT_PER_HOUR = 3;
export const AUTH_PREFIX = "auth";
export const AUTH_REGISTER_PREFIX = "auth-register";
export const AUTH_REGISTER_PREFIX = "auth-register";

export const AUTH_ERRORS = {
USER_EXISTS: "User already exists",
ID_GEN_ERROR: "Error generating an user Id"
};
19 changes: 18 additions & 1 deletion src/components/Auth/registerUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OBN } from "../ObjectBase";
import { Handler } from "../Request";
import { res, res_400 } from "../Response";
import { isEmail, isRequiredLength } from "../Validation";
import { AUTH_ERRORS } from "./constants";
import { User } from "./interfaces";
import { getTokens, hash_bcrypt, randomHash_SHA1 } from "./utils";

Expand All @@ -25,7 +26,7 @@ export const registerUser: Handler = async ({ body }) => {
};


export const registerUserAction = async (email: string, password: string, extraParams = {}) => {
export const registerUserAction = async (email: string, password: string, extraParams = {} as any): Promise<User | undefined> => {
const { state } = apiker.requestParams;
if(!isEmail(email) || !isRequiredLength(password)) {
return;
Expand All @@ -37,10 +38,17 @@ export const registerUserAction = async (email: string, password: string, extraP
const currentUserId = await state(OBN.EMAILTOUUID, email).get(email);

if(currentUserId) {
apiker.responseParams.setError(AUTH_ERRORS.USER_EXISTS);
return;
}

const id = randomHash_SHA1();

if(!id){
apiker.responseParams.setError(AUTH_ERRORS.ID_GEN_ERROR);
return;
}

const signedPassword = hash_bcrypt(password);
const user: User = {
id,
Expand All @@ -59,5 +67,14 @@ export const registerUserAction = async (email: string, password: string, extraP
* Create EmailToUUID entry
*/
await state(OBN.EMAILTOUUID, email).put({ [email]: id });

/**
* If it's an admin, create an entry in admin table
*/
if(extraParams.role === "admin"){
const adminIds = await state(OBN.COMMON).get("adminIds") || [];
adminIds.push(id);
await state(OBN.COMMON).put({ adminIds });
}
return user;
}
12 changes: 12 additions & 0 deletions src/components/Auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ export const getCurrentUser = async (): Promise<User | undefined> => {
}
}

export const isUserAdmin = async (userId = ""): Promise<boolean> => {
if(!userId) return false;
const { state } = apiker.requestParams;
const adminIds = await state(OBN.COMMON).get("adminIds");
return adminIds.includes(userId);
}

export const isCurrentUserAdmin = async (): Promise<boolean> => {
const user = await getCurrentUser();
return await isUserAdmin(user?.id);
}

export const getTokens = (userId: string, expirationInMinutes = AUTH_TOKEN_DURATION_MINS_DEFAULT) => {
const clientId = getClientId();
const token = createJWT({ sub: userId, clientId }, expirationInMinutes);
Expand Down
5 changes: 3 additions & 2 deletions src/components/Logging/Logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export const addUniqueLogEntry = async (prefix: string, additionalParams = {}, o
await addLogEntry(prefix, additionalParams, objectName, signedIp, clientId);
};

export const addLogEntry = async (prefix: string, additionalParams = {}, objectName = OBN.LOGS, signedIp: string | undefined = undefined, clientId: string | undefined = undefined) => {
export const addLogEntry = async (prefix: string, additionalParams = {} as any, objectName = OBN.LOGS, signedIp: string | undefined = undefined, clientId: string | undefined = undefined) => {
if(apiker.objects.includes(objectName)){
const { state } = apiker.requestParams;
const { objectId = null } = additionalParams;
const propertyName = getUserLogPropertyName(prefix, signedIp) + Date.now();
state(objectName).put({ [propertyName]: getLogParams(propertyName, signedIp, clientId, additionalParams) });
state(objectName, objectId).put({ [propertyName]: getLogParams(propertyName, signedIp, clientId, additionalParams) });
}
};

Expand Down
Loading

0 comments on commit fe43017

Please sign in to comment.