Skip to content

Commit

Permalink
Merge pull request #491 from edmcouncil/487-github-login-method-for-auth
Browse files Browse the repository at this point in the history
GitHub login method for auth
  • Loading branch information
mereolog authored Sep 13, 2024
2 parents 677bfa5 + d7de911 commit e331061
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 5 deletions.
44 changes: 42 additions & 2 deletions general/components/Header/AuthLoginModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@
<span>Create an account</span>
</a>
<button type="submit" class="btn normal-button mt-4">Login</button>
<button
class="btn normal-button mt-4 github-button"
@click.prevent="connectGithub()"
>
<svg
class="github-icon"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
Login with GitHub
</button>
</form>
</div>
</BsModal>
Expand All @@ -80,6 +95,8 @@ const toastStore = useToastStore();
const authStore = useAuthStore();
const authModalStore = useAuthModalStore();
const route = useRoute();
const identifier = ref<string>('');
const password = ref<string>('');
const error = ref<string | null>(null);
Expand Down Expand Up @@ -117,8 +134,7 @@ const handleSubmit = async () => {
});
if (response.jwt && response.user) {
authStore.setJwt(response.jwt);
authStore.setUserData(response.user);
await authStore.loginAndFetchProfile(response.jwt, response.user);
toastStore.addToast('You are logged in.');
hideModal();
} else {
Expand All @@ -142,5 +158,29 @@ const handleSubmit = async () => {
}
};
const connectGithub = () => {
const githubConnectUrl = `${baseURL()}/api/connect/github`;
authStore.setRedirectLink(route.fullPath);
window.location.href = githubConnectUrl;
};
const modalOpen = computed(() => authModalStore.authModal === 'login');
</script>

<style lang="scss">
.github-button {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.github-icon {
width: 24px;
height: 24px;
margin-right: 10px;
fill: white;
}
</style>
135 changes: 135 additions & 0 deletions general/pages/connect/github.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<template>
<div class="github-auth-callback">
<article class="full-page">
<section class="blank">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
<h1>Connecting</h1>
<p class="muted">{{ statusMessage }}</p>
</section>
</article>
</div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAuthStore, type UserData } from '~/stores/auth';
import { useToastStore } from '~/stores/toast';
import { useRuntimeConfig } from '#app';
definePageMeta({
layout: 'empty'
});
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const toastStore = useToastStore();
const runtimeConfig = useRuntimeConfig();
const configStore = useConfigurationStore();
const statusMessage = ref('Processing GitHub authentication...');
const baseURL = () => {
return typeof window !== 'undefined'
? window.location.origin + `${runtimeConfig.public.strapiBasePath}`
: `${runtimeConfig.public.strapiBaseUrl}`;
};
interface AuthResponse {
jwt: string;
user: UserData;
}
onMounted(async () => {
if (configStore.config.authEnabled !== 'true') {
handleError('Authentication is not enabled');
return;
}
const accessToken = route.query.access_token as string;
if (!accessToken) {
handleError('No access token provided');
return;
}
try {
const response = await $fetch<AuthResponse>(
`${baseURL()}/api/auth/github/callback`,
{
method: 'GET',
params: { access_token: accessToken }
}
);
if (response.jwt && response.user) {
await authStore.loginAndFetchProfile(response.jwt, response.user);
toastStore.addToast('Successfully logged in with GitHub.');
statusMessage.value = 'Authentication successful. Redirecting...';
const redirectUrl = authStore.redirectLink || '/ontology';
authStore.clearRedirectLink();
setTimeout(() => router.push(redirectUrl), 2000);
} else {
throw new Error('Invalid response from server');
}
} catch (error: any) {
handleError(error.message || 'Failed to authenticate with GitHub');
}
});
const handleError = (message: string) => {
console.error('GitHub auth error:', message);
statusMessage.value = 'Authentication failed. Redirecting...';
toastStore.addToast(`GitHub authentication failed!`, 'error');
authStore.clear();
const redirectUrl = authStore.redirectLink || '/';
authStore.clearRedirectLink();
setTimeout(() => router.push(redirectUrl), 2000);
};
</script>

<style lang="scss" scoped>
.github-auth-callback {
background: white;
text-align: center;
position: relative;
min-height: 100vh;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
article {
padding-top: 60px;
h1 {
opacity: 0.8;
}
}
article svg {
width: 60px;
opacity: 0.5;
padding-bottom: 50px;
}
&::before {
position: absolute;
content: '';
width: 0px;
height: 0px;
top: 45%;
left: 50%;
border-radius: 50px;
}
}
</style>
56 changes: 53 additions & 3 deletions general/stores/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
import { useRuntimeConfig } from '#app';

export interface UserData {
id: number;
Expand All @@ -22,14 +23,16 @@ export interface UserData {
interface AuthState {
jwt: string | null;
user: UserData | null;
redirectLink: string | null;
}

export const useAuthStore = defineStore({
id: 'auth-store',
state: (): AuthState => {
return {
jwt: null,
user: null
user: null,
redirectLink: null
};
},
actions: {
Expand All @@ -39,17 +42,64 @@ export const useAuthStore = defineStore({
setUserData(user: UserData) {
this.user = user;
},
setRedirectLink(link: string) {
this.redirectLink = link;
},
clearRedirectLink() {
this.redirectLink = null;
},
async fetchFullProfile() {
if (!this.jwt) {
throw new Error('No JWT token available');
}

const runtimeConfig = useRuntimeConfig();
const baseURL =
typeof window !== 'undefined'
? window.location.origin + `${runtimeConfig.public.strapiBasePath}`
: `${runtimeConfig.public.strapiBaseUrl}`;

try {
const response = await $fetch<UserData>(
`${baseURL}/api/users/me?populate=role`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.jwt}`
}
}
);

if (response) {
this.setUserData(response);
} else {
throw new Error('Invalid response from server');
}
} catch (error) {
console.error('Error fetching full profile:', error);
throw error;
}
},
async loginAndFetchProfile(
jwt: string,
initialUserData: Partial<UserData>
) {
this.setJwt(jwt);
this.setUserData({ ...initialUserData } as UserData);
await this.fetchFullProfile();
},
clear() {
this.jwt = null;
this.user = null;
this.redirectLink = null;
}
},
persist: {
key: () => {
const runtimeConfig = useRuntimeConfig();
return `ontoviewer-auth-${runtimeConfig.public.ontologyName}`;
},
storage: process.client ? localStorage : undefined,
paths: ['jwt', 'user']
storage: import.meta.client ? localStorage : undefined,
paths: ['jwt', 'user', 'redirectLink']
}
});

0 comments on commit e331061

Please sign in to comment.