Skip to content

Commit 6162bfe

Browse files
authored
Merge pull request #666 from devforth/next
Next
2 parents dbc5d62 + d0a028b commit 6162bfe

11 files changed

Lines changed: 337 additions & 43 deletions

File tree

adminforth/documentation/docs/tutorial/03-Customization/02-customFieldRendering.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,29 @@ list: '@/renderers/ZeroStylesRichText.vue',
583583
`ZeroStyleRichText` fits well for tasks like email templates preview fields.
584584
585585
586+
### Sensitive data blur
587+
588+
For fields containing sensitive data (like passwords, API keys, tokens, or other confidential values), use the `SensitiveBlurCell` renderer. It blurs the value by default and reveals it on click.
589+
590+
```ts title='./resources/anyResource.ts'
591+
columns: [
592+
...
593+
{
594+
name: 'api_key',
595+
//diff-add
596+
components: {
597+
//diff-add
598+
show: '@/renderers/SensitiveBlurCell.vue',
599+
//diff-add
600+
list: '@/renderers/SensitiveBlurCell.vue',
601+
//diff-add
602+
},
603+
...
604+
```
605+
606+
The renderer wraps the standard value output and adds a click-to-reveal blur effect. Clicking again hides the value.
607+
608+
586609
### Custom filter component for square meters
587610
588611

adminforth/documentation/docs/tutorial/06-Adapters/02-oauth2-adapters.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,12 @@ Supports login through Microsoft accounts including Azure AD, Office365, and Out
5454
pnpm i @adminforth/oauth-adapter-twitch
5555
```
5656

57-
Adds support for Twitch authentication, useful for streaming or creator-oriented platforms.
57+
Adds support for Twitch authentication, useful for streaming or creator-oriented platforms.
58+
59+
## Clerk OAuth Adapter
60+
61+
```bash
62+
pnpm i @adminforth/clerk-oauth-adapter
63+
```
64+
65+
Enables sign-in via [Clerk](https://clerk.com/) — a hosted authentication platform with built-in user management, MFA, and social logins.

adminforth/documentation/docs/tutorial/09-Plugins/02-TwoFactorsAuth.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ But if you websocket doesn't work in you application, or you wan't to perform ve
283283
284284
You might want to to allow to call some custom critical/money related actions with additional 2FA approval. This eliminates risks caused by user cookies theft by some virous/doorway software after login.
285285
286-
To do it, first, create frontend custom component which wraps and intercepts click event to menu item, and in click handler do a call to `window.adminforthTwoFaModal.getCode(cb?)` frontend API exposed by this plugin. This is awaitable call wich shows 2FA popup and asks user to authenticate with 2nd factor (if passkey is enabled it will be suggested first, with ability to fallback to TOTP)
286+
To do it, first, create frontend custom component which wraps and intercepts click event to menu item, and in click handler do a call to `get2FaConfirmationResult` frontend API exposed by this plugin. This is awaitable call wich shows 2FA popup and asks user to authenticate with 2nd factor (if passkey is enabled it will be suggested first, with ability to fallback to TOTP)
287287
288288
```ts title='/custom/RequireTwoFaGate.vue'
289289
<template>
@@ -293,6 +293,9 @@ To do it, first, create frontend custom component which wraps and intercepts cli
293293
</template>
294294

295295
<script setup lang="ts">
296+
import { useTwoFactorsAuth } from '@/custom/plugins/TwoFactorsAuthPlugin/use2faApi.ts';
297+
298+
const { get2FaConfirmationResult } = useTwoFactorsAuth();
296299
const emit = defineEmits<{ (e: 'callAction', payload?: any): void }>();
297300
const props = defineProps<{ disabled?: boolean; meta?: Record<string, any> }>();
298301

@@ -301,8 +304,7 @@ To do it, first, create frontend custom component which wraps and intercepts cli
301304
return;
302305
}
303306

304-
const verificationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult(); // this will ask user to enter code
305-
307+
const verificationResult = await get2FaConfirmationResult(); // this will ask user to enter code
306308
emit('callAction', { verificationResult }); // then we pass this verification result to action (from fronted to backend)
307309
}
308310
</script>
@@ -387,20 +389,19 @@ Frontend (Save Interceptor component injected via pageInjections):
387389
```vue title='/custom/SaveInterceptor.vue'
388390
<script setup>
389391
import { useAdminforth } from '@/adminforth';
392+
import { useTwoFactorsAuth } from '@/custom/plugins/TwoFactorsAuthPlugin/use2faApi.ts';
390393

394+
const { get2FaConfirmationResult } = useTwoFactorsAuth();
391395
const { registerSaveInterceptor } = useAdminforth();
392396

393397
registerSaveInterceptor(async ({ action, values, resource }) => {
394398
// action is 'create' or 'edit'
395-
const modal = (window as any)?.adminforthTwoFaModal;
396-
if (modal?.get2FaConfirmationResult) {
397-
const confirmationResult = await modal.get2FaConfirmationResult('Confirm to save changes');
398-
if (!confirmationResult) {
399-
return { ok: false, error: 'Two-factor authentication cancelled' };
400-
}
401-
// Pass data to backend; the view will forward extra.confirmationResult to meta.confirmationResult
402-
return { ok: true, extra: { confirmationResult } };
399+
const confirmationResult = await get2FaConfirmationResult('Confirm to save changes');
400+
if (!confirmationResult) {
401+
return { ok: false, error: 'Two-factor authentication cancelled' };
403402
}
403+
// Pass data to backend; the view will forward extra.confirmationResult to meta.confirmationResult
404+
return { ok: true, extra: { confirmationResult } };
404405
else {
405406
throw new Error('No Two-Factor Authentication modal found, please ensure you have latest version of @adminforth/two-factors-auth installed and instantiated on resource');
406407
}
@@ -493,9 +494,12 @@ Imagine you have some button which does some API call
493494

494495
<script setup lang="ts">
495496
import { callApi } from '@/utils';
497+
import { useTwoFactorsAuth } from '@/custom/plugins/TwoFactorsAuthPlugin/use2faApi.ts';
498+
499+
const { get2FaConfirmationResult } = useTwoFactorsAuth();
496500

497501
async function callAdminAPI() {
498-
const verificationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
502+
const verificationResult = await get2FaConfirmationResult();
499503

500504
const res = await callApi({
501505
path: '/myCriticalAction',
@@ -534,12 +538,14 @@ You might want to protect this call with a second factor also. To do it, we need
534538
<script setup lang="ts">
535539
import { callApi } from '@/utils';
536540
import { useAdminforth } from '@/adminforth';
541+
import { useTwoFactorsAuth } from '@/custom/plugins/TwoFactorsAuthPlugin/use2faApi.ts';
537542

543+
const { get2FaConfirmationResult } = useTwoFactorsAuth();
538544
const { alert } = useAdminforth();
539545

540546
async function callAdminAPI() {
541547
// diff-add
542-
const verificationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
548+
const verificationResult = await get2FaConfirmationResult();
543549

544550
const res = await callApi({
545551
path: '/myCriticalAction',

adminforth/documentation/docs/tutorial/09-Plugins/11-oauth.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,49 @@ plugins: [
476476
]
477477
```
478478

479+
### Clerk Adapter
480+
481+
Install Adapter:
482+
483+
```bash
484+
pnpm install @adminforth/clerk-oauth-adapter --save
485+
```
486+
487+
1. Go to the [Clerk Dashboard](https://dashboard.clerk.com) and open your application.
488+
2. In the left sidebar, go to **Configure****Developers****OAuth Applications**.
489+
3. Click **Add OAuth Application** and give it a name.
490+
4. Copy the **Client Secret** from the OAuth application creation modal.
491+
5. After copying, Clerk redirects you to the application page, where you can copy the **Client ID** and **Domain**.
492+
6. Add the credentials to your `.env` file:
493+
7. In **Redirect URLs**, add `https://your-domain/oauth/callback` and `http://localhost:3500/oauth/callback`. Include `baseUrl` when your AdminForth app uses it, for example `https://your-domain/base/oauth/callback`.
494+
8. In **Scopes**, check the **openid** checkbox.
495+
496+
```bash
497+
CLERK_CLIENT_ID=your_clerk_client_id
498+
CLERK_CLIENT_SECRET=your_clerk_client_secret
499+
CLERK_DOMAIN=https://your-app.clerk.accounts.dev
500+
```
501+
502+
Add the adapter to your plugin configuration:
503+
504+
```typescript title="./resources/adminuser.ts"
505+
import AdminForthAdapterClerkOauth2 from '@adminforth/clerk-oauth-adapter';
506+
507+
// ... existing resource configuration ...
508+
plugins: [
509+
new OAuthPlugin({
510+
adapters: [
511+
...
512+
new AdminForthAdapterClerkOauth2({
513+
clientID: process.env.CLERK_CLIENT_ID as string,
514+
clientSecret: process.env.CLERK_CLIENT_SECRET as string,
515+
domain: process.env.CLERK_DOMAIN as string,
516+
}),
517+
],
518+
}),
519+
]
520+
```
521+
479522
### Need custom provider?
480523

481524
Just fork any existing adapter e.g. [Google](https://github.com/devforth/adminforth-oauth-adapter-google) and adjust it to your needs.

adminforth/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ class AdminForth implements IAdminForth {
499499
║ 💡 SOLUTION
500500
║ Install the required package:
501501
502-
${doesUserHavePnpmLock ? `pnpm add @adminforth/connector-${connectorName}` : `npm install @adminforth/connector-${connectorName}`}
502+
${doesUserHavePnpmLock ? `pnpm add @adminforth/connector-${connectorName}` : `npm install @adminforth/connector-${connectorName}`}
503503
504504
╚════════════════════════════════════════════════════════════════════════════
505505
`);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<template>
2+
<div class="inline-flex items-center gap-1">
3+
<div
4+
class="overflow-hidden max-w-[200px] max-h-[20px] rounded-default"
5+
:title="show ? $t('Click to hide') : $t('Click to show')"
6+
@click="toggle"
7+
>
8+
<span
9+
class="cursor-pointer select-none transition-all duration-200 text-lightListTableText dark:text-darkListTableText"
10+
:class="{
11+
'blur-[7px] hover:blur-[5px] brightness-50 dark:brightness-150': !show,
12+
}"
13+
>
14+
<ValueRenderer :column="column" :record="record" />
15+
</span>
16+
</div>
17+
</div>
18+
</template>
19+
20+
<script setup lang="ts">
21+
import { ref } from 'vue';
22+
import ValueRenderer from '@/components/ValueRenderer.vue';
23+
import type { AdminForthResourceColumnCommon, AdminForthResourceCommon, AdminUser } from '@/types/Common';
24+
25+
const props = defineProps<{
26+
column: AdminForthResourceColumnCommon;
27+
record: any;
28+
meta: any;
29+
resource: AdminForthResourceCommon;
30+
adminUser: AdminUser;
31+
}>();
32+
33+
const show = ref(false);
34+
35+
function toggle(event: MouseEvent) {
36+
show.value = !show.value;
37+
event.stopPropagation();
38+
}
39+
</script>

dev-demo/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ node_modules
33
.sqlite
44
.env
55
db
6-
images/*
6+
images/*
7+
testdb/*

dev-demo/Taskfile.yaml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ tasks:
153153
exit 0
154154
fi
155155
156-
CI=true pnpm add adminforth@latest --save-peer
156+
CI=true PNPM_CONFIG_MINIMUM_RELEASE_AGE=0 PNPM_CONFIG_DANGEROUSLY_ALLOW_ALL_BUILDS=true pnpm add adminforth@latest --save-prefix='^' --save-peer
157157
158158
git add package.json
159159
if [ -e pnpm-lock.yaml ]; then
@@ -230,4 +230,22 @@ tasks:
230230
vars:
231231
DIR: "{{.PLUGINS_DIR}}"
232232
REPOS:
233-
ref: .PLUGINS
233+
ref: .PLUGINS
234+
235+
update_adminforth_adapters:
236+
desc: Update adminforth to latest in adapter repositories, commit, and push
237+
cmds:
238+
- task: update_adminforth_repos
239+
vars:
240+
DIR: "{{.ADAPTERS_DIR}}"
241+
REPOS:
242+
ref: .ADAPTERS
243+
244+
update_adminforth_connectors:
245+
desc: Update adminforth to latest in connector repositories, commit, and push
246+
cmds:
247+
- task: update_adminforth_repos
248+
vars:
249+
DIR: "{{.CONNECTORS_DIR}}"
250+
REPOS:
251+
ref: .CONNECTORS

dev-demo/api.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { Express, Response } from "express";
22
import { Filters, IAdminForth, IAdminUserExpressRequest } from "adminforth";
33
import * as z from "zod";
44
import TwoFactorsAuthPlugin from "../plugins/adminforth-two-factors-auth/index.js";
5+
import LevelDBKeyValueAdapter from '../adapters/adminforth-key-value-adapter-leveldb/index.js';
6+
7+
const levelDbAdapter = new LevelDBKeyValueAdapter({
8+
dbPath: './testdb',
9+
});
510

611
const DASHBOARD_CAR_SOURCES = [
712
{ resourceId: 'cars_sl', label: 'SQLite' },
@@ -184,7 +189,32 @@ export function initApi(app: Express, admin: IAdminForth) {
184189
const { adminUser } = _req;
185190
const t2fa = admin.getPluginByClassName<TwoFactorsAuthPlugin>('TwoFactorsAuthPlugin');
186191
const verifyResult = await t2fa.verifyAuto(adminUser);
187-
res.json({ message: "2FA call received!" });
192+
res.json({ message: "2FA call received!", verifyResult });
193+
}
194+
)
195+
);
196+
app.post(`${admin.config.baseUrl}/api/getLevelDbKeys/`,
197+
admin.express.authorize(
198+
async (_req: IAdminUserExpressRequest, res: Response) => {
199+
console.log('Received getLevelDbKeys');
200+
const { prefix } = _req.body;
201+
const keys = await levelDbAdapter.listByPrefix(prefix, 100);
202+
res.json({ keys });
203+
}
204+
)
205+
);
206+
app.post(`${admin.config.baseUrl}/api/addLevelDbKey/`,
207+
admin.express.authorize(
208+
async (_req: IAdminUserExpressRequest, res: Response) => {
209+
console.log('Received addLevelDbKey');
210+
const {key, value} = _req.body;
211+
await levelDbAdapter.set(key, value);
212+
// for (let i = 0; i < 100; i++) {
213+
// await levelDbAdapter.set(`clean=true||${new Date().toISOString()}||record_${i}`, `true`);
214+
// console.log(`Added record ${i}`);
215+
// await new Promise((resolve) => setTimeout(resolve, 100));
216+
// }
217+
res.json({ ok: true });
188218
}
189219
)
190220
);

0 commit comments

Comments
 (0)