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