mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-09 15:06:37 +08:00
parent
e8564f38ab
commit
c6111de050
13 changed files with 73 additions and 38 deletions
|
@ -2,10 +2,8 @@ package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
@ -311,23 +309,19 @@ func (b *BaseApi) SystemClean(c *gin.Context) {
|
||||||
// @Tags System Setting
|
// @Tags System Setting
|
||||||
// @Summary Load mfa info
|
// @Summary Load mfa info
|
||||||
// @Description 获取 mfa 信息
|
// @Description 获取 mfa 信息
|
||||||
// @Param interval path string true "request"
|
// @Accept json
|
||||||
|
// @Param request body dto.MfaCredential true "request"
|
||||||
// @Success 200 {object} mfa.Otp
|
// @Success 200 {object} mfa.Otp
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /settings/mfa/:interval [get]
|
// @Router /settings/mfa [post]
|
||||||
func (b *BaseApi) GetMFA(c *gin.Context) {
|
func (b *BaseApi) LoadMFA(c *gin.Context) {
|
||||||
intervalStr, ok := c.Params.Get("interval")
|
var req dto.MfaRequest
|
||||||
if !ok {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error interval in path"))
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
return
|
|
||||||
}
|
|
||||||
interval, err := strconv.Atoi(intervalStr)
|
|
||||||
if err != nil {
|
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("type conversion failed, err: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
otp, err := mfa.GetOtp("admin", interval)
|
otp, err := mfa.GetOtp("admin", req.Title, req.Interval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -11,6 +11,11 @@ type UserLoginInfo struct {
|
||||||
MfaStatus string `json:"mfaStatus"`
|
MfaStatus string `json:"mfaStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MfaRequest struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
type MfaCredential struct {
|
type MfaCredential struct {
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
|
|
|
@ -30,7 +30,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||||
settingRouter.GET("/time/option", baseApi.LoadTimeZone)
|
settingRouter.GET("/time/option", baseApi.LoadTimeZone)
|
||||||
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
||||||
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||||
settingRouter.GET("/mfa/:interval", baseApi.GetMFA)
|
settingRouter.POST("/mfa", baseApi.LoadMFA)
|
||||||
settingRouter.POST("/mfa/bind", baseApi.MFABind)
|
settingRouter.POST("/mfa/bind", baseApi.MFABind)
|
||||||
settingRouter.POST("/scan", baseApi.ScanSystem)
|
settingRouter.POST("/scan", baseApi.ScanSystem)
|
||||||
settingRouter.POST("/clean", baseApi.SystemClean)
|
settingRouter.POST("/clean", baseApi.SystemClean)
|
||||||
|
|
|
@ -18,11 +18,11 @@ type Otp struct {
|
||||||
QrImage string `json:"qrImage"`
|
QrImage string `json:"qrImage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOtp(username string, interval int) (otp Otp, err error) {
|
func GetOtp(username, title string, interval int) (otp Otp, err error) {
|
||||||
secret := gotp.RandomSecret(secretLength)
|
secret := gotp.RandomSecret(secretLength)
|
||||||
otp.Secret = secret
|
otp.Secret = secret
|
||||||
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
totp := gotp.NewTOTP(secret, 6, interval, nil)
|
||||||
uri := totp.ProvisioningUri(username, "1Panel")
|
uri := totp.ProvisioningUri(username, title)
|
||||||
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
|
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
|
||||||
dist := make([]byte, 3000)
|
dist := make([]byte, 3000)
|
||||||
base64.StdEncoding.Encode(dist, subImg)
|
base64.StdEncoding.Encode(dist, subImg)
|
||||||
|
|
|
@ -8648,25 +8648,30 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/settings/mfa/:interval": {
|
"/settings/mfa": {
|
||||||
"get": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"ApiKeyAuth": []
|
"ApiKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "获取 mfa 信息",
|
"description": "获取 mfa 信息",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"System Setting"
|
"System Setting"
|
||||||
],
|
],
|
||||||
"summary": "Load mfa info",
|
"summary": "Load mfa info",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
|
||||||
"description": "request",
|
"description": "request",
|
||||||
"name": "interval",
|
"name": "request",
|
||||||
"in": "path",
|
"in": "body",
|
||||||
"required": true
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.MfaCredential"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
|
@ -8641,25 +8641,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/settings/mfa/:interval": {
|
"/settings/mfa": {
|
||||||
"get": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"ApiKeyAuth": []
|
"ApiKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "获取 mfa 信息",
|
"description": "获取 mfa 信息",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"System Setting"
|
"System Setting"
|
||||||
],
|
],
|
||||||
"summary": "Load mfa info",
|
"summary": "Load mfa info",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
|
||||||
"description": "request",
|
"description": "request",
|
||||||
"name": "interval",
|
"name": "request",
|
||||||
"in": "path",
|
"in": "body",
|
||||||
"required": true
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.MfaCredential"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
|
@ -9611,15 +9611,18 @@ paths:
|
||||||
formatEN: reset an expired Password
|
formatEN: reset an expired Password
|
||||||
formatZH: 重置过期密码
|
formatZH: 重置过期密码
|
||||||
paramKeys: []
|
paramKeys: []
|
||||||
/settings/mfa/:interval:
|
/settings/mfa:
|
||||||
get:
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
description: 获取 mfa 信息
|
description: 获取 mfa 信息
|
||||||
parameters:
|
parameters:
|
||||||
- description: request
|
- description: request
|
||||||
in: path
|
in: body
|
||||||
name: interval
|
name: request
|
||||||
required: true
|
required: true
|
||||||
type: string
|
schema:
|
||||||
|
$ref: '#/definitions/dto.MfaCredential'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
|
|
|
@ -70,6 +70,10 @@ export namespace Setting {
|
||||||
export interface PortUpdate {
|
export interface PortUpdate {
|
||||||
serverPort: number;
|
serverPort: number;
|
||||||
}
|
}
|
||||||
|
export interface MFARequest {
|
||||||
|
title: string;
|
||||||
|
interval: number;
|
||||||
|
}
|
||||||
export interface MFAInfo {
|
export interface MFAInfo {
|
||||||
secret: string;
|
secret: string;
|
||||||
qrImage: string;
|
qrImage: string;
|
||||||
|
|
|
@ -57,8 +57,8 @@ export const cleanMonitors = () => {
|
||||||
return http.post(`/settings/monitor/clean`, {});
|
return http.post(`/settings/monitor/clean`, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMFA = (interval: number) => {
|
export const loadMFA = (param: Setting.MFARequest) => {
|
||||||
return http.get<Setting.MFAInfo>(`/settings/mfa/${interval}`, {});
|
return http.post<Setting.MFAInfo>(`/settings/mfa`, param);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadDaemonJsonPath = () => {
|
export const loadDaemonJsonPath = () => {
|
||||||
|
|
|
@ -1125,6 +1125,8 @@ const message = {
|
||||||
mfa: 'MFA',
|
mfa: 'MFA',
|
||||||
secret: 'Secret',
|
secret: 'Secret',
|
||||||
mfaInterval: 'Refresh interval (s)',
|
mfaInterval: 'Refresh interval (s)',
|
||||||
|
mfaTitleHelper:
|
||||||
|
'Used to differentiate between different 1Panel hosts. After modification, please rescan or manually add the key information!',
|
||||||
mfaIntervalHelper: 'Please rescan or manually add key information after modifying the refresh time.',
|
mfaIntervalHelper: 'Please rescan or manually add key information after modifying the refresh time.',
|
||||||
mfaAlert:
|
mfaAlert:
|
||||||
'MFA password is generated based on the current time. Please ensure that the server time is synchronized.',
|
'MFA password is generated based on the current time. Please ensure that the server time is synchronized.',
|
||||||
|
|
|
@ -1117,6 +1117,7 @@ const message = {
|
||||||
mfaHelper3: '輸入手機應用上的 6 位數字',
|
mfaHelper3: '輸入手機應用上的 6 位數字',
|
||||||
mfaCode: '驗證碼',
|
mfaCode: '驗證碼',
|
||||||
mfaInterval: '刷新時間(秒)',
|
mfaInterval: '刷新時間(秒)',
|
||||||
|
mfaTitleHelper: '用於區分不同 1Panel 主機,修改後請重新掃描或手動添加密鑰信息!',
|
||||||
mfaIntervalHelper: '修改刷新時間後,請重新掃描或手動添加密鑰信息!',
|
mfaIntervalHelper: '修改刷新時間後,請重新掃描或手動添加密鑰信息!',
|
||||||
sslChangeHelper: 'https 設置修改需要重啟服務,是否繼續?',
|
sslChangeHelper: 'https 設置修改需要重啟服務,是否繼續?',
|
||||||
sslDisable: '禁用',
|
sslDisable: '禁用',
|
||||||
|
|
|
@ -1118,6 +1118,7 @@ const message = {
|
||||||
mfaHelper3: '输入手机应用上的 6 位数字',
|
mfaHelper3: '输入手机应用上的 6 位数字',
|
||||||
mfaCode: '验证码',
|
mfaCode: '验证码',
|
||||||
mfaInterval: '刷新时间(秒)',
|
mfaInterval: '刷新时间(秒)',
|
||||||
|
mfaTitleHelper: '用于区分不同 1Panel 主机,修改后请重新扫描或手动添加密钥信息!',
|
||||||
mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!',
|
mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!',
|
||||||
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
|
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
|
||||||
sslDisable: '禁用',
|
sslDisable: '禁用',
|
||||||
|
|
|
@ -52,6 +52,16 @@
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('commons.table.title')" prop="title">
|
||||||
|
<el-input v-model="form.title">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="loadMfaCodeBefore(formRef)">
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('setting.mfaTitleHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.mfaInterval')" prop="interval">
|
<el-form-item :label="$t('setting.mfaInterval')" prop="interval">
|
||||||
<el-input v-model.number="form.interval">
|
<el-input v-model.number="form.interval">
|
||||||
<template #append>
|
<template #append>
|
||||||
|
@ -80,7 +90,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { bindMFA, getMFA } from '@/api/modules/setting';
|
import { bindMFA, loadMFA } from '@/api/modules/setting';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
|
@ -96,6 +106,7 @@ const drawerVisiable = ref();
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
|
title: '1Panel',
|
||||||
code: '',
|
code: '',
|
||||||
secret: '',
|
secret: '',
|
||||||
interval: 30,
|
interval: 30,
|
||||||
|
@ -134,7 +145,11 @@ const loadMfaCodeBefore = async (formEl: FormInstance | undefined) => {
|
||||||
loadMfaCode();
|
loadMfaCode();
|
||||||
};
|
};
|
||||||
const loadMfaCode = async () => {
|
const loadMfaCode = async () => {
|
||||||
const res = await getMFA(form.interval);
|
let param = {
|
||||||
|
title: form.title,
|
||||||
|
interval: form.interval,
|
||||||
|
};
|
||||||
|
const res = await loadMFA(param);
|
||||||
form.secret = res.data.secret;
|
form.secret = res.data.secret;
|
||||||
qrImage.value = res.data.qrImage;
|
qrImage.value = res.data.qrImage;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue