From 313e229ef7f01cb136a5484baf6e8ead8d8685d1 Mon Sep 17 00:00:00 2001 From: KabinKhandThakuri Date: Mon, 9 Dec 2024 17:28:29 +0545 Subject: [PATCH] Implement feature to verify role while logging in (#419) * feat: implement feature to verify role while log in * feat: redirect to home page on basis of role --- packages/vue-user/src/store.ts | 7 ++- packages/vue-user/src/supertokens/helper.ts | 47 +++++++++++++++++++++ packages/vue-user/src/supertokens/index.ts | 2 + packages/vue-user/src/types/config.ts | 1 + packages/vue-user/src/views/Login.vue | 40 +++++++++++------- 5 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 packages/vue-user/src/supertokens/helper.ts diff --git a/packages/vue-user/src/store.ts b/packages/vue-user/src/store.ts index ce02d28b3..a333f8f0a 100644 --- a/packages/vue-user/src/store.ts +++ b/packages/vue-user/src/store.ts @@ -39,7 +39,7 @@ const useUserStore = defineStore("user", () => { const login = async (credentials: LoginCredentials) => { const response = await doLogin(credentials); - setUser(response); + return response; }; const logout = async () => { @@ -51,6 +51,10 @@ const useUserStore = defineStore("user", () => { "sFrontToken=; Max-Age=0; path=/; domain=" + location.hostname; }); + removeUser(); + }; + + const removeUser = () => { localStorage.removeItem(USER_KEY); }; @@ -83,6 +87,7 @@ const useUserStore = defineStore("user", () => { getUser, login, logout, + removeUser, resetPassword, requestPasswordReset, setUser, diff --git a/packages/vue-user/src/supertokens/helper.ts b/packages/vue-user/src/supertokens/helper.ts new file mode 100644 index 000000000..591b93162 --- /dev/null +++ b/packages/vue-user/src/supertokens/helper.ts @@ -0,0 +1,47 @@ +import Session from "supertokens-web-js/recipe/session"; +import { UserRoleClaim } from "supertokens-web-js/recipe/userroles"; + +import useUserStore from "../store"; + +export async function verifySessionRoles(claims: string[]): Promise { + if (await Session.doesSessionExist()) { + let errorCount = 0; + + const validationErrors = await Session.validateClaims({ + overrideGlobalClaimValidators: (globalValidators) => { + const validators = claims.map((claim) => + UserRoleClaim.validators.includes(claim), + ); + + return [...globalValidators, ...validators]; + }, + }); + + if (validationErrors.length === 0) { + return true; + } + + for (const err of validationErrors) { + if (err.validatorId === UserRoleClaim.id) { + // user roles claim check failed + errorCount += 1; + } else { + // some other claim check failed (from the global validators list) + } + } + + if (errorCount < claims.length) { + // some user roles claim check passed + return true; + } else { + const userStore = useUserStore(); + + const { logout } = userStore; + // all user roles claim check failed + await logout(); + } + } + // either a session does not exist, or one of the validators failed. + // so we do not allow access to this page. + return false; +} diff --git a/packages/vue-user/src/supertokens/index.ts b/packages/vue-user/src/supertokens/index.ts index d89b574d6..067315c67 100644 --- a/packages/vue-user/src/supertokens/index.ts +++ b/packages/vue-user/src/supertokens/index.ts @@ -4,6 +4,7 @@ import Session from "supertokens-web-js/recipe/session"; import ThirdPartyEmailPassword from "supertokens-web-js/recipe/thirdpartyemailpassword"; import googleSignIn from "./google-signin"; +import { verifySessionRoles } from "./helper"; import login from "./login"; import logout from "./logout"; import requestPasswordReset from "./request-password-reset"; @@ -66,4 +67,5 @@ export { resetPassword, signup, verifyEmail, + verifySessionRoles, }; diff --git a/packages/vue-user/src/types/config.ts b/packages/vue-user/src/types/config.ts index 8f1d58bcf..201dc6239 100644 --- a/packages/vue-user/src/types/config.ts +++ b/packages/vue-user/src/types/config.ts @@ -19,6 +19,7 @@ interface DzangolabVueUserConfig { }; routes?: RouteOverrides; socialLogins?: string[]; + supportedRoles?: string[]; } declare module "@dzangolab/vue3-config" { diff --git a/packages/vue-user/src/views/Login.vue b/packages/vue-user/src/views/Login.vue index 3fa2e5978..86052786b 100644 --- a/packages/vue-user/src/views/Login.vue +++ b/packages/vue-user/src/views/Login.vue @@ -38,7 +38,6 @@ export default { import { useConfig } from "@dzangolab/vue3-config"; import { useI18n } from "@dzangolab/vue3-i18n"; import { Errors, Page } from "@dzangolab/vue3-ui"; -import { storeToRefs } from "pinia"; import { ref } from "vue"; import { useRouter } from "vue-router"; @@ -46,6 +45,7 @@ import GoogleLogin from "../components/GoogleLogin.vue"; import LoginForm from "../components/LoginForm.vue"; import { useTranslations } from "../index"; import useUserStore from "../store"; +import { verifySessionRoles } from "../supertokens"; import type { LoginCredentials } from "../types"; import type { AppConfig } from "@dzangolab/vue3-config"; @@ -59,8 +59,7 @@ const messages = useTranslations(); const { t } = useI18n({ messages }); const userStore = useUserStore(); -const { login } = userStore; -const { user } = storeToRefs(userStore); +const { login, setUser } = userStore; const router = useRouter(); @@ -71,18 +70,29 @@ const loading = ref(false); const handleSubmit = async (credentials: LoginCredentials) => { loading.value = true; - await login(credentials).catch((error) => { - errors.value = [ - { - code: error.message, - message: t(`user.login.errors.${error.message}`), - }, - ]; - }); - - if (user.value) { - router.push({ name: "home" }); - } + await login(credentials) + .then(async (response) => { + if (response) { + const supportedRoles = config?.user?.supportedRoles; + + if ( + (supportedRoles && (await verifySessionRoles(supportedRoles))) || + !supportedRoles?.length + ) { + setUser(response); + + router.push({ name: "home" }); + } + } + }) + .catch((error) => { + errors.value = [ + { + code: error.message, + message: t(`user.login.errors.${error.message}`), + }, + ]; + }); loading.value = false; };