Skip to content

Commit 175c78e

Browse files
authored
Merge pull request #1 from devforth/open-signup
fix: Add open signup configuration for OAuth plugin
2 parents 7f854cc + c0ab8dd commit 175c78e

File tree

4 files changed

+90
-54
lines changed

4 files changed

+90
-54
lines changed

custom/OAuthCallback.vue

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ onMounted(async () => {
1818
const urlParams = new URLSearchParams(window.location.search);
1919
const code = urlParams.get('code');
2020
const state = urlParams.get('state');
21-
22-
if (code && state) {
21+
const redirectUri = window.location.origin + '/oauth/callback';
22+
if (code && state && redirectUri) {
2323
const encodedCode = encodeURIComponent(code);
2424
const encodedState = encodeURIComponent(state);
25+
const encodedRedirectUri = encodeURIComponent(redirectUri);
2526
const response = await callAdminForthApi({
26-
path: `/oauth/callback?code=${encodedCode}&state=${encodedState}`,
27+
path: `/oauth/callback?code=${encodedCode}&state=${encodedState}&redirect_uri=${encodedRedirectUri}`,
2728
method: 'GET',
2829
});
2930
if (response.allowedLogin) {

custom/OAuthLoginButton.vue

-26
This file was deleted.

custom/OAuthLoginButtons.vue

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<template>
2+
<div :class="meta.iconOnly ? 'flex flex-row justify-center items-center gap-3' : 'flex flex-col justify-center items-center gap-2'" >
3+
<a
4+
v-for="provider in meta.providers"
5+
:key="provider.provider"
6+
:href="handleLogin(provider.authUrl)"
7+
class="border dark:border-gray-400 flex items-center justify-center hover:bg-gray-50 hover:dark:border-gray-300 hover:dark:bg-gray-700"
8+
:class="[
9+
meta.iconOnly ? 'w-11 h-11 p-0' : 'w-full py-2 px-4',
10+
meta.pill ? 'rounded-full' : 'rounded-md'
11+
]"
12+
>
13+
<div v-html="provider.icon" class="w-6 h-6" :class="meta.iconOnly ? 'mr-0' : 'mr-4'" :alt="getProviderName(provider.provider)" />
14+
<span v-if="!meta.iconOnly" class="font-medium dark:text-white">Continue with {{ getProviderName(provider.provider) }}</span>
15+
</a>
16+
</div>
17+
</template>
18+
19+
<script setup>
20+
const props = defineProps({
21+
meta: {
22+
type: Object,
23+
required: true
24+
}
25+
});
26+
27+
const getProviderName = (provider) => {
28+
return provider.replace('AdminForthAdapter', '').replace('Oauth2', '');
29+
};
30+
31+
const handleLogin = (authUrl) => {
32+
const redirectUri = window.location.origin + '/oauth/callback';
33+
const url = new URL(authUrl);
34+
url.searchParams.set('redirect_uri', redirectUri);
35+
return url.toString();
36+
};
37+
</script>

index.ts

+49-25
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ interface OAuthPluginOptions {
1010
emailField: string;
1111
emailConfirmedField?: string;
1212
adapters: OAuth2Adapter[];
13+
iconOnly?: boolean;
14+
pill?: boolean;
15+
authenticationExpireDuration?: number;
16+
openSignup?: {
17+
enabled?: boolean;
18+
defaultFieldValues?: Record<string, any>;
19+
};
1320
}
1421

1522
export default class OAuthPlugin extends AdminForthPlugin {
@@ -22,7 +29,17 @@ export default class OAuthPlugin extends AdminForthPlugin {
2229
if (!options.emailField) {
2330
throw new Error('OAuthPlugin: emailField is required');
2431
}
25-
this.options = options;
32+
33+
// Set default values for openSignup
34+
this.options = {
35+
...options,
36+
iconOnly: options.iconOnly ?? false,
37+
pill: options.pill ?? false,
38+
openSignup: {
39+
enabled: options.openSignup?.enabled ?? false,
40+
defaultFieldValues: options.openSignup?.defaultFieldValues ?? {},
41+
}
42+
};
2643
}
2744

2845
async modifyResourceConfig(adminforth: IAdminForth, resource: AdminForthResource) {
@@ -31,11 +48,6 @@ export default class OAuthPlugin extends AdminForthPlugin {
3148
this.adminforth = adminforth;
3249
this.resource = resource;
3350

34-
// Add custom page for OAuth callback
35-
if (!adminforth.config.customization.customPages) {
36-
adminforth.config.customization.customPages = [];
37-
}
38-
3951
adminforth.config.customization.customPages.push({
4052
path: '/oauth/callback',
4153
component: {
@@ -72,24 +84,30 @@ export default class OAuthPlugin extends AdminForthPlugin {
7284
}
7385

7486
// Register the component with the correct plugin path
75-
const componentPath = `@@/plugins/${this.constructor.name}/OAuthLoginButton.vue`;
76-
this.componentPath('OAuthLoginButton.vue');
87+
const componentPath = `@@/plugins/${this.constructor.name}/OAuthLoginButtons.vue`;
88+
this.componentPath('OAuthLoginButtons.vue');
7789

7890
const baseUrl = adminforth.config.baseUrl || '';
79-
this.options.adapters.forEach(adapter => {
91+
const providers = this.options.adapters.map(adapter => {
8092
const state = Buffer.from(JSON.stringify({
8193
provider: adapter.constructor.name
8294
})).toString('base64');
8395

84-
adminforth.config.customization.loginPageInjections.underInputs.push({
85-
file: componentPath,
86-
meta: {
87-
authUrl: `${adapter.getAuthUrl()}&state=${state}`,
88-
provider: adapter.constructor.name,
89-
baseUrl,
90-
icon: adapter.getIcon?.() || ''
91-
}
92-
});
96+
return {
97+
authUrl: `${adapter.getAuthUrl()}&state=${state}`,
98+
provider: adapter.constructor.name,
99+
baseUrl,
100+
icon: adapter.getIcon(),
101+
};
102+
});
103+
104+
adminforth.config.customization.loginPageInjections.underInputs.push({
105+
file: componentPath,
106+
meta: {
107+
providers,
108+
iconOnly: this.options.iconOnly,
109+
pill: this.options.pill,
110+
}
93111
});
94112
}
95113

@@ -124,7 +142,7 @@ export default class OAuthPlugin extends AdminForthPlugin {
124142
response,
125143
username,
126144
pk: user.id,
127-
expireInDays: this.adminforth.config.auth.rememberMeDays
145+
expireInDays: this.options.authenticationExpireDuration ? this.options.authenticationExpireDuration : this.adminforth.config.auth.rememberMeDays
128146
});
129147
}
130148

@@ -137,7 +155,7 @@ export default class OAuthPlugin extends AdminForthPlugin {
137155
path: '/oauth/callback',
138156
noAuth: true,
139157
handler: async ({ query, response, headers, cookies, requestUrl }) => {
140-
const { code, state } = query;
158+
const { code, state, redirect_uri } = query;
141159
if (!code) {
142160
return { error: 'No authorization code provided' };
143161
}
@@ -155,20 +173,26 @@ export default class OAuthPlugin extends AdminForthPlugin {
155173
return { error: 'Invalid OAuth provider' };
156174
}
157175

158-
const userInfo = await adapter.getTokenFromCode(code);
176+
const userInfo = await adapter.getTokenFromCode(code, redirect_uri);
159177

160178
let user = await this.adminforth.resource(this.resource.resourceId).get([
161179
Filters.EQ(this.options.emailField, userInfo.email)
162180
]);
163181

164182
if (!user) {
183+
// Check if open signup is enabled
184+
if (!this.options.openSignup?.enabled) {
185+
return {
186+
error: 'User not found and open signup is disabled',
187+
redirectTo: '/login'
188+
};
189+
}
190+
165191
// When creating a new user, set emailConfirmedField to true if it's configured
166192
const createData: any = {
167-
id: randomUUID(),
168-
created_at: new Date().toISOString(),
169193
[this.options.emailField]: userInfo.email,
170-
role: 'user',
171-
password_hash: ''
194+
[this.adminforth.config.auth.passwordHashField]: '',
195+
...this.options.openSignup.defaultFieldValues
172196
};
173197

174198
if (this.options.emailConfirmedField) {

0 commit comments

Comments
 (0)