mirror of
https://github.com/certd/certd.git
synced 2026-04-24 12:27:25 +08:00
perf: 第三方登录支持gitee
This commit is contained in:
@@ -74,6 +74,7 @@ const sysPublic: Ref<SysPublicSetting> = computed(() => {
|
|||||||
.login-container {
|
.login-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
background: #f0f2f5 url(/static/background.svg) no-repeat 50%;
|
background: #f0f2f5 url(/static/background.svg) no-repeat 50%;
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
//padding: 50px 0 84px;
|
//padding: 50px 0 84px;
|
||||||
|
|||||||
@@ -16,12 +16,14 @@
|
|||||||
<a-descriptions-item :label="t('authentication.email')">{{ userInfo.email }}</a-descriptions-item>
|
<a-descriptions-item :label="t('authentication.email')">{{ userInfo.email }}</a-descriptions-item>
|
||||||
<a-descriptions-item :label="t('authentication.phoneNumber')">{{ userInfo.phoneCode }}{{ userInfo.mobile }}</a-descriptions-item>
|
<a-descriptions-item :label="t('authentication.phoneNumber')">{{ userInfo.phoneCode }}{{ userInfo.mobile }}</a-descriptions-item>
|
||||||
<a-descriptions-item v-if="settingStore.sysPublic.oauthEnabled && settingStore.isPlus" label="第三方账号绑定">
|
<a-descriptions-item v-if="settingStore.sysPublic.oauthEnabled && settingStore.isPlus" label="第三方账号绑定">
|
||||||
<div v-for="item in computedOauthBounds" :key="item.name" class="flex items-center gap-2 mb-2">
|
<template v-for="item in computedOauthBounds" :key="item.name">
|
||||||
<fs-icon :icon="item.icon" class="mr-2 text-blue-500" />
|
<div v-if="item.addonId" class="flex items-center gap-2 mb-2">
|
||||||
<span class="mr-2 w-36">{{ item.title }}</span>
|
<fs-icon :icon="item.icon" class="mr-2 text-blue-500 w-5 flex justify-center items-center" />
|
||||||
<a-button v-if="item.bound" type="primary" danger @click="unbind(item.name)">解绑</a-button>
|
<span class="mr-2 w-36">{{ item.title }}</span>
|
||||||
<a-button v-else type="primary" @click="bind(item.name)">绑定</a-button>
|
<a-button v-if="item.bound" type="primary" danger @click="unbind(item.name)">解绑</a-button>
|
||||||
</div>
|
<a-button v-else type="primary" @click="bind(item.name)">绑定</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item :label="t('common.handle')">
|
<a-descriptions-item :label="t('common.handle')">
|
||||||
<a-button type="primary" @click="doUpdate">{{ t("authentication.updateProfile") }}</a-button>
|
<a-button type="primary" @click="doUpdate">{{ t("authentication.updateProfile") }}</a-button>
|
||||||
@@ -40,6 +42,7 @@ import { useI18n } from "/src/locales";
|
|||||||
import { useUserProfile } from "./use";
|
import { useUserProfile } from "./use";
|
||||||
import { Modal } from "ant-design-vue";
|
import { Modal } from "ant-design-vue";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSettingStore } from "/@/store/settings";
|
||||||
|
import { isEmpty } from "lodash-es";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<template v-for="item in oauthProviderList" :key="item.type">
|
<template v-for="item in oauthProviderList" :key="item.type">
|
||||||
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item.name)">
|
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item.name)">
|
||||||
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
||||||
<div>{{ item.addonTitle || item.title }}</div>
|
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,6 +101,12 @@ async function goOauthLogin(type: string) {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 8px;
|
padding: 8px 8px;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
|
width: 100px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.fs-icon {
|
.fs-icon {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
color: #006be6;
|
color: #006be6;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './api.js'
|
export * from './api.js'
|
||||||
export * from './oidc/plugin-oidc.js'
|
export * from './oidc/plugin-oidc.js'
|
||||||
export * from './wx/plugin-wx.js'
|
export * from './wx/plugin-wx.js'
|
||||||
|
export * from './oauth2/plugin-gitee.js'
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
||||||
|
import { BuildLoginUrlReq, BuildLogoutUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
|
||||||
|
|
||||||
|
@IsAddon({
|
||||||
|
addonType: "oauth",
|
||||||
|
name: 'gitee',
|
||||||
|
title: 'Gitee认证',
|
||||||
|
desc: 'Gitee OAuth2登录',
|
||||||
|
icon:"simple-icons:gitee:red",
|
||||||
|
showTest: false,
|
||||||
|
})
|
||||||
|
export class GiteeOauthProvider extends BaseAddon implements IOauthProvider {
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "ClientId",
|
||||||
|
helper: "[gitee 第三方应用管理](https://gitee.com/oauth/applications)创建应用后获取",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
clientId = "";
|
||||||
|
|
||||||
|
@AddonInput({
|
||||||
|
title: "ClientSecretKey",
|
||||||
|
component: {
|
||||||
|
placeholder: "ClientSecretKey / appSecretKey",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
clientSecretKey = "";
|
||||||
|
|
||||||
|
// @AddonInput({
|
||||||
|
// title: "授权地址",
|
||||||
|
// helper: "授权请求url",
|
||||||
|
// component: {
|
||||||
|
// placeholder: "https://xxxxx.com/oauth/authorize",
|
||||||
|
// },
|
||||||
|
// required: true,
|
||||||
|
// })
|
||||||
|
// authorizeEndpoint = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gitee.authorizeURL = https://gitee.com/oauth/authorize
|
||||||
|
gitee.accessToken = https://gitee.com/oauth/token
|
||||||
|
gitee.userInfo = https://gitee.com/api/v5/user
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @AddonInput({
|
||||||
|
// title: "Token获取地址",
|
||||||
|
// helper: "Token获取url",
|
||||||
|
// component: {
|
||||||
|
// placeholder: "https://xxxxx.com/oauth/token",
|
||||||
|
// },
|
||||||
|
// required: true,
|
||||||
|
// })
|
||||||
|
// tokenEndpoint = "";
|
||||||
|
|
||||||
|
// @AddonInput({
|
||||||
|
// title: "用户信息获取地址",
|
||||||
|
// helper: "用户信息url",
|
||||||
|
// component: {
|
||||||
|
// placeholder: "https://xxxxx.com/api/user_info",
|
||||||
|
// },
|
||||||
|
// required: true,
|
||||||
|
// })
|
||||||
|
// userInfoEndpoint = "";
|
||||||
|
|
||||||
|
// @AddonInput({
|
||||||
|
// title: "Scope",
|
||||||
|
// helper: "授权Scope",
|
||||||
|
// value:"user_info",
|
||||||
|
// component: {
|
||||||
|
// placeholder: "profile",
|
||||||
|
// },
|
||||||
|
// required: true,
|
||||||
|
// })
|
||||||
|
// scope: string;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
|
let scope = "user_info" // Scope of the access request
|
||||||
|
let state:any = {
|
||||||
|
forType: params.forType || 'login',
|
||||||
|
}
|
||||||
|
state = this.ctx.utils.hash.base64(JSON.stringify(state))
|
||||||
|
|
||||||
|
const authorizeEndpoint = "https://gitee.com/oauth/authorize"
|
||||||
|
const redirectUrl = encodeURIComponent(params.redirectUri)
|
||||||
|
// https://gitee.com/oauth/authorize?client_id=5bb5f4158af41c50c7a17b5d9068244e97d3ee572def6a57ed32fd8c9d760ad1&redirect_uri=http%3A%2F%2Fcasdoor.docmirror.cn%3A8000%2Fcallback&response_type=code
|
||||||
|
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
|
||||||
|
return {
|
||||||
|
loginUrl,
|
||||||
|
ticketValue: {
|
||||||
|
state,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async onCallback(req: OnCallbackReq) {
|
||||||
|
|
||||||
|
//校验state
|
||||||
|
|
||||||
|
const code = req.code || ""
|
||||||
|
|
||||||
|
const tokenEndpoint = "https://gitee.com/oauth/token"
|
||||||
|
|
||||||
|
const uri = new URL(req.currentURL)
|
||||||
|
const redirectUri = `${uri.origin}${uri.pathname}`
|
||||||
|
const res = await this.ctx.utils.http.request( {
|
||||||
|
url: tokenEndpoint,
|
||||||
|
method: "post",
|
||||||
|
data:{
|
||||||
|
// https://gitee.com/oauth/token?
|
||||||
|
// grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code,
|
||||||
|
client_id: this.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
client_secret: this.clientSecretKey,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const tokens = res
|
||||||
|
|
||||||
|
|
||||||
|
const userInfoEndpoint = "https://gitee.com/api/v5/user"
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const userInfoRes = await this.ctx.utils.http.request( {
|
||||||
|
url: userInfoEndpoint,
|
||||||
|
method: "get",
|
||||||
|
params:{
|
||||||
|
access_token: tokens.access_token,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const userInfo = userInfoRes
|
||||||
|
|
||||||
|
return {
|
||||||
|
token:{
|
||||||
|
accessToken: tokens.access_token,
|
||||||
|
refreshToken: tokens.refresh_token,
|
||||||
|
expiresIn: tokens.expires_in,
|
||||||
|
},
|
||||||
|
userInfo: {
|
||||||
|
openId: userInfo.id,
|
||||||
|
nickName: userInfo.name || userInfo.nick_name || "",
|
||||||
|
avatar: userInfo.avatar_url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async buildLogoutUrl(params: BuildLogoutUrlReq) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user