Skip to content

Commit

Permalink
feat(auth): introduce sign-in guard
Browse files Browse the repository at this point in the history
- Add `useEnsureSignedIn` to prompt users to sign in before executing
  protected actions.
- Add similar protection for specific routes using navigation guards.

Fixes goplus#974

Signed-off-by: Aofei Sheng <aofei@aofeisheng.com>
  • Loading branch information
aofei committed Oct 18, 2024
1 parent 2eee8bf commit 5799801
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 22 deletions.
4 changes: 4 additions & 0 deletions spx-gui/src/components/community/user/FollowButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useUserStore } from '@/stores'
import { follow, isFollowing, unfollow } from '@/apis/user'
import { UIButton } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
import { useEnsureSignedIn } from '@/utils/user'
const props = defineProps<{
/** Name of user to follow */
Expand All @@ -23,8 +24,11 @@ watch(
{ immediate: true }
)
const ensureSignedIn = useEnsureSignedIn()
const handleClick = useMessageHandle(
async () => {
await ensureSignedIn()
await (following.value ? unfollow(props.name) : follow(props.name))
following.value = !following.value
},
Expand Down
3 changes: 3 additions & 0 deletions spx-gui/src/components/navbar/NavbarNewProjectItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import { UIMenuItem } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
import { useCreateProject } from '@/components/project'
import newSvg from './icons/new.svg'
import { useEnsureSignedIn } from '@/utils/user'
const router = useRouter()
const ensureSignedIn = useEnsureSignedIn()
const createProject = useCreateProject()
const handleNewProject = useMessageHandle(
async () => {
await ensureSignedIn()
const { name } = await createProject()
router.push(getProjectEditorRoute(name))
},
Expand Down
16 changes: 12 additions & 4 deletions spx-gui/src/components/navbar/NavbarOpenProjectItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ import { UIMenuItem } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
import { useOpenProject } from '@/components/project'
import openSvg from './icons/open.svg'
import { useEnsureSignedIn } from '@/utils/user'
const ensureSignedIn = useEnsureSignedIn()
const openProject = useOpenProject()
const handleOpenProject = useMessageHandle(openProject, {
en: 'Failed to open project',
zh: '打开项目失败'
}).fn
const handleOpenProject = useMessageHandle(
async () => {
await ensureSignedIn()
return openProject()
},
{
en: 'Failed to open project',
zh: '打开项目失败'
}
).fn
</script>
9 changes: 6 additions & 3 deletions spx-gui/src/components/navbar/NavbarProfile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
</UIMenuItem>
</UIMenuGroup>
<UIMenuGroup>
<UIMenuItem @click="userStore.signOut()">{{
$t({ en: 'Sign out', zh: '登出' })
}}</UIMenuItem>
<UIMenuItem @click="handleSignOut">{{ $t({ en: 'Sign out', zh: '登出' }) }}</UIMenuItem>
</UIMenuGroup>
</UIMenu>
</UIDropdown>
Expand All @@ -54,6 +52,11 @@ function handleUserPage() {
function handleProjects() {
router.push(getUserPageRoute(userInfo.value!.name, 'projects'))
}
function handleSignOut() {
userStore.signOut()
router.go(0) // Reload the page to trigger navigation guards.
}
</script>

<style lang="scss" scoped>
Expand Down
7 changes: 5 additions & 2 deletions spx-gui/src/pages/community/explore.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,17 @@ import ListResultWrapper from '@/components/common/ListResultWrapper.vue'
import CenteredWrapper from '@/components/community/CenteredWrapper.vue'
import CommunityHeader from '@/components/community/CommunityHeader.vue'
import ProjectItem from '@/components/project/ProjectItem.vue'
import { useEnsureSignedIn } from '@/utils/user'
const order = useRouteQueryParamStrEnum('o', Order, Order.MostLikes)
const maxCount = 50
const ensureSignedIn = useEnsureSignedIn()
const queryRet = useQuery(
() => {
// TODO: login prompt for unauthenticated users
async () => {
if (order.value === Order.FollowingCreated) await ensureSignedIn()
return exploreProjects({
order: order.value,
count: maxCount
Expand Down
3 changes: 3 additions & 0 deletions spx-gui/src/pages/community/user/projects.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useCreateProject } from '@/components/project'
import ListResultWrapper from '@/components/common/ListResultWrapper.vue'
import UserContent from '@/components/community/user/content/UserContent.vue'
import ProjectItem from '@/components/project/ProjectItem.vue'
import { useEnsureSignedIn } from '@/utils/user'
const props = defineProps<{
name: string
Expand Down Expand Up @@ -57,9 +58,11 @@ const queryRet = useQuery(() => listProject(listParams.value), {
})
const router = useRouter()
const ensureSignedIn = useEnsureSignedIn()
const createProject = useCreateProject()
const handleNewProject = useMessageHandle(
async () => {
await ensureSignedIn()
const { name } = await createProject()
router.push(getProjectEditorRoute(name))
},
Expand Down
8 changes: 0 additions & 8 deletions spx-gui/src/pages/editor/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,7 @@ const props = defineProps<{
const LOCAL_CACHE_KEY = 'GOPLUS_BUILDER_CACHED_PROJECT'
// TODO: move this to some outer position
const userStore = useUserStore()
watchEffect(() => {
// This will be called on mount and whenever userStore changes,
// which are the cases when userStore.signOut() is called
if (!userStore.isSignedIn()) {
userStore.initiateSignIn()
}
})
const userInfo = computed(() => userStore.userInfo())
Expand Down
14 changes: 10 additions & 4 deletions spx-gui/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { App } from 'vue'
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import type { ExploreOrder } from './apis/project'
import { useUserStore } from './stores'

export function getProjectEditorRoute(projectName: string) {
return `/editor/${projectName}`
Expand Down Expand Up @@ -94,12 +95,9 @@ const routes: Array<RouteRecordRaw> = [
{
path: '/editor/:projectName',
component: () => import('@/pages/editor/index.vue'),
meta: { requiresSignIn: true },
props: true
},
{
path: '/callback', // TODO: remove me
redirect: '/sign-in/callback'
},
{
path: '/sign-in/callback',
component: () => import('@/pages/sign-in/callback.vue')
Expand All @@ -120,6 +118,14 @@ const router = createRouter({
})

export const initRouter = async (app: App) => {
const userStore = useUserStore()
router.beforeEach((to, _, next) => {
if (to.meta.requiresSignIn && !userStore.isSignedIn()) {
userStore.initiateSignIn()
} else {
next()
}
})
app.use(router)
// This is an example of a routing result that needs to be loaded.
await new Promise((resolve) => {
Expand Down
2 changes: 1 addition & 1 deletion spx-gui/src/stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface TokenResponse {
refresh_token: string
}

const casdoorAuthRedirectPath = '/callback'
const casdoorAuthRedirectPath = '/sign-in/callback'
const casdoorSdk = new Sdk({
...casdoorConfig,
redirectPath: casdoorAuthRedirectPath
Expand Down
24 changes: 24 additions & 0 deletions spx-gui/src/utils/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useUserStore } from '@/stores'
import { useI18n } from './i18n'
import { useConfirmDialog } from '@/components/ui'
import { Cancelled } from './exception'

export function useEnsureSignedIn() {
const userStore = useUserStore()
const { t } = useI18n()
const withConfirm = useConfirmDialog()

return async () => {
if (userStore.isSignedIn()) return
await withConfirm({
title: t({ en: 'Sign in to continue', zh: '登录以继续' }),
content: t({
en: 'You need to sign in first to perform this action. Would you like to sign in now?',
zh: '你需要先登录才能执行此操作。你想现在登录吗?'
}),
confirmText: t({ en: 'Sign in', zh: '登录' }),
confirmHandler: () => userStore.initiateSignIn()
})
throw new Cancelled('redirected to sign in')
}
}

0 comments on commit 5799801

Please sign in to comment.