feat: improve OAuth2 support
This commit is contained in:
@@ -28,10 +28,6 @@ jobs:
|
|||||||
AI_MODEL: ${{ secrets.AI_MODEL || vars.AI_MODEL || '@cf/meta/llama-3.1-8b-instruct-fast' }}
|
AI_MODEL: ${{ secrets.AI_MODEL || vars.AI_MODEL || '@cf/meta/llama-3.1-8b-instruct-fast' }}
|
||||||
CLOUDFLARE_EMAIL: ${{ secrets.CF_EMAIL || vars.CF_EMAIL || false }}
|
CLOUDFLARE_EMAIL: ${{ secrets.CF_EMAIL || vars.CF_EMAIL || false }}
|
||||||
ANALYSIS_CACHE: ${{ secrets.ANALYSIS_CACHE || vars.ANALYSIS_CACHE || false }}
|
ANALYSIS_CACHE: ${{ secrets.ANALYSIS_CACHE || vars.ANALYSIS_CACHE || false }}
|
||||||
LINUXDO_SWITCH: ${{ secrets.LINUXDO_SWITCH || vars.LINUXDO_SWITCH }}
|
|
||||||
GITHUB_SWITCH: ${{ secrets.GITHUB_SWITCH || vars.GITHUB_SWITCH }}
|
|
||||||
GITLAB_SWITCH: ${{ secrets.GITLAB_SWITCH || vars.GITLAB_SWITCH }}
|
|
||||||
GOOGLE_SWITCH: ${{ secrets.GOOGLE_SWITCH || vars.GOOGLE_SWITCH }}
|
|
||||||
|
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
@@ -107,10 +103,6 @@ jobs:
|
|||||||
sed -i '/^project_link = /d' "$CONFIG_FILE"
|
sed -i '/^project_link = /d' "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$LINUXDO_CLIENT_ID" ] || [ -z "$LINUXDO_CLIENT_SECRET" ]; then
|
|
||||||
sed -i '/^linuxdo_client_id = /,/^linuxdo_switch = /d' "$CONFIG_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$CUSTOM_DOMAIN" ]; then
|
if [ -z "$CUSTOM_DOMAIN" ]; then
|
||||||
sed -i '/\[\[routes\]\]/,/^$/d' "$CONFIG_FILE"
|
sed -i '/\[\[routes\]\]/,/^$/d' "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
@@ -135,7 +127,6 @@ jobs:
|
|||||||
sed -i "s|\${LINUXDO_CLIENT_ID}|${LINUXDO_CLIENT_ID}|g" "$CONFIG_FILE"
|
sed -i "s|\${LINUXDO_CLIENT_ID}|${LINUXDO_CLIENT_ID}|g" "$CONFIG_FILE"
|
||||||
sed -i "s|\${LINUXDO_CLIENT_SECRET}|${LINUXDO_CLIENT_SECRET}|g" "$CONFIG_FILE"
|
sed -i "s|\${LINUXDO_CLIENT_SECRET}|${LINUXDO_CLIENT_SECRET}|g" "$CONFIG_FILE"
|
||||||
sed -i "s|\${LINUXDO_CALLBACK_URL}|${LINUXDO_CALLBACK_URL}|g" "$CONFIG_FILE"
|
sed -i "s|\${LINUXDO_CALLBACK_URL}|${LINUXDO_CALLBACK_URL}|g" "$CONFIG_FILE"
|
||||||
sed -i "s|\${LINUXDO_SWITCH}|${LINUXDO_SWITCH}|g" "$CONFIG_FILE"
|
|
||||||
|
|
||||||
echo "✅ Environment setup completed."
|
echo "✅ Environment setup completed."
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ With only one domain, you can create multiple different email addresses, similar
|
|||||||
|
|
||||||
- **🤖 CAPTCHA**: Integrated with Turnstile CAPTCHA to prevent automated registration.
|
- **🤖 CAPTCHA**: Integrated with Turnstile CAPTCHA to prevent automated registration.
|
||||||
|
|
||||||
- **🔑 OAuth2 Login**: Support LinuxDo / GitHub / GitLab / Google third-party login.
|
- **🔑 OAuth2 Login**: Support LinuxDo / GitHub / Google third-party login.
|
||||||
|
|
||||||
- **📜 More Features**: Under development...
|
- **📜 More Features**: Under development...
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
- **🤖 人机验证**:集成Turnstile人机验证,防止人机批量注册
|
- **🤖 人机验证**:集成Turnstile人机验证,防止人机批量注册
|
||||||
|
|
||||||
- **🔑 OAuth2 登录**:支持 LinuxDo / GitHub / GitLab / Google 第三方登录
|
- **🔑 OAuth2 登录**:支持 LinuxDo / GitHub / Google 第三方登录
|
||||||
|
|
||||||
- **📜 更多功能**:正在开发中...
|
- **📜 更多功能**:正在开发中...
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
| `ADMIN` | ✅ | 您的管理员邮箱地址(例如 `[email protected]`) |
|
| `ADMIN` | ✅ | 您的管理员邮箱地址(例如 `[email protected]`) |
|
||||||
| `JWT_SECRET` | ✅ | 用于生成和验证 JWT 的随机长字符串 |
|
| `JWT_SECRET` | ✅ | 用于生成和验证 JWT 的随机长字符串 |
|
||||||
| `PROJECT_LINK` | ❌ | (可选)是否在登录页显示 GitHub 项目链接(`true` / `false`) | |
|
| `PROJECT_LINK` | ❌ | (可选)是否在登录页显示 GitHub 项目链接(`true` / `false`) | |
|
||||||
| `LINUXDO_SWITCH` | ❌ | LinuxDo OAuth 启用开关(`true` / `false`) | |
|
|
||||||
| `GITHUB_SWITCH` | ❌ | GitHub OAuth 启用开关(`true` / `false`) | |
|
|
||||||
| `GITLAB_SWITCH` | ❌ | GitLab OAuth 启用开关(`true` / `false`) | |
|
|
||||||
| `GOOGLE_SWITCH` | ❌ | Google OAuth 启用开关(`true` / `false`) |
|
|
||||||
| `INIT_URL` | ❌ | (可选)部署后用于初始化数据库的 Worker URL(格式参考下述手动初始化) |
|
| `INIT_URL` | ❌ | (可选)部署后用于初始化数据库的 Worker URL(格式参考下述手动初始化) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -343,7 +343,6 @@ const en = {
|
|||||||
oauthSetting: 'OAuth',
|
oauthSetting: 'OAuth',
|
||||||
clientId: 'Client ID',
|
clientId: 'Client ID',
|
||||||
clientSecret: 'Client Secret',
|
clientSecret: 'Client Secret',
|
||||||
callbackBase: 'https://domain',
|
|
||||||
notOwner: 'Base email does not belong to you',
|
notOwner: 'Base email does not belong to you',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -343,7 +343,6 @@ const zh = {
|
|||||||
oauthSetting: 'OAuth',
|
oauthSetting: 'OAuth',
|
||||||
clientId: '客户端 ID',
|
clientId: '客户端 ID',
|
||||||
clientSecret: '客户端密钥',
|
clientSecret: '客户端密钥',
|
||||||
callbackBase: 'https://domain',
|
|
||||||
notOwner: '基础邮箱不属于您',
|
notOwner: '基础邮箱不属于您',
|
||||||
}
|
}
|
||||||
export default zh
|
export default zh
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
import http from '@/axios/index.js';
|
import http from '@/axios/index.js';
|
||||||
|
|
||||||
export function oauthLinuxDoLogin(code) {
|
export function oauthLinuxDoLogin(code, redirectUri) {
|
||||||
return http.post('/oauth/linuxDo/login',{code})
|
return http.post('/oauth/linuxDo/login',{code, redirectUri})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function oauthGithubLogin(code) {
|
export function oauthGithubLogin(code, redirectUri) {
|
||||||
return http.post('/oauth/github/login',{code})
|
return http.post('/oauth/github/login',{code, redirectUri})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function oauthGitlabLogin(code) {
|
export function oauthGoogleLogin(code, redirectUri) {
|
||||||
return http.post('/oauth/gitlab/login',{code})
|
return http.post('/oauth/google/login',{code, redirectUri})
|
||||||
}
|
|
||||||
|
|
||||||
export function oauthGoogleLogin(code) {
|
|
||||||
return http.post('/oauth/google/login',{code})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function oauthBindUser(form) {
|
export function oauthBindUser(form) {
|
||||||
|
|||||||
@@ -59,26 +59,6 @@ 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',
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ 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, oauthGithubLogin, oauthGitlabLogin, oauthGoogleLogin} from "@/request/ouath.js";
|
import {oauthBindUser, oauthLinuxDoLogin, oauthGithubLogin, oauthGoogleLogin} from "@/request/ouath.js";
|
||||||
|
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
const accountStore = useAccountStore();
|
const accountStore = useAccountStore();
|
||||||
@@ -181,19 +181,22 @@ const oauthLoading = ref(false);
|
|||||||
const showBindForm = ref(false);
|
const showBindForm = ref(false);
|
||||||
const show = ref('login')
|
const show = ref('login')
|
||||||
|
|
||||||
|
const oauthKeys = ['linuxdo', 'github', 'google']
|
||||||
|
|
||||||
const oauthProvider = computed(() => {
|
const oauthProvider = computed(() => {
|
||||||
const match = route.path.match(/^\/login\/(.+)/)
|
const fromState = route.query.state
|
||||||
return match ? match[1] : null
|
if (oauthKeys.includes(fromState)) return fromState
|
||||||
|
const fromStore = sessionStorage.getItem('oauthProvider')
|
||||||
|
return oauthKeys.includes(fromStore) ? fromStore : null
|
||||||
})
|
})
|
||||||
|
|
||||||
const oauthProviders = computed(() => {
|
const oauthProviders = computed(() => {
|
||||||
const allProviders = [
|
const allProviders = [
|
||||||
{ key: 'linuxdo', label: 'LinuxDo', icon: '/image/linuxdo.webp', iconType: 'image' },
|
{ key: 'linuxdo', label: 'LinuxDo', icon: '/image/linuxdo.webp', iconType: 'image' },
|
||||||
{ key: 'github', label: 'GitHub', icon: 'mingcute:github-fill', iconType: 'iconify' },
|
{ 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' },
|
{ key: 'google', label: 'Google', icon: 'mingcute:google-fill', iconType: 'iconify' },
|
||||||
]
|
]
|
||||||
return allProviders.filter(p => settingStore.settings[p.key + 'Switch'])
|
return allProviders.filter(p => settingStore.settings[p.key + 'Switch'] === 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
const bindForm = reactive({
|
const bindForm = reactive({
|
||||||
@@ -284,12 +287,12 @@ const getEmailName = (email) => {
|
|||||||
|
|
||||||
function oauthLogin(provider) {
|
function oauthLogin(provider) {
|
||||||
const clientId = settingStore.settings[provider + 'ClientId']
|
const clientId = settingStore.settings[provider + 'ClientId']
|
||||||
const redirectUri = encodeURIComponent(window.location.origin + '/login/' + provider)
|
const redirectUri = encodeURIComponent(window.location.origin + '/login')
|
||||||
|
sessionStorage.setItem('oauthProvider', provider)
|
||||||
const authorizeUrls = {
|
const authorizeUrls = {
|
||||||
linuxdo: `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&state=${provider}`,
|
||||||
github: `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=user:email`,
|
github: `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=user:email&state=${provider}`,
|
||||||
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&state=${provider}`,
|
||||||
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]
|
window.location.href = authorizeUrls[provider]
|
||||||
}
|
}
|
||||||
@@ -297,7 +300,6 @@ function oauthLogin(provider) {
|
|||||||
const loginFns = {
|
const loginFns = {
|
||||||
linuxdo: oauthLinuxDoLogin,
|
linuxdo: oauthLinuxDoLogin,
|
||||||
github: oauthGithubLogin,
|
github: oauthGithubLogin,
|
||||||
gitlab: oauthGitlabLogin,
|
|
||||||
google: oauthGoogleLogin,
|
google: oauthGoogleLogin,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,15 +307,16 @@ oauthGetUser();
|
|||||||
|
|
||||||
async function 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')
|
||||||
|
if (!code || !oauthProvider.value) return
|
||||||
|
|
||||||
if (code) {
|
const provider = oauthProvider.value
|
||||||
|
|
||||||
oauthLoading.value = true
|
oauthLoading.value = true
|
||||||
loginFns[oauthProvider.value](code).then(data => {
|
sessionStorage.removeItem('oauthProvider')
|
||||||
|
window.history.replaceState({}, '', window.location.origin + window.location.pathname)
|
||||||
|
|
||||||
|
loginFns[provider](code, window.location.origin + '/login').then(data => {
|
||||||
|
|
||||||
bindForm.oauthUserId = data.userInfo.oauthUserId;
|
bindForm.oauthUserId = data.userInfo.oauthUserId;
|
||||||
|
|
||||||
@@ -333,10 +336,6 @@ async function oauthGetUser() {
|
|||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
oauthLoading.value = false
|
oauthLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const cleanUrl = window.location.origin + window.location.pathname
|
|
||||||
window.history.replaceState({}, '', cleanUrl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bind() {
|
function bind() {
|
||||||
|
|||||||
@@ -353,22 +353,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OAuth Login Card -->
|
|
||||||
<div class="settings-card">
|
|
||||||
<div class="card-title">{{ $t('oauthLogin') }}</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="setting-item" v-for="p in oauthPlatforms" :key="p.key">
|
|
||||||
<div><span>{{ p.label }}</span></div>
|
|
||||||
<div class="forward">
|
|
||||||
<span>{{ setting[p.key + 'Switch'] ? $t('enabled') : $t('disabled') }}</span>
|
|
||||||
<el-button class="opt-button" size="small" type="primary" :disabled="!setting[p.key + 'Switch']" @click="openOauthSetting(p)">
|
|
||||||
<Icon icon="fluent:settings-48-regular" width="18" height="18"/>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<div class="card-title">{{ $t('noticeTitle') }}</div>
|
<div class="card-title">{{ $t('noticeTitle') }}</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -413,6 +397,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- OAuth Login Card -->
|
||||||
|
<div class="settings-card">
|
||||||
|
<div class="card-title">{{ $t('oauthLogin') }}</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="setting-item" v-for="p in oauthPlatforms" :key="p.key">
|
||||||
|
<div><span>{{ p.label }}</span></div>
|
||||||
|
<div class="forward">
|
||||||
|
<span>{{ setting[p.key + 'Switch'] === 0 ? $t('enabled') : $t('disabled') }}</span>
|
||||||
|
<el-button class="opt-button" size="small" type="primary" @click="openOauthSetting(p)">
|
||||||
|
<Icon icon="fluent:settings-48-regular" width="18" height="18"/>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-card about">
|
<div class="settings-card about">
|
||||||
<div class="card-title">{{ $t('about') }}</div>
|
<div class="card-title">{{ $t('about') }}</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -504,17 +504,18 @@
|
|||||||
</form>
|
</form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog v-model="oauthSettingShow" :title="$t('oauthSetting') + ' - ' + oauthForm.label" width="340"
|
<el-dialog v-model="oauthSettingShow" :title="$t('oauthSetting') + ' - ' + oauthForm.label" width="340"
|
||||||
@closed="oauthForm.clientId = ''; oauthForm.clientSecret = ''; oauthForm.callbackBase = ''">
|
@closed="oauthForm.clientId = ''; oauthForm.clientSecret = ''; oauthForm.switch = 1">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<el-input type="text" :placeholder="$t('clientId')" v-model="oauthForm.clientId"/>
|
<el-input type="text" :placeholder="$t('clientId')" v-model="oauthForm.clientId"/>
|
||||||
<el-input type="text" style="margin-top: 15px" :placeholder="$t('clientSecret')" v-model="oauthForm.clientSecret"/>
|
<el-input type="text" style="margin-top: 15px" :placeholder="$t('clientSecret')" v-model="oauthForm.clientSecret"/>
|
||||||
<el-input type="text" style="margin-top: 15px" :placeholder="$t('callbackBase')" v-model="oauthForm.callbackBase">
|
|
||||||
<template #append>/login/{{ oauthForm.key }}</template>
|
|
||||||
</el-input>
|
|
||||||
<div style="display: flex; justify-content: flex-end; margin-top: 15px;">
|
|
||||||
<el-button type="primary" @click="saveOauth">{{ $t('save') }}</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-switch v-model="oauthForm.switch" :active-value="0" :inactive-value="1" :active-text="$t('enable')"
|
||||||
|
:inactive-text="$t('disable')"/>
|
||||||
|
<el-button type="primary" :loading="settingLoading" @click="saveOauth">{{ $t('save') }}</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showSetBackground"
|
v-model="showSetBackground"
|
||||||
@@ -919,7 +920,6 @@ const turnstileForm = reactive({
|
|||||||
const oauthPlatforms = [
|
const oauthPlatforms = [
|
||||||
{ key: 'linuxdo', label: 'LinuxDo' },
|
{ key: 'linuxdo', label: 'LinuxDo' },
|
||||||
{ key: 'github', label: 'GitHub' },
|
{ key: 'github', label: 'GitHub' },
|
||||||
{ key: 'gitlab', label: 'GitLab' },
|
|
||||||
{ key: 'google', label: 'Google' },
|
{ key: 'google', label: 'Google' },
|
||||||
]
|
]
|
||||||
const oauthSettingShow = ref(false)
|
const oauthSettingShow = ref(false)
|
||||||
@@ -928,7 +928,7 @@ const oauthForm = reactive({
|
|||||||
label: '',
|
label: '',
|
||||||
clientId: '',
|
clientId: '',
|
||||||
clientSecret: '',
|
clientSecret: '',
|
||||||
callbackBase: '',
|
switch: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const s3 = reactive({
|
const s3 = reactive({
|
||||||
@@ -1393,9 +1393,7 @@ function openOauthSetting(p) {
|
|||||||
oauthForm.label = p.label
|
oauthForm.label = p.label
|
||||||
oauthForm.clientId = setting.value[p.key + 'ClientId'] || ''
|
oauthForm.clientId = setting.value[p.key + 'ClientId'] || ''
|
||||||
oauthForm.clientSecret = setting.value[p.key + 'ClientSecret'] || ''
|
oauthForm.clientSecret = setting.value[p.key + 'ClientSecret'] || ''
|
||||||
const suffix = '/login/' + p.key
|
oauthForm.switch = setting.value[p.key + 'Switch'] ?? 1
|
||||||
const fullUrl = setting.value[p.key + 'CallbackUrl'] || ''
|
|
||||||
oauthForm.callbackBase = fullUrl.endsWith(suffix) ? fullUrl.slice(0, -suffix.length) : fullUrl
|
|
||||||
oauthSettingShow.value = true
|
oauthSettingShow.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1403,9 +1401,8 @@ function saveOauth() {
|
|||||||
const form = {}
|
const form = {}
|
||||||
form[oauthForm.key + 'ClientId'] = oauthForm.clientId
|
form[oauthForm.key + 'ClientId'] = oauthForm.clientId
|
||||||
form[oauthForm.key + 'ClientSecret'] = oauthForm.clientSecret
|
form[oauthForm.key + 'ClientSecret'] = oauthForm.clientSecret
|
||||||
form[oauthForm.key + 'CallbackUrl'] = oauthForm.callbackBase + '/login/' + oauthForm.key
|
form[oauthForm.key + 'Switch'] = oauthForm.switch
|
||||||
editSetting(form)
|
editSetting(form)
|
||||||
oauthSettingShow.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveTurnstileKey() {
|
function saveTurnstileKey() {
|
||||||
@@ -1571,6 +1568,7 @@ function editSetting(settingForm, refreshStatus = true) {
|
|||||||
addS3Show.value = false
|
addS3Show.value = false
|
||||||
emailPrefixShow.value = false
|
emailPrefixShow.value = false
|
||||||
aiCodeFilterShow.value = false
|
aiCodeFilterShow.value = false
|
||||||
|
oauthSettingShow.value = false
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
loginOpacity.value = setting.value.loginOpacity
|
loginOpacity.value = setting.value.loginOpacity
|
||||||
setting.value = {...setting.value, ...JSON.parse(backup)}
|
setting.value = {...setting.value, ...JSON.parse(backup)}
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ app.post('/oauth/github/login', async (c) => {
|
|||||||
return c.json(result.ok(loginInfo))
|
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) => {
|
app.post('/oauth/google/login', async (c) => {
|
||||||
const loginInfo = await oauthService.googleLogin(c, await c.req.json());
|
const loginInfo = await oauthService.googleLogin(c, await c.req.json());
|
||||||
return c.json(result.ok(loginInfo))
|
return c.json(result.ok(loginInfo))
|
||||||
|
|||||||
@@ -55,15 +55,12 @@ export const setting = sqliteTable('setting', {
|
|||||||
aiCodeFilter: text('ai_code_filter').default('').notNull(),
|
aiCodeFilter: text('ai_code_filter').default('').notNull(),
|
||||||
linuxdoClientId: text('linuxdo_client_id').default('').notNull(),
|
linuxdoClientId: text('linuxdo_client_id').default('').notNull(),
|
||||||
linuxdoClientSecret: text('linuxdo_client_secret').default('').notNull(),
|
linuxdoClientSecret: text('linuxdo_client_secret').default('').notNull(),
|
||||||
linuxdoCallbackUrl: text('linuxdo_callback_url').default('').notNull(),
|
linuxdoSwitch: integer('linuxdo_switch').default(1).notNull(),
|
||||||
githubClientId: text('github_client_id').default('').notNull(),
|
githubClientId: text('github_client_id').default('').notNull(),
|
||||||
githubClientSecret: text('github_client_secret').default('').notNull(),
|
githubClientSecret: text('github_client_secret').default('').notNull(),
|
||||||
githubCallbackUrl: text('github_callback_url').default('').notNull(),
|
githubSwitch: integer('github_switch').default(1).notNull(),
|
||||||
gitlabClientId: text('gitlab_client_id').default('').notNull(),
|
|
||||||
gitlabClientSecret: text('gitlab_client_secret').default('').notNull(),
|
|
||||||
gitlabCallbackUrl: text('gitlab_callback_url').default('').notNull(),
|
|
||||||
googleClientId: text('google_client_id').default('').notNull(),
|
googleClientId: text('google_client_id').default('').notNull(),
|
||||||
googleClientSecret: text('google_client_secret').default('').notNull(),
|
googleClientSecret: text('google_client_secret').default('').notNull(),
|
||||||
googleCallbackUrl: text('google_callback_url').default('').notNull()
|
googleSwitch: integer('google_switch').default(1).notNull()
|
||||||
});
|
});
|
||||||
export default setting
|
export default setting
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const en = {
|
|||||||
noOsDomainSendAtt: 'Cannot send attachments: object storage domain not configured',
|
noOsDomainSendAtt: 'Cannot send attachments: object storage domain not configured',
|
||||||
noOsSendAtt: 'Cannot send attachments: object storage not configured',
|
noOsSendAtt: 'Cannot send attachments: object storage not configured',
|
||||||
disabledSend: 'Email sending feature is disabled',
|
disabledSend: 'Email sending feature is disabled',
|
||||||
|
oauthDisabled: 'This OAuth login is not enabled',
|
||||||
daySendLimit: 'Daily send limit reached',
|
daySendLimit: 'Daily send limit reached',
|
||||||
totalSendLimit: 'Total send limit reached',
|
totalSendLimit: 'Total send limit reached',
|
||||||
daySendLack: 'Not enough remaining sends today',
|
daySendLack: 'Not enough remaining sends today',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const zh = {
|
|||||||
noOsDomainSendAtt: '对象存储域名未配置不能发送附件',
|
noOsDomainSendAtt: '对象存储域名未配置不能发送附件',
|
||||||
noOsSendAtt: '对象存储未配置不能发送附件',
|
noOsSendAtt: '对象存储未配置不能发送附件',
|
||||||
disabledSend: '邮件发送功能已停用',
|
disabledSend: '邮件发送功能已停用',
|
||||||
|
oauthDisabled: '该 OAuth 登录未启用',
|
||||||
daySendLimit: '发送次数已到达每日限制',
|
daySendLimit: '发送次数已到达每日限制',
|
||||||
totalSendLimit: '发送次数已到达限制',
|
totalSendLimit: '发送次数已到达限制',
|
||||||
daySendLack: '当日剩余发送次数不足',
|
daySendLack: '当日剩余发送次数不足',
|
||||||
|
|||||||
@@ -31,16 +31,29 @@ const dbInit = {
|
|||||||
await this.v3_0DB(c);
|
await this.v3_0DB(c);
|
||||||
await this.v3_1DB(c);
|
await this.v3_1DB(c);
|
||||||
await this.v3_2DB(c);
|
await this.v3_2DB(c);
|
||||||
|
await this.v3_3DB(c);
|
||||||
await settingService.refresh(c);
|
await settingService.refresh(c);
|
||||||
return c.text('success');
|
return c.text('success');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async v3_3DB(c) {
|
||||||
|
const oauthSwitchFields = [
|
||||||
|
'linuxdo_switch', 'github_switch', 'google_switch',
|
||||||
|
];
|
||||||
|
for (const field of oauthSwitchFields) {
|
||||||
|
try {
|
||||||
|
await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN ${field} INTEGER NOT NULL DEFAULT 1;`).run();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`跳过字段:${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async v3_2DB(c) {
|
async v3_2DB(c) {
|
||||||
const oauthFields = [
|
const oauthFields = [
|
||||||
'linuxdo_client_id', 'linuxdo_client_secret', 'linuxdo_callback_url',
|
'linuxdo_client_id', 'linuxdo_client_secret',
|
||||||
'github_client_id', 'github_client_secret', 'github_callback_url',
|
'github_client_id', 'github_client_secret',
|
||||||
'gitlab_client_id', 'gitlab_client_secret', 'gitlab_callback_url',
|
'google_client_id', 'google_client_secret',
|
||||||
'google_client_id', 'google_client_secret', 'google_callback_url',
|
|
||||||
];
|
];
|
||||||
for (const field of oauthFields) {
|
for (const field of oauthFields) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import userService from "./user-service";
|
|||||||
import loginService from "./login-service";
|
import loginService from "./login-service";
|
||||||
import cryptoUtils from "../utils/crypto-utils";
|
import cryptoUtils from "../utils/crypto-utils";
|
||||||
import settingService from "./setting-service";
|
import settingService from "./setting-service";
|
||||||
|
import {t} from '../i18n/i18n';
|
||||||
|
|
||||||
const oauthService = {
|
const oauthService = {
|
||||||
|
|
||||||
@@ -33,15 +34,16 @@ const oauthService = {
|
|||||||
|
|
||||||
async linuxDoLogin(c, params) {
|
async linuxDoLogin(c, params) {
|
||||||
|
|
||||||
const { code } = params;
|
const { code, redirectUri } = params;
|
||||||
|
|
||||||
const setting = await settingService.query(c);
|
const setting = await settingService.query(c);
|
||||||
|
this.assertEnabled(setting, 'linuxdoSwitch');
|
||||||
|
|
||||||
const reqParams = new URLSearchParams()
|
const reqParams = new URLSearchParams()
|
||||||
reqParams.append('client_id', setting.linuxdoClientId)
|
reqParams.append('client_id', setting.linuxdoClientId)
|
||||||
reqParams.append('client_secret', setting.linuxdoClientSecret)
|
reqParams.append('client_secret', setting.linuxdoClientSecret)
|
||||||
reqParams.append('code', code)
|
reqParams.append('code', code)
|
||||||
reqParams.append('redirect_uri', setting.linuxdoCallbackUrl)
|
reqParams.append('redirect_uri', redirectUri)
|
||||||
reqParams.append('grant_type', 'authorization_code')
|
reqParams.append('grant_type', 'authorization_code')
|
||||||
|
|
||||||
const tokenRes = await fetch("https://connect.linux.do/oauth2/token", {
|
const tokenRes = await fetch("https://connect.linux.do/oauth2/token", {
|
||||||
@@ -80,9 +82,10 @@ const oauthService = {
|
|||||||
|
|
||||||
async githubLogin(c, params) {
|
async githubLogin(c, params) {
|
||||||
|
|
||||||
const { code } = params;
|
const { code, redirectUri } = params;
|
||||||
|
|
||||||
const setting = await settingService.query(c);
|
const setting = await settingService.query(c);
|
||||||
|
this.assertEnabled(setting, 'githubSwitch');
|
||||||
|
|
||||||
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
|
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -94,7 +97,7 @@ const oauthService = {
|
|||||||
client_id: setting.githubClientId,
|
client_id: setting.githubClientId,
|
||||||
client_secret: setting.githubClientSecret,
|
client_secret: setting.githubClientSecret,
|
||||||
code: code,
|
code: code,
|
||||||
redirect_uri: setting.githubCallbackUrl
|
redirect_uri: redirectUri
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,63 +132,18 @@ const oauthService = {
|
|||||||
return await this.saveAndLogin(c, userInfo);
|
return await this.saveAndLogin(c, userInfo);
|
||||||
},
|
},
|
||||||
|
|
||||||
async gitlabLogin(c, params) {
|
|
||||||
|
|
||||||
const { code } = params;
|
|
||||||
|
|
||||||
const setting = await settingService.query(c);
|
|
||||||
|
|
||||||
const reqParams = new URLSearchParams()
|
|
||||||
reqParams.append('client_id', setting.gitlabClientId)
|
|
||||||
reqParams.append('client_secret', setting.gitlabClientSecret)
|
|
||||||
reqParams.append('code', code)
|
|
||||||
reqParams.append('redirect_uri', setting.gitlabCallbackUrl)
|
|
||||||
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) {
|
async googleLogin(c, params) {
|
||||||
|
|
||||||
const { code } = params;
|
const { code, redirectUri } = params;
|
||||||
|
|
||||||
const setting = await settingService.query(c);
|
const setting = await settingService.query(c);
|
||||||
|
this.assertEnabled(setting, 'googleSwitch');
|
||||||
|
|
||||||
const reqParams = new URLSearchParams()
|
const reqParams = new URLSearchParams()
|
||||||
reqParams.append('client_id', setting.googleClientId)
|
reqParams.append('client_id', setting.googleClientId)
|
||||||
reqParams.append('client_secret', setting.googleClientSecret)
|
reqParams.append('client_secret', setting.googleClientSecret)
|
||||||
reqParams.append('code', code)
|
reqParams.append('code', code)
|
||||||
reqParams.append('redirect_uri', setting.googleCallbackUrl)
|
reqParams.append('redirect_uri', redirectUri)
|
||||||
reqParams.append('grant_type', 'authorization_code')
|
reqParams.append('grant_type', 'authorization_code')
|
||||||
|
|
||||||
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
||||||
@@ -246,6 +204,12 @@ const oauthService = {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
assertEnabled(setting, switchKey) {
|
||||||
|
if (setting[switchKey] !== 0) {
|
||||||
|
throw new BizError(t('oauthDisabled'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async getById(c, oauthUserId) {
|
async getById(c, oauthUserId) {
|
||||||
return await orm(c).select().from(oauth).where(eq(oauth.oauthUserId, oauthUserId)).get();
|
return await orm(c).select().from(oauth).where(eq(oauth.oauthUserId, oauthUserId)).get();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,11 +59,6 @@ const settingService = {
|
|||||||
|
|
||||||
setting.projectLink = projectLink;
|
setting.projectLink = projectLink;
|
||||||
|
|
||||||
setting.linuxdoSwitch = this.parseBoolean(c.env.linuxdo_switch);
|
|
||||||
setting.githubSwitch = this.parseBoolean(c.env.github_switch);
|
|
||||||
setting.gitlabSwitch = this.parseBoolean(c.env.gitlab_switch);
|
|
||||||
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);
|
||||||
@@ -131,15 +126,6 @@ const settingService = {
|
|||||||
|
|
||||||
params.resendTokens = JSON.stringify(resendTokens);
|
params.resendTokens = JSON.stringify(resendTokens);
|
||||||
|
|
||||||
const callbackPlatforms = ['linuxdo', 'github', 'gitlab', 'google'];
|
|
||||||
for (const platform of callbackPlatforms) {
|
|
||||||
const key = platform + 'CallbackUrl';
|
|
||||||
const expectedSuffix = '/login/' + platform;
|
|
||||||
if (params[key] && !params[key].endsWith(expectedSuffix)) {
|
|
||||||
throw new BizError(`Invalid callback URL for ${platform}: must end with ${expectedSuffix}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await orm(c).update(setting).set({ ...params }).returning().get();
|
await orm(c).update(setting).set({ ...params }).returning().get();
|
||||||
await this.refresh(c);
|
await this.refresh(c);
|
||||||
},
|
},
|
||||||
@@ -229,28 +215,16 @@ const settingService = {
|
|||||||
notice: settingRow.notice,
|
notice: settingRow.notice,
|
||||||
loginDomain: settingRow.loginDomain,
|
loginDomain: settingRow.loginDomain,
|
||||||
linuxdoClientId: settingRow.linuxdoClientId,
|
linuxdoClientId: settingRow.linuxdoClientId,
|
||||||
linuxdoCallbackUrl: settingRow.linuxdoCallbackUrl,
|
|
||||||
linuxdoSwitch: settingRow.linuxdoSwitch,
|
linuxdoSwitch: settingRow.linuxdoSwitch,
|
||||||
githubClientId: settingRow.githubClientId,
|
githubClientId: settingRow.githubClientId,
|
||||||
githubCallbackUrl: settingRow.githubCallbackUrl,
|
|
||||||
githubSwitch: settingRow.githubSwitch,
|
githubSwitch: settingRow.githubSwitch,
|
||||||
gitlabClientId: settingRow.gitlabClientId,
|
|
||||||
gitlabCallbackUrl: settingRow.gitlabCallbackUrl,
|
|
||||||
gitlabSwitch: settingRow.gitlabSwitch,
|
|
||||||
googleClientId: settingRow.googleClientId,
|
googleClientId: settingRow.googleClientId,
|
||||||
googleCallbackUrl: settingRow.googleCallbackUrl,
|
|
||||||
googleSwitch: settingRow.googleSwitch,
|
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;
|
||||||
|
|||||||
@@ -44,10 +44,5 @@ admin = "${ADMIN}"
|
|||||||
jwt_secret = "${JWT_SECRET}"
|
jwt_secret = "${JWT_SECRET}"
|
||||||
project_link = "${PROJECT_LINK}"
|
project_link = "${PROJECT_LINK}"
|
||||||
|
|
||||||
linuxdo_switch = "${LINUXDO_SWITCH}"
|
|
||||||
github_switch = "${GITHUB_SWITCH}"
|
|
||||||
gitlab_switch = "${GITLAB_SWITCH}"
|
|
||||||
google_switch = "${GOOGLE_SWITCH}"
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
command = "pnpm --prefix ../mail-vue install && pnpm --prefix ../mail-vue run build"
|
command = "pnpm --prefix ../mail-vue install && pnpm --prefix ../mail-vue run build"
|
||||||
|
|||||||
@@ -42,10 +42,6 @@ crons = ["0 16 * * *"] #每天晚上12点执行每日任务,刷新分析缓存
|
|||||||
#domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"]
|
#domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"]
|
||||||
#admin = "" #管理员的邮箱 示例: [email protected]
|
#admin = "" #管理员的邮箱 示例: [email protected]
|
||||||
#jwt_secret = "" #jwt令牌的密钥,随便填一串字符串
|
#jwt_secret = "" #jwt令牌的密钥,随便填一串字符串
|
||||||
#linuxdo_switch = "" #LinuxDo OAuth 开关
|
|
||||||
#github_switch = "" #GitHub OAuth 开关
|
|
||||||
#gitlab_switch = "" #GitLab OAuth 开关
|
|
||||||
#google_switch = "" #Google OAuth 开关
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
command = "pnpm --prefix ../mail-vue install && pnpm --prefix ../mail-vue run build"
|
command = "pnpm --prefix ../mail-vue install && pnpm --prefix ../mail-vue run build"
|
||||||
|
|||||||
Reference in New Issue
Block a user