You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

196 lines
5.7 KiB

<template>
<h2 class="mb-3 text-2xl font-bold text-center xl:text-3xl enter-x xl:text-left">
{{ client.name + t('sys.login.ssoSignInFormTitle') }}
</h2>
<Form class="p-4 enter-x" :model="loginForm" ref="formRef" @keypress.enter="handleAuthorize(true)">
此第三方应用请求获取以下权限
<Row class="enter-x">
<Col :span="12">
<template v-for="scope in params.scopes" :key="scope">
<FormItem>
<!-- No logic, you need to deal with it yourself -->
<Checkbox :checked="scope" size="small">
<Button type="link" size="small">
{{ formatScope(scope) }}
</Button>
</Checkbox>
</FormItem>
</template>
</Col>
</Row>
<FormItem class="enter-x">
<Button type="primary" size="large" block @click="handleAuthorize(true)" :loading="loading">
{{ t('sys.login.loginButton') }}
</Button>
<Button size="large" class="mt-4 enter-x" block @click="handleAuthorize(false)">
{{ t('common.cancelText') }}
</Button>
</FormItem>
</Form>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Checkbox, Form, Row, Col, Button } from 'ant-design-vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useFormValid } from './useLogin'
import { useDesign } from '@/hooks/web/useDesign'
import { authorize, getAuthorize } from '@/api/base/login'
import { onMounted } from 'vue'
const FormItem = Form.Item
const { t } = useI18n()
const { query } = useRoute()
const { notification, createErrorModal } = useMessage()
const { prefixCls } = useDesign('login')
const formRef = ref()
const loading = ref(false)
const loginForm = reactive({
scopes: [] as any[] // 已选中的 scope 数组
})
// URL 上的 client_id、scope 等参数
const params = reactive({
responseType: undefined as any,
clientId: undefined as any,
redirectUri: undefined as any,
state: undefined as any,
scopes: [] as any[] // 优先从 query 参数获取;如果未传递,从后端获取
})
// 客户端信息
let client = reactive({
name: '',
logo: ''
})
const { validForm } = useFormValid(formRef)
async function init() {
// 解析参数
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
params.responseType = query.response_type as any
params.clientId = query.client_id as any
params.redirectUri = query.redirect_uri as any
params.state = query.state as any
if (query.scope) {
params.scopes = (query.scope as any).split(' ')
}
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
if (params.scopes.length > 0) {
const res = await doAuthorize(true, params.scopes, [])
const href = res
if (!href) {
console.log('自动授权未通过!')
return
}
location.href = href
}
// 获取授权页的基本信息
const res = await getAuthorize(params.clientId)
client = res.client
// 解析 scope
let scopes
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes
if (params.scopes.length > 0) {
scopes = []
for (const scope of res.scopes) {
if (params.scopes.indexOf(scope.key) >= 0) {
scopes.push(scope)
}
}
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
} else {
scopes = res.data.scopes
for (const scope of scopes) {
params.scopes.push(scope.key)
}
}
// 生成已选中的 checkedScopes
for (const scope of scopes) {
if (scope.value) {
loginForm.scopes.push(scope.key)
}
}
}
async function handleAuthorize(approved) {
const data = await validForm()
if (!data) return
try {
loading.value = true
// 计算 checkedScopes + uncheckedScopes
let checkedScopes
let uncheckedScopes
if (approved) {
// 同意授权,按照用户的选择
checkedScopes = loginForm.scopes
uncheckedScopes = params.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
} else {
// 拒绝,则都是取消
checkedScopes = []
uncheckedScopes = params.scopes
}
// 提交授权的请求
const res = await doAuthorize(false, checkedScopes, uncheckedScopes)
if (res) {
const href = res
if (!href) {
return
}
location.href = href
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}`,
duration: 3
})
}
} catch (error) {
createErrorModal({
title: t('sys.api.errorTip'),
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'),
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body
})
} finally {
loading.value = false
}
}
async function doAuthorize(autoApprove, checkedScopes, uncheckedScopes) {
return await authorize(
params.responseType,
params.clientId,
params.redirectUri,
params.state,
autoApprove,
checkedScopes,
uncheckedScopes
)
}
function formatScope(scope) {
// 格式化 scope 授权范围,方便用户理解。
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
switch (scope) {
case 'user.read':
return t('sys.login.ssoInfoDesc')
case 'user.write':
return t('sys.login.ssoEditDesc')
default:
return scope
}
}
onMounted(() => {
init()
})
</script>