Skip to content
Merged

Next #395

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
18ce292
fix: remove "npm version patch" from package.json template in create …
yaroslav8765 Oct 6, 2025
22d4959
docs: update example for passkeys in TwoFactorAuth
yaroslav8765 Oct 6, 2025
8c45a64
Merge pull request #385 from devforth/AdminForth/884
ivictbor Oct 6, 2025
0ca5f6b
feat: enforce JSON-only content type for mutation methods in ExpressS…
ivictbor Oct 6, 2025
6406840
fix: adjust margin for settings view template
yaroslav8765 Oct 7, 2025
94392f2
docs: update docs for passkeys
yaroslav8765 Oct 7, 2025
ab84e7c
fix: remove sidebarAndHeader meta property from settings route
ivictbor Oct 7, 2025
b0e4c0c
Merge branch 'next' of github.com:devforth/adminforth into next
ivictbor Oct 7, 2025
6244254
fix: add cache control headers to get_base_config endpoint
yaroslav8765 Oct 7, 2025
f3d223a
fix: add migration for legacy custom layout to update sidebarAndHeade…
yaroslav8765 Oct 7, 2025
2620e89
Merge pull request #386 from devforth/AdminForth/895
ivictbor Oct 7, 2025
b427222
fix: update sidebar component styles and adjust widths for expanded a…
SerVitasik Oct 8, 2025
90e67bc
fix: adjust sidebar margin for icon-only view in App.vue
SerVitasik Oct 8, 2025
d7fc4c0
feat: add ability to style setting button in user menu
yaroslav8765 Oct 9, 2025
8fcf305
fix: add right shadow to sidebar
SerVitasik Oct 9, 2025
f20be31
fix: enhance sidebar shadow styling for improved visibility
SerVitasik Oct 9, 2025
7013a2e
fix: adjust table height for better layout consistency in ResourceLis…
SerVitasik Oct 9, 2025
458f557
fix: fix database path in .env.sample for dev demo setup
yaroslav8765 Oct 9, 2025
1052204
fix: update SQL play URL in clicks resource configuration
yaroslav8765 Oct 9, 2025
59bc231
fix: update sidebar shadow styles for collapsed state in dark mode
SerVitasik Oct 10, 2025
37fd2a0
Merge branch 'next' of github.com:devforth/adminforth into next
SerVitasik Oct 10, 2025
4407eb3
docs: update docs for the insequreRawSql example
yaroslav8765 Oct 10, 2025
39c66fa
chore: edit comments for the customHeadItems, so i won't break buildi…
yaroslav8765 Oct 10, 2025
f865470
feat: move login error message to the bottom of authentication modal
yaroslav8765 Oct 10, 2025
ce6d14f
fix: fix login error message positioning
yaroslav8765 Oct 10, 2025
0a9c3f5
fix: fix padding for the login error message
yaroslav8765 Oct 10, 2025
6abacc3
fix: correct formatting of adapter list in install-adapters.sh
ivictbor Oct 10, 2025
b54dd48
Merge pull request #387 from devforth/forbidCacheForGetBaseConfig
ivictbor Oct 10, 2025
1d65c21
Merge pull request #388 from devforth/AdminForth/915
ivictbor Oct 10, 2025
6d4775c
docs: add raw SQL usage example
yaroslav8765 Oct 13, 2025
7b9dcd2
fix: refactor model generation for generate-models command to use cal…
yaroslav8765 Oct 13, 2025
ed136dd
fix: teleport tooltip to body
yaroslav8765 Oct 13, 2025
edcd2bd
docs: add docs for the 2fa modal
yaroslav8765 Oct 13, 2025
bf124da
add passkey
ivictbor Oct 13, 2025
73bd03c
fix: correct typo in documentation for custom SQL queries
ivictbor Oct 13, 2025
81617ba
fix: correct typo in documentation regarding SQL injection mitigation
ivictbor Oct 13, 2025
94b8c01
Merge pull request #390 from devforth/AdminForth/573
ivictbor Oct 13, 2025
3a74ad5
feat: add prop "closable" to the afcl dialog
yaroslav8765 Oct 13, 2025
94af857
fix: remove tooltip from DOM when its hidden
yaroslav8765 Oct 13, 2025
1c92c17
Merge pull request #392 from devforth/AdminForth/911
ivictbor Oct 13, 2025
58efa3c
docs: improve 2fa plugin docs
ivictbor Oct 14, 2025
3b39e75
Update 02-TwoFactorsAuth.md
ivictbor Oct 14, 2025
218fc29
feat: add ability to pass headers in callApi function
yaroslav8765 Oct 14, 2025
d32f7a1
Merge pull request #394 from devforth/AdminForth/926
yaroslav8765 Oct 14, 2025
44f12d6
fix: handle null and undefined values for DECIMAL type in getFieldVal…
NoOne7135 Oct 14, 2025
aff3b9e
Merge pull request #393 from devforth/AdminForth/906
ivictbor Oct 14, 2025
28ed53f
fix: refactor config loading and model generation to use getAdminInst…
yaroslav8765 Oct 14, 2025
bbccede
fix: fix "adminforth component" command
yaroslav8765 Oct 15, 2025
59b6de2
feat: add ability to sort injections by meta.afOrder
ivictbor Oct 15, 2025
a019eef
Merge branch 'next' of github.com:devforth/adminforth into next
ivictbor Oct 15, 2025
09a40da
Merge pull request #391 from devforth/AdminForth/514
ivictbor Oct 15, 2025
dad5e4d
feat: enhance injection sorting by preserving initial order
ivictbor Oct 15, 2025
023425d
Merge branch 'next' of github.com:devforth/adminforth into next
ivictbor Oct 15, 2025
9ecd737
feat: add underLoginInjection and order prop for login injection comp…
yaroslav8765 Oct 15, 2025
eb01fe7
fix: refactor injection validation and sorting logic for improved mai…
yaroslav8765 Oct 15, 2025
f086b33
fix: remove console log for loading config path in getAdminInstance f…
yaroslav8765 Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions adapters/install-adapters.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses \
adminforth-email-adapter-mailgun adminforth-google-oauth-adapter adminforth-github-oauth-adapter \
adminforth-facebook-oauth-adapter adminforth-keycloak-oauth-adapter adminforth-microsoft-oauth-adapter \
adminforth-twitch-oauth-adapter adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 \
adminforth-facebook-oauth-adapter adminforth-keycloak-oauth-adapter adminforth-microsoft-oauth-adapter \
adminforth-twitch-oauth-adapter adminforth-image-generation-adapter-openai adminforth-storage-adapter-amazon-s3 \
adminforth-storage-adapter-local adminforth-image-vision-adapter-openai adminforth-key-value-adapter-ram \
adminforth-login-captcha-adapter-cloudflare adminforth-login-captcha-adapter-recaptcha"

Expand Down
20 changes: 15 additions & 5 deletions adminforth/commands/createCustomComponent/configLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import fs from 'fs/promises';
import path from 'path';
import chalk from 'chalk';
import jiti from 'jiti';
import dotenv from "dotenv";
import dotenv, { config } from "dotenv";

dotenv.config({ path: '.env.local', override: true });
dotenv.config({ path: '.env', override: true });

export async function loadAdminForthConfig() {
export async function getAdminInstance() {
const configFileName = 'index.ts';
const configPath = path.resolve(process.cwd(), configFileName);

try {
await fs.access(configPath);
} catch (error) {
console.error(chalk.red(`\nError: Configuration file not found at ${configPath}`));
console.error(chalk.yellow(`Please ensure you are running this command from your project's root directory and the '${configFileName}' file exists.`));
process.exit(1);
return null;
}

try {
Expand All @@ -29,8 +28,19 @@ export async function loadAdminForthConfig() {
const configModule = _require(configPath);

const adminInstance = configModule.admin || configModule.default?.admin;
return { adminInstance, configPath, configFileName };
} catch (error) {
console.error(chalk.red(`\nError loading or parsing configuration file: ${configPath}`));
console.error(error);
return null;
}
}

export async function loadAdminForthConfig() {

const { adminInstance, configPath, configFileName } = await getAdminInstance();

try {
if (!adminInstance) {
throw new Error(`Could not find 'admin' export in ${configFileName}. Please ensure your config file exports the AdminForth instance like: 'export const admin = new AdminForth({...});'`);
}
Expand All @@ -53,7 +63,7 @@ export async function loadAdminForthConfig() {
return config;

} catch (error) {
console.error(chalk.red(`\nError loading or parsing configuration file: ${configPath}`));
console.error(chalk.red(`\nError loading or parsing configuration file: ${configPath}, error: ${error}`));
console.error(error);
process.exit(1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/ && npm version patch"
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
},
"keywords": [],
"author": "",
Expand Down
52 changes: 30 additions & 22 deletions adminforth/commands/generateModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import fs from "fs";
import path from "path";
import { toPascalCase, mapToTypeScriptType, getInstance } from "./utils.js";
import dotenv from "dotenv";
import { callTsProxy } from "./callTsProxy.js";
import { getAdminInstance } from "../commands/createCustomComponent/configLoader.js";

const envFileArg = process.argv.find((arg) => arg.startsWith("--env-file="));
const envFilePath = envFileArg ? envFileArg.split("=")[1] : ".env";
Expand All @@ -23,39 +25,45 @@ async function generateModels() {
}

let modelContent = "// Generated model file\n\n";
const files = fs.readdirSync(currentDirectory);
let instanceFound = false;

for (const file of files) {
if (file.endsWith(".js") || file.endsWith(".ts")) {
const instance = await getInstance(file, currentDirectory);
if (instance) {
await instance.discoverDatabases();
instanceFound = true;
instance.config.resources.forEach((resource) => {
if (resource.columns) {
modelContent += `export type ${toPascalCase(
resource.resourceId
)} = {\n`;
resource.columns.forEach((column) => {
if (column.name && column.type) {
modelContent += ` ${column.name}: ${mapToTypeScriptType(
column.type
)};\n`;
const { adminInstance, configPath, configFileName } = await getAdminInstance();
if (adminInstance) {
await adminInstance.discoverDatabases();
instanceFound = true;
for (const resource of adminInstance.config.resources) {
if (resource.columns) {
const typeName = toPascalCase(resource.resourceId);
const tsCode = `
export async function exec() {
const columns = ${JSON.stringify(resource.columns)};
const typeName = "${typeName}";
function mapToTypeScriptType(type) {
const map = { "integer": "number", "varchar": "string", "boolean": "boolean", "date": "string", "datetime": "string", "decimal": "number", "float": "number", "json": "Record<string, any>", "text": "string", "string": "string", "time": "string" };
return map[type] || "any";
}

let typeStr = \`export type \${typeName} = {\\n\`;
for (const col of columns) {
if (col.name && col.type) {
typeStr += \` \${col.name}: \${mapToTypeScriptType(col.type)};\\n\`;
}
});
modelContent += `}\n\n`;
}
typeStr += "}\\n\\n";
return typeStr;
}
});
`;

const result = await callTsProxy(tsCode);
modelContent += result;
}
}
};
}

if (!instanceFound) {
console.error("Error: No valid instance found to generate models.");
return;
}

fs.writeFileSync(modelFilePath, modelContent, "utf-8");
console.log(`Generated TypeScript model file: ${modelFilePath}`);
return true;
Expand Down
6 changes: 6 additions & 0 deletions adminforth/dataConnectors/mongo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
return value === null ? null : !!value;
} else if (field.type == AdminForthDataTypes.DECIMAL) {
if (value === null || value === undefined) {
return null;
}
return value?.toString();
}

Expand All @@ -206,6 +209,9 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
return value === null ? null : (value ? true : false);
} else if (field.type == AdminForthDataTypes.DECIMAL) {
if (value === null || value === undefined) {
return null;
}
return Decimal128.fromString(value?.toString());
}
return value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ This way, when admin selects, for example, "Luxury" option for "Apartment Type"

### Custom SQL queries with `insecureRawSQL`

Rarely the sec of Filters supported by AdminForth is not enough for your needs.
Rarely the set of Filters supported by AdminForth is not enough for your needs.
In this case you can use `insecureRawSQL` to write your own part of where clause.

However the vital concern that the SQL passed to DB as is, so if you substitute any user inputs it will not be escaped and can lead to SQL injection. To miticate the issue we recommend using `sqlstring` package which will escape the inputs for you.
However the vital concern that the SQL passed to DB as is, so if you substitute any user inputs it will not be escaped and can lead to SQL injection. To mitigate the issue we recommend using `sqlstring` package which will escape the inputs for you.

```bash
npm i sqlstring
Expand All @@ -163,7 +163,7 @@ import sqlstring from 'sqlstring';
if (filter.field === 'some_json_b_field') {
return {
// check if some_json_b_field->'$.some_field' is equal to filter.value
insecureRawSQL: `some_json_b_field->'$.some_field' = ${sqlstring.escape(filter.value)}`,
insecureRawSQL: `some_json_b_field->>'$.some_field' = ${sqlstring.escape(filter.value)}`,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,39 @@ const users = await admin.resource('adminuser').list(
);
```

## Using a raw SQL in queries.

Rarely you might want to add ciondition for some exotic SQL but still want to keep the rest of API.
Technically it happened that AdminForth allows you to do this also

```js
const minUgcAge = 18;
const usersWithNoUgcAccess = await admin.resource('adminuser').list(
[
Filters.NEQ('role', 'Admin'),
{
insecureRawSQL: `(user_meta->>'age') < ${sqlstring.escape(minUgcAge)}`
}

], 15, 0, Sorts.DESC('createdAt')
);

This will produce next SQL query:
```

```
SELECT *
FROM "adminuser"
WHERE "role" != 'Admin'
AND (user_meta->>'age') < 18
ORDER BY "createdAt" DESC
LIMIT 15 OFFSET 0;

```


Finds users with age less then 18 from meta field which should be a JSONB field in Postgress.

## Create a new item in database

Signature:
Expand Down
Loading