Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login Oauth providers using plugin 'Headless Login for WPGraphQL' #214

Draft
wants to merge 24 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0fe0210
Add getLoginClients, handle login requests
alexookah Jul 11, 2024
7d7ecf1
Fix LoginClients type
alexookah Jul 11, 2024
05e2b04
Merge branch 'main' into login_oauth
alexookah Jul 11, 2024
aa79a73
Fix spacing for providers
alexookah Jul 11, 2024
121b894
Change login
alexookah Jul 11, 2024
8a6ce69
Clear params
alexookah Jul 12, 2024
d6a4bac
Merge branch 'main' into login_oauth
alexookah Jul 14, 2024
a1c364f
Merge branch 'main' into login_oauth
alexookah Jul 14, 2024
382b667
Merge branch 'main' into login_oauth
alexookah Jul 15, 2024
a2652b2
Merge branch 'main' into login_oauth
alexookah Jul 15, 2024
421bfbb
Merge branch 'main' into login_oauth
alexookah Jul 28, 2024
b3e14ae
Merge branch 'main' into login_oauth
alexookah Aug 5, 2024
5c5b5f6
Move getIcon function
alexookah Aug 5, 2024
1a50335
Merge branch 'main' into login_oauth
alexookah Aug 5, 2024
41a6ffc
Fallback to my-account
alexookah Aug 6, 2024
6433c2e
Merge branch 'main' into login_oauth
alexookah Aug 7, 2024
1ed8b5c
Merge branch 'main' into login_oauth
alexookah Aug 13, 2024
e955d06
Merge branch 'main' into login_oauth
alexookah Aug 19, 2024
d8fbc1c
Merge branch 'main' into login_oauth
alexookah Aug 19, 2024
21ec2f8
Merge branch 'main' into login_oauth
alexookah Aug 19, 2024
d21081e
Merge branch 'main' into login_oauth
alexookah Aug 25, 2024
2c7309b
Merge branch 'main' into login_oauth
alexookah Aug 26, 2024
57d455b
Merge branch 'main' into login_oauth
alexookah Sep 21, 2024
81eb433
Merge branch 'main' into login_oauth
alexookah Nov 29, 2024
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
7 changes: 6 additions & 1 deletion woonuxt_base/app/components/forms/LoginAndRegister.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,19 @@
{{ $t('messages.account.forgotPassword') }}
</div>
<div class="my-8 text-center cursor-pointer" @click="navigate('login')" v-if="formView === 'forgotPassword'">{{ $t('messages.account.backToLogin') }}</div>

<LoginProviders v-if="formView === 'login' || formView === 'register'" :loginClients="loginClients" />
</div>
</template>

<script setup lang="ts">
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const { loginUser, isPending, registerUser, sendResetPasswordEmail } = useAuth();
const { loginUser, isPending, registerUser, sendResetPasswordEmail, loginClients, getLoginClients } = useAuth();

if (loginClients.value === null) getLoginClients();

const userInfo = ref({ email: '', password: '', username: '' });
const formView = ref('login');
const message = ref('');
Expand Down
48 changes: 48 additions & 0 deletions woonuxt_base/app/components/forms/LoginProviders.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script setup lang="ts">
const props = defineProps({
loginClients: { type: Object as PropType<LoginClients>, default: [] },
});

const route = useRoute();
const router = useRouter();
const { loginWithProvider } = useAuth();

const { provider, code, state } = route.query as { provider?: string; code?: string; state?: string };
if (code && state && provider) {
loginWithProvider(state, code, provider.toUpperCase())
.then(() => {
router.replace({ query: {} });
})
.catch((error) => {
console.error('Login failed:', error);
});
}

const getIcon = (provider: any) => {
switch (provider) {
case 'FACEBOOK':
return 'ion:logo-facebook';
case 'GITHUB':
return 'ion:logo-github';
case 'GOOGLE':
return 'ion:logo-google';
case 'INSTAGRAM':
return 'ion:logo-instagram';
case 'LINKEDIN':
return 'ion:logo-linkedin';
default:
return 'ion:log-in';
}
};
</script>

<template>
<div class="flex justify-center items-center pt-4">
<div v-for="(loginClient, index) in props.loginClients" :key="index">
<NuxtLink v-if="loginClient && loginClient?.authorizationUrl" :to="loginClient?.authorizationUrl">
<Icon :name="getIcon(loginClient.provider)" size="56" class="mx-6" />
</NuxtLink>
</div>
</div>
</template>

61 changes: 57 additions & 4 deletions woonuxt_base/app/composables/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GqlLogin, GqlLogout, GqlRegisterCustomer, GqlResetPasswordEmail, GqlGetOrders } from '#gql';
import type { RegisterCustomerInput, CreateAccountInput } from '#gql';
import { GqlLogin, GqlLogout, GqlRegisterCustomer, GqlResetPasswordEmail, GqlGetOrders, } from '#gql';
import type { RegisterCustomerInput, CreateAccountInput, LoginInput } from '#gql';

export const useAuth = () => {
const { refreshCart } = useCart();
Expand All @@ -11,15 +11,16 @@ export const useAuth = () => {
const isPending = useState<boolean>('isPending', () => false);
const orders = useState<Order[] | null>('orders', () => null);
const downloads = useState<DownloadableItem[] | null>('downloads', () => null);
const loginClients = useState<LoginClients | null>('loginClients', () => null);

// Log in the user
const loginUser = async (credentials: CreateAccountInput): Promise<{ success: boolean; error: any }> => {
isPending.value = true;

try {
const { loginWithCookies } = await GqlLogin(credentials);
const { login } = await GqlLogin(credentials);

if (loginWithCookies?.status === 'SUCCESS') {
if (login?.authToken !== null) {
await refreshCart();
if (viewer === null) {
return {
Expand All @@ -30,6 +31,40 @@ export const useAuth = () => {
}
}

isPending.value = false;
return {
success: true,
error: null,
};
} catch (error: any) {
logGQLError(error);
isPending.value = false;

return {
success: false,
error: error?.gqlErrors?.[0]?.message,
};
}
};

const loginWithProvider = async (state: string, code: string, provider: any): Promise<{ success: boolean; error: any }> => {
isPending.value = true;

try {
const input: LoginInput = { oauthResponse: { state, code }, provider }
const response = await GqlLoginWithProvider({ input });

if (response.login?.authToken) {
const { viewer } = await refreshCart();
if (viewer === null) {
return {
success: false,
error:
'Your credentials are correct, but there was an error logging in. This is most likely due to an SSL error. Please try again later. If the problem persists, please contact support.',
};
}
}

return {
success: true,
error: null,
Expand Down Expand Up @@ -173,6 +208,21 @@ export const useAuth = () => {
}
};

const getLoginClients = async () => {
try {
const response = await GqlGetLoginClients();
if (response.loginClients) {
loginClients.value = response.loginClients;
return { success: true, error: null };
}
return { success: false, error: 'There was an error getting your OAuth clients. Please try again later.' };
} catch (error: any) {
logGQLError(error);
const gqlError = error?.gqlErrors?.[0];
return { success: false, error: gqlError?.message };
}
};

const avatar = computed(() => viewer.value?.avatar?.url ?? null);
const wishlistLink = computed<string>(() => (viewer.value ? '/my-account?tab=wishlist' : '/wishlist'));

Expand All @@ -185,6 +235,8 @@ export const useAuth = () => {
avatar,
wishlistLink,
loginUser,
loginClients,
loginWithProvider,
updateCustomer,
updateViewer,
logoutUser,
Expand All @@ -193,5 +245,6 @@ export const useAuth = () => {
resetPasswordWithKey,
getOrders,
getDownloads,
getLoginClients,
};
};
24 changes: 24 additions & 0 deletions woonuxt_base/app/pages/oauth/login/[provider].vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
const route = useRoute();
const router = useRouter();

const provider = route.params.provider as string;

const code = route.query.code as string;
const state = route.query.state as string;
const error = route.query.error as string;

if (code && state && provider && !error) {
router.push({ name: 'my-account', query: { ...route.query, provider } });
} else {
router.push('/my-account');
}
</script>

<template>
<div class="container min-h-[600px]">
<div v-if="showLoader" class="flex flex-col min-h-[500px]">
<LoadingIcon class="m-auto" />
</div>
</div>
</template>
8 changes: 8 additions & 0 deletions woonuxt_base/app/queries/getLoginClients.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query getLoginClients {
loginClients {
name
provider
isEnabled
authorizationUrl
}
}
7 changes: 5 additions & 2 deletions woonuxt_base/app/queries/login.gql
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mutation login($username: String!, $password: String!) {
loginWithCookies(input: { login: $username, password: $password, rememberMe: true }) {
status
login(input: { provider: PASSWORD, credentials: { username: $username, password: $password } }) {
authToken
authTokenExpiration
refreshToken
wooSessionToken
}
}
7 changes: 7 additions & 0 deletions woonuxt_base/app/queries/loginWithProvider.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mutation loginWithProvider($input: LoginInput!) {
login(input: $input) {
authToken
refreshToken
authTokenExpiration
}
}
1 change: 1 addition & 0 deletions woonuxt_base/app/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Terms = import('#gql').TermsFragment;
type VariationAttribute = import('#gql').VariationAttributeFragment;
type Comment = import('#gql').CommentFragment;
type ProductAttribute = import('#gql').ProductAttributeFragment;
type LoginClients = import('#gql').GetLoginClientsQuery['loginClients'];

interface ProductAttributeInput {
attributeName: string;
Expand Down