Skip to content

Commit ca7d409

Browse files
authored
feat: OAuth adapter interface
* feat: implement SSO authentication with OAuth2 support * fix: update OAuth documentation and plugin configuration * fix: update OAuth documentation and plugin configuration * fix: replace SSO auth plugin with OAuth plugin * fix: remove OAuth callback route and component
1 parent 1b1d743 commit ca7d409

File tree

11 files changed

+257
-31
lines changed

11 files changed

+257
-31
lines changed

adapters/install-adapters.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses"
1+
ADAPTERS="adminforth-completion-adapter-open-ai-chat-gpt adminforth-email-adapter-aws-ses adminforth-google-oauth-adapter adminforth-github-oauth-adapter"
22

33
# for each plugin
44
for adapter in $ADAPTERS; do
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# OAuth Authentication
2+
3+
The OAuth plugin enables OAuth2-based authentication in AdminForth, allowing users to sign in using their Google, GitHub, or other OAuth2 provider accounts.
4+
5+
## Installation
6+
7+
To install the plugin:
8+
9+
```bash
10+
npm install @adminforth/oauth --save
11+
npm install @adminforth/google-oauth-adapter --save # for Google OAuth
12+
```
13+
14+
## Configuration
15+
16+
### 1. OAuth Provider Setup
17+
18+
You need to get the client ID and client secret from your OAuth2 provider.
19+
20+
For Google:
21+
1. Go to the [Google Cloud Console](https://console.cloud.google.com)
22+
2. Create a new project or select an existing one
23+
3. Go to "APIs & Services" → "Credentials"
24+
4. Create credentials for OAuth 2.0 client IDs
25+
5. Select application type: "Web application"
26+
6. Add your application's name and redirect URI
27+
7. Set the redirect URI to `http://your-domain/oauth/callback`
28+
8. Add the credentials to your `.env` file:
29+
30+
```bash
31+
GOOGLE_CLIENT_ID=your_google_client_id
32+
GOOGLE_CLIENT_SECRET=your_google_client_secret
33+
```
34+
35+
### 2. Plugin Configuration
36+
37+
Configure the plugin in your user resource file:
38+
39+
```typescript title="./resources/adminuser.ts"
40+
import OAuth2Plugin from '@adminforth/oauth';
41+
import AdminForthAdapterGoogleOauth2 from '@adminforth/google-oauth-adapter';
42+
43+
// ... existing resource configuration ...
44+
45+
plugins: [
46+
new OAuth2Plugin({
47+
adapters: [
48+
new AdminForthAdapterGoogleOauth2({
49+
clientID: process.env.GOOGLE_CLIENT_ID,
50+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
51+
redirectUri: 'http://localhost:3000/oauth/callback',
52+
}),
53+
],
54+
emailField: 'email', // Required: field that stores the user's email
55+
emailConfirmedField: 'email_confirmed' // Optional: field to track email verification
56+
}),
57+
]
58+
```
59+
60+
### 3. Email Confirmation
61+
62+
The plugin supports automatic email confirmation for OAuth users. To enable this:
63+
64+
1. Add the `email_confirmed` field to your database schema:
65+
66+
```prisma title='./schema.prisma'
67+
model adminuser {
68+
// ... existing fields ...
69+
email_confirmed Boolean @default(false)
70+
}
71+
```
72+
73+
2. Run the migration:
74+
75+
```bash
76+
npx prisma migrate dev --name add-email-confirmed-to-adminuser
77+
```
78+
79+
3. Configure the plugin with `emailConfirmedField`:
80+
81+
```typescript title="./resources/adminuser.ts"
82+
new OAuth2Plugin({
83+
// ... adapters configuration ...
84+
emailField: 'email',
85+
emailConfirmedField: 'email_confirmed' // Enable email confirmation tracking
86+
}),
87+
```
88+
89+
When using OAuth:
90+
- New users will have their email automatically confirmed (`email_confirmed = true`)
91+
- Existing users will have their email marked as confirmed upon successful OAuth login
92+
- The `email_confirmed` field must be a boolean type

adminforth/types/Adapters.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ export interface CompletionAdapter {
2424
error?: string;
2525
}>;
2626
}
27+
28+
export interface OAuth2Adapter {
29+
getAuthUrl(): string;
30+
getTokenFromCode(code: string): Promise<{ email: string }>;
31+
getIcon(): string;
32+
}

dev-demo/custom/ApartsPie.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<template>
2+
<div class="max-w-sm w-full bg-white rounded-lg shadow dark:bg-gray-800 p-4 md:p-4 mb-5">
3+
<PieChart
4+
:data="data"
5+
:options="{
6+
chart: {
7+
height: 250,
8+
},
9+
dataLabels: {
10+
enabled: true,
11+
},
12+
plotOptions: {
13+
pie: {
14+
dataLabels: {
15+
offset: -10,
16+
minAngleToShowLabel: 10,
17+
},
18+
expandOnClick: true,
19+
},
20+
},
21+
}"
22+
/>
23+
</div>
24+
</template>
25+
26+
<script setup lang="ts">
27+
import { onMounted, ref, Ref } from 'vue';
28+
import { PieChart } from '@/afcl';
29+
import { callApi } from '@/utils';
30+
import adminforth from '@/adminforth';
31+
32+
33+
const data: Ref<any[]> = ref([]);
34+
35+
36+
onMounted(async () => {
37+
try {
38+
data.value = await callApi({path: '/api/aparts-by-room-percentages', method: 'GET'});
39+
} catch (error) {
40+
adminforth.alert({
41+
message: `Error fetching data: ${error.message}`,
42+
variant: 'danger',
43+
timeout: 'unlimited'
44+
});
45+
return;
46+
}
47+
})
48+
49+
</script>

dev-demo/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,35 @@ app.get(`${ADMIN_BASE_URL}/api/dashboard/`,
426426
)
427427
);
428428

429+
app.get(`${ADMIN_BASE_URL}/api/aparts-by-room-percentages/`,
430+
admin.express.authorize(
431+
async (req, res) => {
432+
const roomPercentages = await admin.resource('aparts').dataConnector.db.prepare(
433+
`SELECT
434+
number_of_rooms,
435+
COUNT(*) as count
436+
FROM apartments
437+
GROUP BY number_of_rooms
438+
ORDER BY number_of_rooms;
439+
`
440+
).all()
441+
442+
443+
const totalAparts = roomPercentages.reduce((acc, { count }) => acc + count, 0);
444+
445+
res.json(
446+
roomPercentages.map(
447+
({ number_of_rooms, count }) => ({
448+
amount: Math.round(count / totalAparts * 100),
449+
label: `${number_of_rooms} rooms`,
450+
})
451+
)
452+
);
453+
}
454+
)
455+
);
456+
457+
429458
// serve after you added all api
430459
admin.express.serve(app)
431460
admin.discoverDatabases().then(async () => {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- AlterTable
2+
ALTER TABLE "users" ADD COLUMN "email_verified" BOOLEAN DEFAULT false;
3+
4+
-- CreateTable
5+
CREATE TABLE "apartment_buyers" (
6+
"id" TEXT NOT NULL PRIMARY KEY,
7+
"created_at" DATETIME NOT NULL,
8+
"name" TEXT NOT NULL,
9+
"age" INTEGER,
10+
"gender" TEXT,
11+
"info" TEXT,
12+
"contact_info" TEXT,
13+
"language" TEXT,
14+
"ideal_price" DECIMAL,
15+
"ideal_space" REAL,
16+
"ideal_subway_distance" REAL,
17+
"contacted" BOOLEAN NOT NULL DEFAULT false,
18+
"contact_date" DATETIME,
19+
"contact_time" DATETIME,
20+
"realtor_id" TEXT
21+
);

dev-demo/resources/apartment_buyers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import AdminForth, {
77
AdminUser,
88
} from "../../adminforth";
99
import { v1 as uuid } from "uuid";
10-
import RichEditorPlugin from "../../plugins/adminforth-rich-editor";
10+
// import RichEditorPlugin from "../../plugins/adminforth-rich-editor";
1111

1212
export default {
1313
dataSource: 'mysql',
@@ -148,9 +148,9 @@ export default {
148148
},
149149
],
150150
plugins: [
151-
new RichEditorPlugin({
152-
htmlFieldName: 'info',
153-
}),
151+
// new RichEditorPlugin({
152+
// htmlFieldName: 'info',
153+
// }),
154154
],
155155
options: {
156156
fieldGroups: [

dev-demo/resources/apartments.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -319,29 +319,29 @@ export default {
319319
// debounceTime: 250,
320320
// }
321321
// }),
322-
new RichEditorPlugin({
323-
htmlFieldName: "description",
324-
completion: {
325-
adapter: new CompletionAdapterOpenAIChatGPT({
326-
openAiApiKey: process.env.OPENAI_API_KEY as string,
327-
}),
328-
expert: {
329-
debounceTime: 250,
330-
},
331-
},
332-
// requires to have table 'description_images' with upload plugin installed on attachment field
322+
// new RichEditorPlugin({
323+
// htmlFieldName: "description",
324+
// completion: {
325+
// adapter: new CompletionAdapterOpenAIChatGPT({
326+
// openAiApiKey: process.env.OPENAI_API_KEY as string,
327+
// }),
328+
// expert: {
329+
// debounceTime: 250,
330+
// },
331+
// },
332+
// // requires to have table 'description_images' with upload plugin installed on attachment field
333333

334-
...(process.env.AWS_ACCESS_KEY_ID
335-
? {
336-
attachments: {
337-
attachmentResource: "description_images",
338-
attachmentFieldName: "image_path",
339-
attachmentRecordIdFieldName: "record_id",
340-
attachmentResourceIdFieldName: "resource_id",
341-
},
342-
}
343-
: {}),
344-
}),
334+
// ...(process.env.AWS_ACCESS_KEY_ID
335+
// ? {
336+
// attachments: {
337+
// attachmentResource: "description_images",
338+
// attachmentFieldName: "image_path",
339+
// attachmentRecordIdFieldName: "record_id",
340+
// attachmentResourceIdFieldName: "resource_id",
341+
// },
342+
// }
343+
// : {}),
344+
// }),
345345
],
346346

347347
options: {

dev-demo/resources/users.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import TwoFactorsAuthPlugin from "../../plugins/adminforth-two-factors-auth";
1111
import EmailResetPasswordPlugin from "../../plugins/adminforth-email-password-reset/index.js";
1212
import { v1 as uuid } from "uuid";
1313
import EmailAdapterAwsSes from "../../adapters/adminforth-email-adapter-aws-ses/index.js";
14-
14+
import { OAuthPlugin } from "../../plugins/adminforth-oauth";
15+
import { AdminForthAdapterGoogleOauth2 } from "../../adapters/adminforth-google-oauth-adapter";
16+
import { AdminForthAdapterGithubOauth2 } from "../../adapters/adminforth-github-oauth-adapter";
1517
export default {
1618
dataSource: "maindb",
1719
table: "users",
@@ -83,6 +85,22 @@ export default {
8385
// }),
8486
// },
8587
}),
88+
new OAuthPlugin({
89+
adapters: [
90+
new AdminForthAdapterGithubOauth2({
91+
clientID: process.env.GITHUB_CLIENT_ID,
92+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
93+
redirectUri: 'http://localhost:3000/oauth/callback',
94+
}),
95+
new AdminForthAdapterGoogleOauth2({
96+
clientID: process.env.GOOGLE_CLIENT_ID,
97+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
98+
redirectUri: 'http://localhost:3000/oauth/callback',
99+
})
100+
],
101+
emailField: 'email',
102+
emailConfirmedField: 'email_confirmed'
103+
}),
86104
],
87105
options: {
88106
allowedActions: {
@@ -132,6 +150,16 @@ export default {
132150
showIn: [],
133151
backendOnly: true, // will never go to frontend
134152
},
153+
{
154+
name: 'email_confirmed',
155+
type: AdminForthDataTypes.BOOLEAN,
156+
showIn: {
157+
list: true,
158+
show: true,
159+
edit: false,
160+
create: false
161+
}
162+
},
135163
{
136164
name: "role",
137165
enum: [

dev-demo/schema.prisma

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ model users {
1717
last_login_ip String?
1818
email_confirmed Boolean? @default(false)
1919
parentUserId String?
20+
email_verified Boolean? @default(false)
2021
}
2122

2223
model apartments {
@@ -100,7 +101,7 @@ model apartment_buyers {
100101
ideal_space Float?
101102
ideal_subway_distance Float?
102103
contacted Boolean @default(false)
103-
contact_date Date?
104-
contact_time Time?
104+
contact_date DateTime?
105+
contact_time DateTime?
105106
realtor_id String?
106107
}

0 commit comments

Comments
 (0)