From c713ed0b08257a44687891eb0808610324094a3d Mon Sep 17 00:00:00 2001 From: pallabez Date: Wed, 3 Jan 2024 16:42:12 +0530 Subject: [PATCH 1/3] feat: auth store integration --- adapters/auth/auth.transformer.ts | 41 +++++----- adapters/auth/auth.type.ts | 18 +++-- adapters/auth/index.ts | 58 +++++--------- middleware/authenticate.global.ts | 9 +++ nuxt.config.ts | 1 + store/auth.ts | 124 +++++++++--------------------- store/goals.ts | 1 + 7 files changed, 101 insertions(+), 151 deletions(-) create mode 100644 middleware/authenticate.global.ts diff --git a/adapters/auth/auth.transformer.ts b/adapters/auth/auth.transformer.ts index 9ee43bab..d1ac975b 100644 --- a/adapters/auth/auth.transformer.ts +++ b/adapters/auth/auth.transformer.ts @@ -1,29 +1,30 @@ -import { type User } from '~/interfaces/User' import { type SelfInfo } from './auth.type' +import { transformUser } from '~/adapters/user.transformer' -export const transformSelfInfoFromApi = (userInfo: any): { info: SelfInfo, user: User } => { - const info = { - userId: userInfo?.id, +export const transformSelfInfoFromApi = (userInfo: any): SelfInfo => { + return { + ...transformUser(userInfo), + status: userInfo?.status, + incompleteUserDetails: userInfo?.incompleteUserDetails, roles: { archived: userInfo?.roles?.archived, - member: userInfo?.roles?.member + member: userInfo?.roles?.member, }, - status: userInfo?.status, - incompleteUserDetails: userInfo?.incompleteUserDetails - } - - const user = { - id: userInfo?.id, - username: userInfo?.username, - firstName: userInfo?.first_name, - lastName: userInfo?.last_name, - githubId: userInfo?.github_id, - githubDisplayName: userInfo?.github_display_name, - avatarUrl: userInfo?.picture?.url } +} +export const transformGoalTokenFromApi = (response: any): { + token: { + exp: number, + access: string, + }, + id: string, +} => { return { - info, - user + token: { + exp: response?.user?.token?.exp, + access: response?.user?.token?.access, + }, + id: response?.user?.id, } -} +} \ No newline at end of file diff --git a/adapters/auth/auth.type.ts b/adapters/auth/auth.type.ts index 79b7d2b5..eb4e4987 100644 --- a/adapters/auth/auth.type.ts +++ b/adapters/auth/auth.type.ts @@ -1,9 +1,15 @@ export interface SelfInfo { - userId: string + id: string + displayName: string + username: string + firstName: string + lastName: string + avatar: string + initials: string roles: { - member: boolean - archived: boolean - } - status: string - incompleteUserDetails: boolean + archived: boolean | undefined; + member: boolean | undefined; + }; + status: string | undefined; + incompleteUserDetails: boolean | undefined; } diff --git a/adapters/auth/index.ts b/adapters/auth/index.ts index 825bd208..6fd9d821 100644 --- a/adapters/auth/index.ts +++ b/adapters/auth/index.ts @@ -1,44 +1,28 @@ -import axios, { isAxiosError } from 'axios' -import { transformSelfInfoFromApi } from './auth.transformer' -import { type ApiResponse } from '~/interfaces/ApiResponse' -import { type User } from '~/interfaces/User' +import axios from 'axios' +import { transformSelfInfoFromApi, transformGoalTokenFromApi } from './auth.transformer' import { type SelfInfo } from './auth.type' -import { - type ErrorApiForbidden, - type ErrorApiUnauthorized, - type ErrorApiUnavailable, - type ErrorApiNotFound, - type ErrorApiBase -} from '~/interfaces/ErrorApi' import { getConfig } from '~/config' -type GetSelfResponse = ApiResponse< -{ - user: User - info: SelfInfo -}, -ErrorApiForbidden | ErrorApiUnauthorized | ErrorApiUnavailable | ErrorApiNotFound | ErrorApiBase -> - -export const getSelf = async (): Promise => { +export const getSelf = async (): Promise => { const config = getConfig(); - const response: GetSelfResponse = await axios + const response = await axios .get(`${config.RDS_API}/users/self/`, { withCredentials: true }) - .then(res => ({ data: transformSelfInfoFromApi(res.data) })) - .catch((error) => { - if (isAxiosError(error)) { - if (error.response != null) { - const responseData = error.response.data - return { - error: { - status: responseData.statusCode, - error: responseData.error, - message: responseData.message - } - } - } - } - throw error // or Log here - }) + .then(res => transformSelfInfoFromApi(res.data)) + return response } + +export const getGoalApiCredentials = async (): Promise<{ + token: { + exp: number, + access: string, + }, + id: string, +}> => { + const config = getConfig(); + const response = await axios + .get(`${config.RDS_API}/goals/token/`, { withCredentials: true }) + .then(res => transformGoalTokenFromApi(res.data)) + + return response +} \ No newline at end of file diff --git a/middleware/authenticate.global.ts b/middleware/authenticate.global.ts new file mode 100644 index 00000000..6f31fce4 --- /dev/null +++ b/middleware/authenticate.global.ts @@ -0,0 +1,9 @@ +import { useAuthStore } from "~/store/auth" + +export default defineNuxtRouteMiddleware((to, from) => { + const authStore = useAuthStore(); + + if (!authStore.user) { + authStore.signin(); + } +}) \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index 33dcf638..a8f6094c 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -39,6 +39,7 @@ export default defineNuxtConfig({ public: { GOALS_API: process.env.NUXT_PUBLIC_GOAL_API, RDS_API: process.env.NUXT_PUBLIC_RDS_API, + ENV: process.env.NUXT_PUBLIC_ENV, } }, imports: { diff --git a/store/auth.ts b/store/auth.ts index 0f955ccb..b30d0971 100644 --- a/store/auth.ts +++ b/store/auth.ts @@ -1,97 +1,45 @@ -import { defineStore } from 'pinia' import * as authAdapter from '~/adapters/auth' -import { type AuthState } from '~/interfaces/AuthState' -import { type ErrorApiBase } from '~/interfaces/ErrorApi' -import { userRepo } from '~/models/User' +import { getConfig } from '~/config' -type AuthStoreState = - | { - kind: 'UNAUTHENTICATED' - isLoading: boolean - error: null - authInfo: null - } - | { - kind: 'ERRORED' - isLoading: false - error: ErrorApiBase - authInfo: null - } - | { - kind: 'AUTHENTICATED' - isLoading: false - error: null - authInfo: { - userId: string - status: string - roles: { - archived?: boolean - member?: boolean - } - } +export const useAuthStore = defineStore('auth', () => { + const config = getConfig() + const user = ref() + const token = useCookie(`goals_token_${config.ENV}`, { + maxAge: 60 * 60 * 24 * 30, // 30 days + sameSite: 'lax' + }) + + const setToken = (newToken: string) => { + token.value = newToken } -export const useAuthStore = defineStore({ - id: 'auth-store', - state: (): AuthStoreState => ({ - kind: 'UNAUTHENTICATED', - isLoading: false, - error: null, - authInfo: null - }), - actions: { - async fetchSelf () { - if (this.isLoading) return + const setUser = (user: any) => { + user.value = user + } - this.isLoading = true + const signin = async () => { + try { + const [selfData, credentials] = await Promise.all([authAdapter.getSelf(), authAdapter.getGoalApiCredentials()]) - try { - const { data, error } = await authAdapter.getSelf() - if (error != null) { - this.$patch({ - kind: 'ERRORED', - error, - isLoading: false, - authInfo: null - }) - } else if (data != null) { - this.$patch({ - kind: 'AUTHENTICATED', - authInfo: data.info, - isLoading: false, - error: null - }) - userRepo.save(data.user) - } - } catch (e) { - console.error(e) - } + user.value = selfData + token.value = credentials.token.access + } catch (error) { + user.value = undefined + token.value = undefined } - }, - getters: { - getAuthState (state): AuthState { - if (state.kind === 'UNAUTHENTICATED') { - return { - kind: 'UNAUTHENTICATED', - isLoading: state.isLoading - } - } else if (state.kind === 'ERRORED') { - return { - kind: 'UNAUTHENTICATED', - isLoading: false, - error: state.error - } - } else { - const user = userRepo.find(state.authInfo.userId) - if (user == null) throw new Error('User not found in repository') + } - return { - kind: 'AUTHENTICATED', - isLoading: false, - roles: state.authInfo.roles, - user - } - } - } + const signout = () => { + token.value = undefined + user.value = undefined + } + + return { + user, + token, + setToken, + setUser, + signin, + signout, } -}) +}) \ No newline at end of file diff --git a/store/goals.ts b/store/goals.ts index b3dd5196..56369db8 100644 --- a/store/goals.ts +++ b/store/goals.ts @@ -12,6 +12,7 @@ export const useGoalListQuery = (query: GetGoalQuery, options = {}) => { const response = useQuery({ queryFn: () => goalAdapter.fetchGoals(query), queryKey: ['goals', 'list', query], + staleTime: 1000 * 60, }) return response From b5e44e997a7c26c088b2c0219f6845f1b8ff25e5 Mon Sep 17 00:00:00 2001 From: pallabez Date: Wed, 3 Jan 2024 19:03:04 +0530 Subject: [PATCH 2/3] feat: use auth store for navbar login --- components/AppNavBar.vue | 88 ++++++++++++++++++++++++++-------------- layouts/default.vue | 10 ++--- store/auth.ts | 5 +++ 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/components/AppNavBar.vue b/components/AppNavBar.vue index a20e05b7..4cf31828 100644 --- a/components/AppNavBar.vue +++ b/components/AppNavBar.vue @@ -12,43 +12,61 @@ - - - + - diff --git a/layouts/default.vue b/layouts/default.vue index 956e215f..9298cacf 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,7 +1,7 @@ -