(cherry picked from commit 8933b95e6148670f60ca6c7bd7568d9f19774cc8)
This commit is contained in:
LeeQianXi
2026-08-10 13:19:36 +08:00
parent 5c8c8a6dc7
commit b65e77f9a9
7 changed files with 274 additions and 24 deletions
+12
View File
@@ -4,6 +4,18 @@ export function oauthLinuxDoLogin(code) {
return http.post('/oauth/linuxDo/login',{code}) return http.post('/oauth/linuxDo/login',{code})
} }
export function oauthGithubLogin(code) {
return http.post('/oauth/github/login',{code})
}
export function oauthGitlabLogin(code) {
return http.post('/oauth/gitlab/login',{code})
}
export function oauthGoogleLogin(code) {
return http.post('/oauth/google/login',{code})
}
export function oauthBindUser(form) { export function oauthBindUser(form) {
return http.put('/oauth/bindUser', form) return http.put('/oauth/bindUser', form)
} }
+23 -3
View File
@@ -59,6 +59,26 @@ const routes = [
name: 'login', name: 'login',
component: () => import('@/views/login/index.vue') component: () => import('@/views/login/index.vue')
}, },
{
path: '/login/github',
name: 'loginGithub',
component: () => import('@/views/login/index.vue')
},
{
path: '/login/gitlab',
name: 'loginGitlab',
component: () => import('@/views/login/index.vue')
},
{
path: '/login/google',
name: 'loginGoogle',
component: () => import('@/views/login/index.vue')
},
{
path: '/login/linuxdo',
name: 'loginLinuxdo',
component: () => import('@/views/login/index.vue')
},
{ {
path: '/test', path: '/test',
name: 'test', name: 'test',
@@ -100,16 +120,16 @@ router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (!token && to.name !== 'login') { if (!token && !to.path.startsWith('/login')) {
return next({name: 'login'}) return next({name: 'login'})
} }
if (!token && to.name === 'login') { if (!token && to.path.startsWith('/login')) {
loadBackground(next) loadBackground(next)
return return
} }
if (token && to.name === 'login') { if (token && to.path.startsWith('/login')) {
return next(from.path) return next(from.path)
} }
+48 -13
View File
@@ -44,8 +44,10 @@
<el-button class="btn" type="primary" @click="submit" :loading="loginLoading" <el-button class="btn" type="primary" @click="submit" :loading="loginLoading"
>{{ $t('loginBtn') }} >{{ $t('loginBtn') }}
</el-button> </el-button>
<el-button class="btn" v-if="settingStore.settings.linuxdoSwitch" style="margin-top: 10px" @click="linuxDoLogin"> <el-button v-for="p in oauthProviders" :key="p.key" class="btn" style="margin-top: 10px" @click="oauthLogin(p.key)">
<el-avatar src="/image/linuxdo.webp" :size="18" style="margin-right: 10px" />LinuxDo <el-avatar v-if="p.iconType === 'image'" :src="p.icon" :size="18" style="margin-right: 10px" />
<Icon v-else :icon="p.icon" width="18" height="18" style="margin-right: 10px" />
{{ p.label }}
</el-button> </el-button>
</div> </div>
<div v-show="show !== 'login'"> <div v-show="show !== 'login'">
@@ -94,8 +96,10 @@
<el-button class="btn" style="margin: 0" type="primary" @click="submitRegister" :loading="registerLoading" <el-button class="btn" style="margin: 0" type="primary" @click="submitRegister" :loading="registerLoading"
>{{ $t('regBtn') }} >{{ $t('regBtn') }}
</el-button> </el-button>
<el-button v-if="settingStore.settings.linuxdoSwitch" class="btn" style="margin-top: 10px" @click="linuxDoLogin"> <el-button v-for="p in oauthProviders" :key="p.key" class="btn" style="margin-top: 10px" @click="oauthLogin(p.key)">
<el-avatar src="/image/linuxdo.webp" :size="18" style="margin-right: 10px" />LinuxDo <el-avatar v-if="p.iconType === 'image'" :src="p.icon" :size="18" style="margin-right: 10px" />
<Icon v-else :icon="p.icon" width="18" height="18" style="margin-right: 10px" />
{{ p.label }}
</el-button> </el-button>
</div> </div>
<template v-if="settingStore.settings.register === 0"> <template v-if="settingStore.settings.register === 0">
@@ -148,6 +152,7 @@
<script setup> <script setup>
import router from "@/router"; import router from "@/router";
import {useRoute} from "vue-router";
import {computed, nextTick, reactive, ref} from "vue"; import {computed, nextTick, reactive, ref} from "vue";
import {login} from "@/request/login.js"; import {login} from "@/request/login.js";
import {register} from "@/request/login.js"; import {register} from "@/request/login.js";
@@ -162,19 +167,35 @@ import {cvtR2Url} from "@/utils/convert.js";
import {loginUserInfo} from "@/request/my.js"; import {loginUserInfo} from "@/request/my.js";
import {permsToRouter} from "@/perm/perm.js"; import {permsToRouter} from "@/perm/perm.js";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import {oauthBindUser, oauthLinuxDoLogin} from "@/request/ouath.js"; import {oauthBindUser, oauthLinuxDoLogin, oauthGithubLogin, oauthGitlabLogin, oauthGoogleLogin} from "@/request/ouath.js";
const {t} = useI18n(); const {t} = useI18n();
const accountStore = useAccountStore(); const accountStore = useAccountStore();
const userStore = useUserStore(); const userStore = useUserStore();
const uiStore = useUiStore(); const uiStore = useUiStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const route = useRoute();
const loginLoading = ref(false) const loginLoading = ref(false)
const bindLoading = ref(false) const bindLoading = ref(false)
const oauthLoading = ref(false); const oauthLoading = ref(false);
const showBindForm = ref(false); const showBindForm = ref(false);
const show = ref('login') const show = ref('login')
const oauthProvider = computed(() => {
const match = route.path.match(/^\/login\/(.+)/)
return match ? match[1] : null
})
const oauthProviders = computed(() => {
const allProviders = [
{ key: 'linuxdo', label: 'LinuxDo', icon: '/image/linuxdo.webp', iconType: 'image' },
{ key: 'github', label: 'GitHub', icon: 'mingcute:github-fill', iconType: 'iconify' },
{ key: 'gitlab', label: 'GitLab', icon: 'mingcute:gitlab-fill', iconType: 'iconify' },
{ key: 'google', label: 'Google', icon: 'mingcute:google-fill', iconType: 'iconify' },
]
return allProviders.filter(p => settingStore.settings[p.key + 'Switch'])
})
const bindForm = reactive({ const bindForm = reactive({
email: '', email: '',
oauthUserId: '', oauthUserId: '',
@@ -261,16 +282,30 @@ const getEmailName = (email) => {
return email.split('@')[0] return email.split('@')[0]
} }
function linuxDoLogin() { function oauthLogin(provider) {
const clientId = settingStore.settings.linuxdoClientId const clientId = settingStore.settings[provider + 'ClientId']
const redirectUri = encodeURIComponent(settingStore.settings.linuxdoCallbackUrl) const redirectUri = encodeURIComponent(window.location.origin + '/login/' + provider)
window.location.href = const authorizeUrls = {
`https://connect.linux.do/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=openid+profile+email` linuxdo: `https://connect.linux.do/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=openid+profile+email`,
github: `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=user:email`,
gitlab: `https://gitlab.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=read_user`,
google: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=openid+profile+email`,
}
window.location.href = authorizeUrls[provider]
} }
linuxDoGetUser(); const loginFns = {
linuxdo: oauthLinuxDoLogin,
github: oauthGithubLogin,
gitlab: oauthGitlabLogin,
google: oauthGoogleLogin,
}
async function linuxDoGetUser() { oauthGetUser();
async function oauthGetUser() {
if (!oauthProvider.value) return
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
const code = params.get('code') const code = params.get('code')
@@ -278,7 +313,7 @@ async function linuxDoGetUser() {
if (code) { if (code) {
oauthLoading.value = true oauthLoading.value = true
oauthLinuxDoLogin(code).then(data => { loginFns[oauthProvider.value](code).then(data => {
bindForm.oauthUserId = data.userInfo.oauthUserId; bindForm.oauthUserId = data.userInfo.oauthUserId;
+15
View File
@@ -7,6 +7,21 @@ app.post('/oauth/linuxDo/login', async (c) => {
return c.json(result.ok(loginInfo)) return c.json(result.ok(loginInfo))
}); });
app.post('/oauth/github/login', async (c) => {
const loginInfo = await oauthService.githubLogin(c, await c.req.json());
return c.json(result.ok(loginInfo))
});
app.post('/oauth/gitlab/login', async (c) => {
const loginInfo = await oauthService.gitlabLogin(c, await c.req.json());
return c.json(result.ok(loginInfo))
});
app.post('/oauth/google/login', async (c) => {
const loginInfo = await oauthService.googleLogin(c, await c.req.json());
return c.json(result.ok(loginInfo))
});
app.put('/oauth/bindUser', async (c) => { app.put('/oauth/bindUser', async (c) => {
const loginInfo = await oauthService.bindUser(c, await c.req.json()); const loginInfo = await oauthService.bindUser(c, await c.req.json());
return c.json(result.ok(loginInfo)) return c.json(result.ok(loginInfo))
+1
View File
@@ -11,6 +11,7 @@ export const oauth = sqliteTable('oauth', {
trustLevel: integer('trust_level'), trustLevel: integer('trust_level'),
silenced: integer('silenced'), silenced: integer('silenced'),
createTime: text('create_time').default(sql`CURRENT_TIMESTAMP`).notNull(), createTime: text('create_time').default(sql`CURRENT_TIMESTAMP`).notNull(),
platform: text('platform'),
userId: integer('user_id').default(0).notNull() userId: integer('user_id').default(0).notNull()
}); });
+148 -8
View File
@@ -34,9 +34,6 @@ const oauthService = {
const { code } = params; const { code } = params;
let token = '';
let userInfo = {}
const reqParams = new URLSearchParams() const reqParams = new URLSearchParams()
reqParams.append('client_id', c.env.linuxdo_client_id) reqParams.append('client_id', c.env.linuxdo_client_id)
reqParams.append('client_secret', c.env.linuxdo_client_secret) reqParams.append('client_secret', c.env.linuxdo_client_secret)
@@ -54,7 +51,7 @@ const oauthService = {
throw new BizError(tokenRes.statusText) throw new BizError(tokenRes.statusText)
} }
token = await tokenRes.json() const token = await tokenRes.json()
const userRes = await fetch('https://connect.linux.do/api/user', { const userRes = await fetch('https://connect.linux.do/api/user', {
headers: { headers: {
@@ -66,23 +63,166 @@ const oauthService = {
throw new BizError(userRes.statusText) throw new BizError(userRes.statusText)
} }
userInfo = await userRes.json(); const userInfo = await userRes.json();
userInfo.oauthUserId = String(userInfo.id); userInfo.oauthUserId = String(userInfo.id);
userInfo.active = userInfo.active ? 0 : 1; userInfo.active = userInfo.active ? 0 : 1;
userInfo.silenced = userInfo.silenced ? 0 : 1; userInfo.silenced = userInfo.silenced ? 0 : 1;
userInfo.trustLevel = userInfo.trust_level; userInfo.trustLevel = userInfo.trust_level;
userInfo.avatar = userInfo.avatar_url; userInfo.avatar = userInfo.avatar_url;
userInfo.platform = 'linuxdo';
const oauthRow = await this.saveUser(c, userInfo); return await this.saveAndLogin(c, userInfo)
},
async githubLogin(c, params) {
const { code } = params;
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
client_id: c.env.github_client_id,
client_secret: c.env.github_client_secret,
code: code,
redirect_uri: c.env.github_callback_url
})
});
if (!tokenRes.ok) {
throw new BizError(tokenRes.statusText);
}
const token = await tokenRes.json();
if (token.error) {
throw new BizError(token.error_description || token.error);
}
const userRes = await fetch('https://api.github.com/user', {
headers: {
Authorization: 'Bearer ' + token.access_token,
'User-Agent': 'cloud-mail'
}
});
if (!userRes.ok) {
throw new BizError(userRes.statusText);
}
const userInfo = await userRes.json();
userInfo.oauthUserId = String(userInfo.id);
userInfo.username = userInfo.login;
userInfo.avatar = userInfo.avatar_url;
userInfo.platform = 'github';
return await this.saveAndLogin(c, userInfo);
},
async gitlabLogin(c, params) {
const { code } = params;
const reqParams = new URLSearchParams()
reqParams.append('client_id', c.env.gitlab_client_id)
reqParams.append('client_secret', c.env.gitlab_client_secret)
reqParams.append('code', code)
reqParams.append('redirect_uri', c.env.gitlab_callback_url)
reqParams.append('grant_type', 'authorization_code')
const tokenRes = await fetch("https://gitlab.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: reqParams.toString()
});
if (!tokenRes.ok) {
throw new BizError(tokenRes.statusText);
}
const token = await tokenRes.json();
const userRes = await fetch('https://gitlab.com/api/v4/user', {
headers: {
Authorization: 'Bearer ' + token.access_token
}
});
if (!userRes.ok) {
throw new BizError(userRes.statusText);
}
const userInfo = await userRes.json();
userInfo.oauthUserId = String(userInfo.id);
userInfo.username = userInfo.username;
userInfo.name = userInfo.name;
userInfo.avatar = userInfo.avatar_url;
userInfo.platform = 'gitlab';
return await this.saveAndLogin(c, userInfo);
},
async googleLogin(c, params) {
const { code } = params;
const reqParams = new URLSearchParams()
reqParams.append('client_id', c.env.google_client_id)
reqParams.append('client_secret', c.env.google_client_secret)
reqParams.append('code', code)
reqParams.append('redirect_uri', c.env.google_callback_url)
reqParams.append('grant_type', 'authorization_code')
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: reqParams.toString()
});
if (!tokenRes.ok) {
throw new BizError(tokenRes.statusText);
}
const token = await tokenRes.json();
const userRes = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
headers: {
Authorization: 'Bearer ' + token.access_token
}
});
if (!userRes.ok) {
throw new BizError(userRes.statusText);
}
const userInfo = await userRes.json();
userInfo.oauthUserId = String(userInfo.sub);
userInfo.username = userInfo.email;
userInfo.name = userInfo.name;
userInfo.avatar = userInfo.picture;
userInfo.platform = 'google';
return await this.saveAndLogin(c, userInfo);
},
async saveAndLogin(c, userInfo) {
const oauthRow = await this.saveUser(c, userInfo);
const userRow = await userService.selectByIdIncludeDel(c, oauthRow.userId); const userRow = await userService.selectByIdIncludeDel(c, oauthRow.userId);
if (!userRow) { if (!userRow) {
return { userInfo: oauthRow, token: null } return { userInfo: oauthRow, token: null };
} }
const JwtToken = await loginService.login(c, { email: userRow.email, password: null }, true); const JwtToken = await loginService.login(c, { email: userRow.email, password: null }, true);
return { userInfo: oauthRow, token: JwtToken } return { userInfo: oauthRow, token: JwtToken };
}, },
async saveUser(c, userInfo) { async saveUser(c, userInfo) {
@@ -74,6 +74,18 @@ const settingService = {
setting.linuxdoCallbackUrl = c.env.linuxdo_callback_url; setting.linuxdoCallbackUrl = c.env.linuxdo_callback_url;
setting.linuxdoSwitch = linuxdoSwitch; setting.linuxdoSwitch = linuxdoSwitch;
setting.githubClientId = c.env.github_client_id;
setting.githubCallbackUrl = c.env.github_callback_url;
setting.githubSwitch = this.parseBoolean(c.env.github_switch);
setting.gitlabClientId = c.env.gitlab_client_id;
setting.gitlabCallbackUrl = c.env.gitlab_callback_url;
setting.gitlabSwitch = this.parseBoolean(c.env.gitlab_switch);
setting.googleClientId = c.env.google_client_id;
setting.googleCallbackUrl = c.env.google_callback_url;
setting.googleSwitch = this.parseBoolean(c.env.google_switch);
setting.emailPrefixFilter = setting.emailPrefixFilter.split(",").filter(Boolean); setting.emailPrefixFilter = setting.emailPrefixFilter.split(",").filter(Boolean);
c.set?.('setting', setting); c.set?.('setting', setting);
@@ -231,11 +243,26 @@ const settingService = {
linuxdoClientId: settingRow.linuxdoClientId, linuxdoClientId: settingRow.linuxdoClientId,
linuxdoCallbackUrl: settingRow.linuxdoCallbackUrl, linuxdoCallbackUrl: settingRow.linuxdoCallbackUrl,
linuxdoSwitch: settingRow.linuxdoSwitch, linuxdoSwitch: settingRow.linuxdoSwitch,
githubClientId: settingRow.githubClientId,
githubCallbackUrl: settingRow.githubCallbackUrl,
githubSwitch: settingRow.githubSwitch,
gitlabClientId: settingRow.gitlabClientId,
gitlabCallbackUrl: settingRow.gitlabCallbackUrl,
gitlabSwitch: settingRow.gitlabSwitch,
googleClientId: settingRow.googleClientId,
googleCallbackUrl: settingRow.googleCallbackUrl,
googleSwitch: settingRow.googleSwitch,
minEmailPrefix: settingRow.minEmailPrefix, minEmailPrefix: settingRow.minEmailPrefix,
projectLink: settingRow.projectLink projectLink: settingRow.projectLink
}; };
}, },
parseBoolean(value) {
if (typeof value === 'string' && value === 'true') return true;
if (value === true) return true;
return false;
},
}; };
export default settingService; export default settingService;