feat: 登录

This commit is contained in:
wxg0103 2024-07-11 19:51:28 +08:00
parent 8994ac4b20
commit f1672cfb66
10 changed files with 315 additions and 167 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-07-11 19:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_user_create_time_user_update_time'),
]
operations = [
migrations.AddField(
model_name='user',
name='source',
field=models.CharField(default='LOCAL', max_length=10, verbose_name='来源'),
),
]

View File

@ -0,0 +1,39 @@
import {Result} from '@/request/Result'
import {get, post, del, put} from '@/request/index'
import type {pageRequest} from '@/api/type/common'
import {type Ref} from 'vue'
const prefix = '/auth'
/**
*
*/
const getAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (auth_type, loading) => {
return get(`${prefix}/${auth_type}/info`, undefined, loading)
}
/**
*
*/
const postAuthSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading
) => {
return post(`${prefix}/connection`, data, undefined, loading)
}
/**
*
*/
const putAuthSetting: (auth_type: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
auth_type,
data,
loading
) => {
return put(`${prefix}/${auth_type}/info`, data, undefined, loading)
}
export default {
getAuthSetting,
postAuthSetting,
putAuthSetting
}

View File

@ -1,5 +1,5 @@
import { Result } from '@/request/Result' import {Result} from '@/request/Result'
import { get, post } from '@/request/index' import {get, post} from '@/request/index'
import type { import type {
LoginRequest, LoginRequest,
RegisterRequest, RegisterRequest,
@ -8,18 +8,23 @@ import type {
User, User,
ResetCurrentUserPasswordRequest ResetCurrentUserPasswordRequest
} from '@/api/type/user' } from '@/api/type/user'
import type { Ref } from 'vue' import type {Ref} from 'vue'
/** /**
* *
* @param auth_type
* @param request * @param request
* @param loading * @param loading
* @returns * @returns
*/ */
const login: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<string>> = ( const login: (auth_type: string, request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<string>> = (
auth_type,
request, request,
loading loading
) => { ) => {
if (auth_type !== '') {
return post(`/${auth_type}/login`, request, undefined, loading)
}
return post('/user/login', request, undefined, loading) return post('/user/login', request, undefined, loading)
} }
/** /**
@ -68,7 +73,7 @@ const sendEmit: (
type: 'register' | 'reset_password', type: 'register' | 'reset_password',
loading?: Ref<boolean> loading?: Ref<boolean>
) => Promise<Result<boolean>> = (email, type, loading) => { ) => Promise<Result<boolean>> = (email, type, loading) => {
return post('/user/send_email', { email, type }, undefined, loading) return post('/user/send_email', {email, type}, undefined, loading)
} }
/** /**
* *
@ -121,7 +126,7 @@ const getUserList: (email_or_username: string, loading?: Ref<boolean>) => Promis
email_or_username, email_or_username,
loading loading
) => { ) => {
return get('/user/list', { email_or_username }, loading) return get('/user/list', {email_or_username}, loading)
} }
/** /**
@ -143,6 +148,12 @@ const getValid: (
) => Promise<Result<any>> = (valid_type, valid_count, loading) => { ) => Promise<Result<any>> = (valid_type, valid_count, loading) => {
return get(`/valid/${valid_type}/${valid_count}`, undefined, loading) return get(`/valid/${valid_type}/${valid_count}`, undefined, loading)
} }
/**
*
*/
const getAuthType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
return get('auth/types', undefined, loading)
}
export default { export default {
login, login,
@ -156,5 +167,6 @@ export default {
logout, logout,
getUserList, getUserList,
getProfile, getProfile,
getValid getValid,
getAuthType
} }

View File

@ -2,11 +2,11 @@
<el-dropdown trigger="click" type="primary"> <el-dropdown trigger="click" type="primary">
<div class="flex-center cursor"> <div class="flex-center cursor">
<AppAvatar> <AppAvatar>
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" /> <img src="@/assets/user-icon.svg" style="width: 54%" alt=""/>
</AppAvatar> </AppAvatar>
<span class="ml-8">{{ user.userInfo?.username }}</span> <span class="ml-8">{{ user.userInfo?.username }}</span>
<el-icon class="el-icon--right"> <el-icon class="el-icon--right">
<CaretBottom /> <CaretBottom/>
</el-icon> </el-icon>
</div> </div>
@ -23,7 +23,7 @@
<el-dropdown-item class="border-t p-8" @click="openResetPassword"> <el-dropdown-item class="border-t p-8" @click="openResetPassword">
{{ $t('layout.topbar.avatar.resetPassword') }} {{ $t('layout.topbar.avatar.resetPassword') }}
</el-dropdown-item> </el-dropdown-item>
<div v-hasPermission="new ComplexPermission(['ADMIN'], ['x-pack'], 'AND')"> <div v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')">
<el-dropdown-item class="border-t p-8" @click="openAPIKeyDialog"> <el-dropdown-item class="border-t p-8" @click="openAPIKeyDialog">
{{ $t('layout.topbar.avatar.apiKey') }} {{ $t('layout.topbar.avatar.apiKey') }}
</el-dropdown-item> </el-dropdown-item>
@ -39,19 +39,20 @@
</el-dropdown> </el-dropdown>
<ResetPassword ref="resetPasswordRef"></ResetPassword> <ResetPassword ref="resetPasswordRef"></ResetPassword>
<AboutDialog ref="AboutDialogRef"></AboutDialog> <AboutDialog ref="AboutDialogRef"></AboutDialog>
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef" /> <APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef"/>
<UserPwdDialog ref="UserPwdDialogRef" /> <UserPwdDialog ref="UserPwdDialogRef"/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import {ref, onMounted} from 'vue'
import useStore from '@/stores' import useStore from '@/stores'
import { useRouter } from 'vue-router' import {useRouter} from 'vue-router'
import ResetPassword from './ResetPassword.vue' import ResetPassword from './ResetPassword.vue'
import AboutDialog from './AboutDialog.vue' import AboutDialog from './AboutDialog.vue'
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue' import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
import APIKeyDialog from './APIKeyDialog.vue' import APIKeyDialog from './APIKeyDialog.vue'
import { ComplexPermission } from '@/utils/permission/type' import {ComplexPermission} from '@/utils/permission/type'
const { user } = useStore()
const {user} = useStore()
const router = useRouter() const router = useRouter()
const UserPwdDialogRef = ref() const UserPwdDialogRef = ref()
@ -62,6 +63,7 @@ const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
const openAbout = () => { const openAbout = () => {
AboutDialogRef.value?.open() AboutDialogRef.value?.open()
} }
function openAPIKeyDialog() { function openAPIKeyDialog() {
APIKeyDialogRef.value.open() APIKeyDialogRef.value.open()
} }
@ -72,7 +74,7 @@ const openResetPassword = () => {
const logout = () => { const logout = () => {
user.logout().then(() => { user.logout().then(() => {
router.push({ name: 'login' }) router.push({name: 'login'})
}) })
} }
@ -85,9 +87,11 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.avatar-dropdown { .avatar-dropdown {
min-width: 210px; min-width: 210px;
.userInfo { .userInfo {
padding: 12px 11px; padding: 12px 11px;
} }
:deep(.el-dropdown-menu__item) { :deep(.el-dropdown-menu__item) {
padding: 12px 11px; padding: 12px 11px;
} }

View File

@ -9,4 +9,28 @@ export default {
views, views,
components, components,
en, en,
login: {
authentication: 'Login Authentication',
ldap: {
title: 'LDAP Settings',
address: 'LDAP Address',
serverPlaceholder: 'Please enter LDAP address',
bindDN: 'Bind DN',
bindDNPlaceholder: 'Please enter Bind DN',
password: 'Password',
passwordPlaceholder: 'Please enter password',
ou: 'User OU',
ouPlaceholder: 'Please enter User OU',
ldap_filter: 'User Filter',
ldap_filterPlaceholder: 'Please enter User Filter',
ldap_mapping: 'LDAP Attribute Mapping',
ldap_mappingPlaceholder: 'Please enter LDAP Attribute Mapping',
test: 'Test Connection',
enableAuthentication: 'Enable LDAP Authentication',
save: 'Save',
testConnectionSuccess: 'Test Connection Success',
testConnectionFailed: 'Test Connection Failed',
saveSuccess: 'Save Success',
}
}
}; };

View File

@ -9,4 +9,28 @@ export default {
views, views,
components, components,
zhCn, zhCn,
login: {
authentication: '登录认证',
ldap: {
title: 'LDAP设置',
address: 'LDAP地址',
serverPlaceholder: '请输入LDAP地址',
bindDN: '绑定DN',
bindDNPlaceholder: '请输入绑定DN',
password: '密码',
passwordPlaceholder: '请输入密码',
ou: '用户OU',
ouPlaceholder: '请输入用户OU',
ldap_filter: '用户过滤器',
ldap_filterPlaceholder: '请输入用户过滤器',
ldap_mapping: 'LDAP属性映射',
ldap_mappingPlaceholder: '请输入LDAP属性映射',
test: '测试连接',
enableAuthentication: '启用LDAP认证',
save: '保存',
testConnectionSuccess: '测试连接成功',
testConnectionFailed: '测试连接失败',
saveSuccess: '保存成功',
}
}
}; };

View File

@ -85,8 +85,8 @@ const useUserStore = defineStore({
}) })
}, },
async login(username: string, password: string) { async login(auth_type: string, username: string, password: string) {
return UserApi.login({ username, password }).then((ok) => { return UserApi.login(auth_type, { username, password }).then((ok) => {
this.token = ok.data this.token = ok.data
localStorage.setItem('token', ok.data) localStorage.setItem('token', ok.data)
return this.profile() return this.profile()
@ -98,6 +98,11 @@ const useUserStore = defineStore({
localStorage.removeItem('token') localStorage.removeItem('token')
return true return true
}) })
},
async getAuthType() {
return UserApi.getAuthType().then((ok) => {
return ok.data
})
} }
} }
}) })

View File

@ -1,72 +1,75 @@
<template> <template>
<div class="p-24" v-loading="loading"> <div class="p-24" v-loading="loading">
<!-- <el-form <el-form
ref="emailFormRef" ref="authFormRef"
:rules="rules" :rules="rules"
:model="form" :model="form"
label-position="top" label-position="top"
require-asterisk-position="right" require-asterisk-position="right"
> >
<el-form-item label="SMTP 主机" prop="email_host"> <el-form-item :label="$t('login.ldap.address')" prop="config_data.ldap_server">
<el-input v-model="form.email_host" placeholder="请输入 SMTP 主机" /> <el-input v-model="form.config_data.ldap_server" :placeholder="$t('login.ldap.serverPlaceholder')"/>
</el-form-item> </el-form-item>
<el-form-item label="SMTP 端口" prop="email_port"> <el-form-item :label="$t('login.ldap.bindDN')" prop="config_data.base_dn">
<el-input v-model="form.email_port" placeholder="请输入 SMTP 端口" /> <el-input v-model="form.config_data.base_dn" :placeholder="$t('login.ldap.bindDNPlaceholder')"/>
</el-form-item> </el-form-item>
<el-form-item label="SMTP 账户" prop="email_host_user"> <el-form-item :label="$t('login.ldap.password')" prop="config_data.password">
<el-input v-model="form.email_host_user" placeholder="请输入 SMTP 账户" /> <el-input v-model="form.config_data.password" :placeholder="$t('login.ldap.passwordPlaceholder')"
show-password/>
</el-form-item> </el-form-item>
<el-form-item label="发件人邮箱" prop="from_email"> <el-form-item :label="$t('login.ldap.ou')" prop="config_data.ou">
<el-input v-model="form.from_email" placeholder="请输入发件人邮箱" /> <el-input v-model="form.config_data.ou" :placeholder="$t('login.ldap.ouPlaceholder')"/>
</el-form-item> </el-form-item>
<el-form-item label="密码" prop="email_host_password"> <el-form-item :label="$t('login.ldap.ldap_filter')" prop="config_data.ldap_filter">
<el-input v-model="form.email_host_password" placeholder="请输入发件人密码" show-password /> <el-input v-model="form.config_data.ldap_filter" :placeholder="$t('login.ldap.ldap_filterPlaceholder')"/>
</el-form-item>
<el-form-item :label="$t('login.ldap.ldap_mapping')" prop="config_data.ldap_mapping">
<el-input v-model="form.config_data.ldap_mapping" :placeholder="$t('login.ldap.ldap_mappingPlaceholder')"/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-checkbox v-model="form.email_use_ssl" <el-checkbox v-model="form.is_active">{{ $t('login.ldap.enableAuthentication') }}</el-checkbox>
>开启SSL(如果SMTP端口是465通常需要启用SSL)
</el-checkbox>
</el-form-item> </el-form-item>
<el-form-item> <el-button @click="submit(authFormRef, 'test')" :disabled="loading"> {{ $t('login.ldap.test') }}</el-button>
<el-checkbox v-model="form.email_use_tls"
>开启TLS(如果SMTP端口是587通常需要启用TLS)</el-checkbox
>
</el-form-item>
<el-button @click="submit(emailFormRef, 'test')" :disabled="loading"> 测试连接 </el-button>
</el-form> </el-form>
<div class="text-right"> <div class="text-right">
<el-button @click="submit(emailFormRef)" type="primary" :disabled="loading"> 保存 </el-button> <el-button @click="submit(authFormRef)" type="primary" :disabled="loading"> {{ $t('login.ldap.save') }}
</div> --> </el-button>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue' import {reactive, ref, watch, onMounted} from 'vue'
import emailApi from '@/api/email-setting' import authApi from '@/api/auth-setting'
import type { FormInstance, FormRules } from 'element-plus' import type {FormInstance, FormRules} from 'element-plus'
import {t} from '@/locales'
import { MsgSuccess } from '@/utils/message' import {MsgSuccess} from '@/utils/message'
const form = ref<any>({ const form = ref<any>({
email_host: '', id: '',
email_port: '', auth_type: 'LDAP',
email_host_user: '', config_data: {
email_host_password: '', ldap_server: '',
email_use_tls: false, base_dn: '',
email_use_ssl: false, password: '',
from_email: '' ou: '',
ldap_filter: '',
ldap_mapping: '',
},
is_active: true
}) })
const emailFormRef = ref() const authFormRef = ref()
const loading = ref(false) const loading = ref(false)
const rules = reactive<FormRules<any>>({ const rules = reactive<FormRules<any>>({
email_host: [{ required: true, message: '请输入 SMTP 主机', trigger: 'blur' }], 'config_data.ldap_server': [{required: true, message: t('login.ldap.serverPlaceholder'), trigger: 'blur'}],
email_port: [{ required: true, message: '请输入 SMTP 端口', trigger: 'blur' }], 'config_data.base_dn': [{required: true, message: t('login.ldap.bindDNPlaceholder'), trigger: 'blur'}],
email_host_user: [{ required: true, message: '请输入 SMTP 账户', trigger: 'blur' }], 'config_data.password': [{required: true, message: t('login.ldap.passwordPlaceholder'), trigger: 'blur'}],
email_host_password: [{ required: true, message: '请输入发件人邮箱密码', trigger: 'blur' }], 'config_data.ou': [{required: true, message: t('login.ldap.ouPlaceholder'), trigger: 'blur'}],
from_email: [{ required: true, message: '请输入发件人邮箱', trigger: 'blur' }] 'config_data.ldap_filter': [{required: true, message: t('login.ldap.ldap_filterPlaceholder'), trigger: 'blur'}],
'config_data.ldap_mapping': [{required: true, message: t('login.ldap.ldap_mappingPlaceholder'), trigger: 'blur'}]
}) })
const submit = async (formEl: FormInstance | undefined, test?: string) => { const submit = async (formEl: FormInstance | undefined, test?: string) => {
@ -74,12 +77,12 @@ const submit = async (formEl: FormInstance | undefined, test?: string) => {
await formEl.validate((valid, fields) => { await formEl.validate((valid, fields) => {
if (valid) { if (valid) {
if (test) { if (test) {
emailApi.postTestEmail(form.value, loading).then((res) => { authApi.postAuthSetting(form.value, loading).then((res) => {
MsgSuccess('测试连接成功') MsgSuccess(t('login.ldap.testConnectionSuccess'))
}) })
} else { } else {
emailApi.putEmailSetting(form.value, loading).then((res) => { authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {
MsgSuccess('设置成功') MsgSuccess(t('login.ldap.saveSuccess'))
}) })
} }
} }
@ -87,9 +90,12 @@ const submit = async (formEl: FormInstance | undefined, test?: string) => {
} }
function getDetail() { function getDetail() {
emailApi.getEmailSetting(loading).then((res: any) => { authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {
if (res.data && JSON.stringify(res.data) !== '{}') { if (res.data && JSON.stringify(res.data) !== '{}') {
form.value = res.data form.value = res.data
if (res.data.config_data.ldap_mapping) {
form.value.config_data.ldap_mapping = JSON.stringify(JSON.parse(res.data.config_data.ldap_mapping))
}
} }
}) })
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="authentication-setting p-24"> <div class="authentication-setting p-24">
<h4>登录认证</h4> <h4>{{$t('login.authentication')}}</h4>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<template v-for="(item, index) in tabList" :key="index"> <template v-for="(item, index) in tabList" :key="index">
<el-tab-pane :label="item.label" :name="item.name"> <el-tab-pane :label="item.label" :name="item.name">
@ -17,16 +17,13 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import emailApi from '@/api/email-setting'
import type { FormInstance, FormRules } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import LDAP from './component/LDAP.vue' import LDAP from './component/LDAP.vue'
import { t } from '@/locales'
const activeName = ref('LDAP') const activeName = ref('LDAP')
const tabList = [ const tabList = [
{ {
label: 'LDAP设置', label: t('login.ldap.title'),
name: 'LDAP', name: 'LDAP',
component: LDAP component: LDAP
} }

View File

@ -49,38 +49,42 @@
</el-button> </el-button>
</div> </div>
<!-- <div class="login-gradient-divider lighter mt-24"> <div class="login-gradient-divider lighter mt-24" v-if="modeList.length > 1">
<span>更多登录方式</span> <span>更多登录方式</span>
</div> </div>
<div class="text-center mt-16"> <div class="text-center mt-16">
<template v-for="item in modeList">
<el-button <el-button
v-if="loginMode !== 'LDAP'" v-if="item !== ''&&loginMode !== item"
circle circle
:key="item"
class="login-button-circle color-secondary" class="login-button-circle color-secondary"
@click="changeMode('LDAP')" @click="changeMode(item)"
>LDAP</el-button >{{ item }}
> </el-button>
<el-button <el-button
v-if="loginMode !== ''" v-if="item === ''&&loginMode !== ''"
circle circle
:key="item"
class="login-button-circle color-secondary" class="login-button-circle color-secondary"
style="font-size: 24px" style="font-size: 24px"
icon="UserFilled" icon="UserFilled"
@click="changeMode('')" @click="changeMode('')"
/> />
</div> --> </template>
</div>
</LoginContainer> </LoginContainer>
</login-layout> </login-layout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import {onMounted, ref} from 'vue'
import type { LoginRequest } from '@/api/type/user' import type {LoginRequest} from '@/api/type/user'
import { useRouter } from 'vue-router' import {useRouter} from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus' import type {FormInstance, FormRules} from 'element-plus'
import useStore from '@/stores' import useStore from '@/stores'
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
const { user } = useStore() const {user} = useStore()
const router = useRouter() const router = useRouter()
const loginForm = ref<LoginRequest>({ const loginForm = ref<LoginRequest>({
username: '', username: '',
@ -105,6 +109,8 @@ const rules = ref<FormRules<LoginRequest>>({
}) })
const loginFormRef = ref<FormInstance>() const loginFormRef = ref<FormInstance>()
const modeList = ref<string[]>(['']);
const loginMode = ref('') const loginMode = ref('')
function changeMode(val: string) { function changeMode(val: string) {
@ -120,13 +126,25 @@ const login = () => {
loginFormRef.value?.validate().then(() => { loginFormRef.value?.validate().then(() => {
loading.value = true loading.value = true
user user
.login(loginForm.value.username, loginForm.value.password) .login(loginMode.value, loginForm.value.username, loginForm.value.password)
.then(() => { .then(() => {
router.push({ name: 'home' }) router.push({name: 'home'})
}) })
.finally(() => (loading.value = false)) .finally(() => (loading.value = false))
}) })
} }
onMounted(() => {
user.asyncGetProfile().then((res) => {
if (user.isXPack) {
loading.value = true
user.getAuthType().then((res) => {
modeList.value = [...modeList.value, ...res];
}).finally(() => (loading.value = false))
}
})
})
</script> </script>
<style lang="scss" scope> <style lang="scss" scope>
.login-gradient-divider { .login-gradient-divider {
@ -154,6 +172,7 @@ const login = () => {
top: 50%; top: 50%;
} }
} }
.login-button-circle { .login-button-circle {
padding: 25px !important; padding: 25px !important;
} }