mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-05-10 10:21:51 +00:00
chore: implement 2fa auth (#2968)
Some checks failed
Build and Release 3X-UI / build (386) (push) Has been cancelled
Build and Release 3X-UI / build (amd64) (push) Has been cancelled
Build and Release 3X-UI / build (arm64) (push) Has been cancelled
Build and Release 3X-UI / build (armv5) (push) Has been cancelled
Build and Release 3X-UI / build (armv6) (push) Has been cancelled
Build and Release 3X-UI / build (armv7) (push) Has been cancelled
Build and Release 3X-UI / build (s390x) (push) Has been cancelled
Some checks failed
Build and Release 3X-UI / build (386) (push) Has been cancelled
Build and Release 3X-UI / build (amd64) (push) Has been cancelled
Build and Release 3X-UI / build (arm64) (push) Has been cancelled
Build and Release 3X-UI / build (armv5) (push) Has been cancelled
Build and Release 3X-UI / build (armv6) (push) Has been cancelled
Build and Release 3X-UI / build (armv7) (push) Has been cancelled
Build and Release 3X-UI / build (s390x) (push) Has been cancelled
* chore: implement 2fa auth from #2786 * chore: format code * chore: replace two factor token input with qr-code * chore: requesting confirmation of setting/removing two-factor authentication otpauth library was taken from cdnjs * chore: revert changes in `ClipboardManager` don't need it. * chore: removing twoFactor prop in settings page * chore: remove `twoFactorQr` object in `mounted` function
This commit is contained in:
parent
d39ccf4b8f
commit
fe3b1c9b52
31 changed files with 452 additions and 302 deletions
|
@ -24,7 +24,6 @@ var db *gorm.DB
|
|||
const (
|
||||
defaultUsername = "admin"
|
||||
defaultPassword = "admin"
|
||||
defaultSecret = ""
|
||||
)
|
||||
|
||||
func initModels() error {
|
||||
|
@ -63,7 +62,6 @@ func initUser() error {
|
|||
user := &model.User{
|
||||
Username: defaultUsername,
|
||||
Password: hashedPassword,
|
||||
LoginSecret: defaultSecret,
|
||||
}
|
||||
return db.Create(user).Error
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ type User struct {
|
|||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
LoginSecret string `json:"loginSecret"`
|
||||
}
|
||||
|
||||
type Inbound struct {
|
||||
|
|
3
go.mod
3
go.mod
|
@ -15,8 +15,10 @@ require (
|
|||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.3
|
||||
github.com/valyala/fasthttp v1.61.0
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
github.com/xtls/xray-core v1.250306.1-0.20250430044058-87ab8e512882
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/text v0.24.0
|
||||
google.golang.org/grpc v1.72.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
|
@ -84,7 +86,6 @@ require (
|
|||
go.uber.org/mock v0.5.2 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -187,6 +187,8 @@ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn
|
|||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
||||
github.com/xtls/xray-core v1.250306.1-0.20250430044058-87ab8e512882 h1:O/aN4TCrJ+fmaDOBoQhtTRev2hVHIENy2EJ70jQcyEY=
|
||||
|
|
35
main.go
35
main.go
|
@ -346,36 +346,6 @@ func migrateDb() {
|
|||
fmt.Println("Migration done!")
|
||||
}
|
||||
|
||||
func removeSecret() {
|
||||
userService := service.UserService{}
|
||||
|
||||
secretExists, err := userService.CheckSecretExistence()
|
||||
if err != nil {
|
||||
fmt.Println("Error checking secret existence:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !secretExists {
|
||||
fmt.Println("No secret exists to remove.")
|
||||
return
|
||||
}
|
||||
|
||||
err = userService.RemoveUserSecret()
|
||||
if err != nil {
|
||||
fmt.Println("Error removing secret:", err)
|
||||
return
|
||||
}
|
||||
|
||||
settingService := service.SettingService{}
|
||||
err = settingService.SetSecretStatus(false)
|
||||
if err != nil {
|
||||
fmt.Println("Error updating secret status:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Secret removed successfully.")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
runWebServer()
|
||||
|
@ -403,10 +373,8 @@ func main() {
|
|||
var reset bool
|
||||
var show bool
|
||||
var getCert bool
|
||||
var remove_secret bool
|
||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
||||
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
|
||||
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
|
||||
settingCmd.StringVar(&username, "username", "", "Set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "Set login password")
|
||||
|
@ -470,9 +438,6 @@ func main() {
|
|||
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||
}
|
||||
if remove_secret {
|
||||
removeSecret()
|
||||
}
|
||||
if enabletgbot {
|
||||
updateTgbotEnableSts(enabletgbot)
|
||||
}
|
||||
|
|
|
@ -23,8 +23,9 @@ class AllSetting {
|
|||
this.tgBotLoginNotify = true;
|
||||
this.tgCpu = 80;
|
||||
this.tgLang = "en-US";
|
||||
this.twoFactorEnable = false;
|
||||
this.twoFactorToken = "";
|
||||
this.xrayTemplateConfig = "";
|
||||
this.secretEnable = false;
|
||||
this.subEnable = false;
|
||||
this.subTitle = "";
|
||||
this.subListen = "";
|
||||
|
|
|
@ -145,6 +145,33 @@ class RandomUtil {
|
|||
|
||||
return Base64.alternativeEncode(String.fromCharCode(...array));
|
||||
}
|
||||
|
||||
static randomBase32String(length = 16) {
|
||||
const array = new Uint8Array(length);
|
||||
|
||||
window.crypto.getRandomValues(array);
|
||||
|
||||
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
let result = '';
|
||||
let bits = 0;
|
||||
let buffer = 0;
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
buffer = (buffer << 8) | array[i];
|
||||
bits += 8;
|
||||
|
||||
while (bits >= 5) {
|
||||
bits -= 5;
|
||||
result += base32Chars[(buffer >>> bits) & 0x1F];
|
||||
}
|
||||
}
|
||||
|
||||
if (bits > 0) {
|
||||
result += base32Chars[(buffer << (5 - bits)) & 0x1F];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectUtil {
|
||||
|
|
19
web/assets/otpauth/otpauth.umd.min.js
vendored
Normal file
19
web/assets/otpauth/otpauth.umd.min.js
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
//! otpauth 9.4.0 | (c) Héctor Molinero Fernández | MIT | https://github.com/hectorm/otpauth
|
||||
//! noble-hashes 1.7.1 | (c) Paul Miller | MIT | https://github.com/paulmillr/noble-hashes
|
||||
/// <reference types="./otpauth.d.ts" />
|
||||
// @ts-nocheck
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).OTPAuth={})}(this,(function(t){"use strict";function e(t){if(!Number.isSafeInteger(t)||t<0)throw new Error("positive integer expected, got "+t)}function s(t,...e){if(!((s=t)instanceof Uint8Array||ArrayBuffer.isView(s)&&"Uint8Array"===s.constructor.name))throw new Error("Uint8Array expected");var s;if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function i(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function r(t,e){s(t);const i=e.outputLen;if(t.length<i)throw new Error("digestInto() expects output buffer of length at least "+i)}function n(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function o(t,e){return t<<32-e|t>>>e}function h(t,e){return t<<e|t>>>32-e>>>0}const a=(()=>68===new Uint8Array(new Uint32Array([287454020]).buffer)[0])();function l(t){for(let s=0;s<t.length;s++)t[s]=(e=t[s])<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255;var e}function c(t){return"string"==typeof t&&(t=function(t){if("string"!=typeof t)throw new Error("utf8ToBytes expected string, got "+typeof t);return new Uint8Array((new TextEncoder).encode(t))}(t)),s(t),t}class u{clone(){return this._cloneInto()}}function d(t){const e=e=>t().update(c(e)).digest(),s=t();return e.outputLen=s.outputLen,e.blockLen=s.blockLen,e.create=()=>t(),e}class f extends u{update(t){return i(this),this.iHash.update(t),this}digestInto(t){i(this),s(t,this.outputLen),this.finished=!0,this.iHash.digestInto(t),this.oHash.update(t),this.oHash.digestInto(t),this.destroy()}digest(){const t=new Uint8Array(this.oHash.outputLen);return this.digestInto(t),t}_cloneInto(t){t||(t=Object.create(Object.getPrototypeOf(this),{}));const{oHash:e,iHash:s,finished:i,destroyed:r,blockLen:n,outputLen:o}=this
|
||||
;return t.finished=i,t.destroyed=r,t.blockLen=n,t.outputLen=o,t.oHash=e._cloneInto(t.oHash),t.iHash=s._cloneInto(t.iHash),t}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}constructor(t,s){super(),this.finished=!1,this.destroyed=!1,function(t){if("function"!=typeof t||"function"!=typeof t.create)throw new Error("Hash should be wrapped by utils.wrapConstructor");e(t.outputLen),e(t.blockLen)}(t);const i=c(s);if(this.iHash=t.create(),"function"!=typeof this.iHash.update)throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;const r=this.blockLen,n=new Uint8Array(r);n.set(i.length>r?t.create().update(i).digest():i);for(let t=0;t<n.length;t++)n[t]^=54;this.iHash.update(n),this.oHash=t.create();for(let t=0;t<n.length;t++)n[t]^=106;this.oHash.update(n),n.fill(0)}}const b=(t,e,s)=>new f(t,e).update(s).digest();function g(t,e,s){return t&e^~t&s}function p(t,e,s){return t&e^t&s^e&s}b.create=(t,e)=>new f(t,e);class w extends u{update(t){i(this);const{view:e,buffer:s,blockLen:r}=this,o=(t=c(t)).length;for(let i=0;i<o;){const h=Math.min(r-this.pos,o-i);if(h!==r)s.set(t.subarray(i,i+h),this.pos),this.pos+=h,i+=h,this.pos===r&&(this.process(e,0),this.pos=0);else{const e=n(t);for(;r<=o-i;i+=r)this.process(e,i)}}return this.length+=t.length,this.roundClean(),this}digestInto(t){i(this),r(t,this),this.finished=!0;const{buffer:e,view:s,blockLen:o,isLE:h}=this;let{pos:a}=this;e[a++]=128,this.buffer.subarray(a).fill(0),this.padOffset>o-a&&(this.process(s,0),a=0);for(let t=a;t<o;t++)e[t]=0;!function(t,e,s,i){if("function"==typeof t.setBigUint64)return t.setBigUint64(e,s,i);const r=BigInt(32),n=BigInt(4294967295),o=Number(s>>r&n),h=Number(s&n),a=i?4:0,l=i?0:4;t.setUint32(e+a,o,i),t.setUint32(e+l,h,i)}(s,o-8,BigInt(8*this.length),h),this.process(s,0);const l=n(t),c=this.outputLen;if(c%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const u=c/4,d=this.get()
|
||||
;if(u>d.length)throw new Error("_sha2: outputLen bigger than state");for(let t=0;t<u;t++)l.setUint32(4*t,d[t],h)}digest(){const{buffer:t,outputLen:e}=this;this.digestInto(t);const s=t.slice(0,e);return this.destroy(),s}_cloneInto(t){t||(t=new this.constructor),t.set(...this.get());const{blockLen:e,buffer:s,length:i,finished:r,destroyed:n,pos:o}=this;return t.length=i,t.pos=o,t.finished=r,t.destroyed=n,i%e&&t.buffer.set(s),t}constructor(t,e,s,i){super(),this.blockLen=t,this.outputLen=e,this.padOffset=s,this.isLE=i,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(t),this.view=n(this.buffer)}}const y=new Uint32Array([1732584193,4023233417,2562383102,271733878,3285377520]),x=new Uint32Array(80);class A extends w{get(){const{A:t,B:e,C:s,D:i,E:r}=this;return[t,e,s,i,r]}set(t,e,s,i,r){this.A=0|t,this.B=0|e,this.C=0|s,this.D=0|i,this.E=0|r}process(t,e){for(let s=0;s<16;s++,e+=4)x[s]=t.getUint32(e,!1);for(let t=16;t<80;t++)x[t]=h(x[t-3]^x[t-8]^x[t-14]^x[t-16],1);let{A:s,B:i,C:r,D:n,E:o}=this;for(let t=0;t<80;t++){let e,a;t<20?(e=g(i,r,n),a=1518500249):t<40?(e=i^r^n,a=1859775393):t<60?(e=p(i,r,n),a=2400959708):(e=i^r^n,a=3395469782);const l=h(s,5)+e+o+a+x[t]|0;o=n,n=r,r=h(i,30),i=s,s=l}s=s+this.A|0,i=i+this.B|0,r=r+this.C|0,n=n+this.D|0,o=o+this.E|0,this.set(s,i,r,n,o)}roundClean(){x.fill(0)}destroy(){this.set(0,0,0,0,0),this.buffer.fill(0)}constructor(){super(64,20,8,!1),this.A=0|y[0],this.B=0|y[1],this.C=0|y[2],this.D=0|y[3],this.E=0|y[4]}}
|
||||
const m=d((()=>new A)),H=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),L=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),I=new Uint32Array(64);class S extends w{get(){const{A:t,B:e,C:s,D:i,E:r,F:n,G:o,H:h}=this;return[t,e,s,i,r,n,o,h]}set(t,e,s,i,r,n,o,h){this.A=0|t,this.B=0|e,this.C=0|s,this.D=0|i,this.E=0|r,this.F=0|n,this.G=0|o,this.H=0|h}process(t,e){for(let s=0;s<16;s++,e+=4)I[s]=t.getUint32(e,!1);for(let t=16;t<64;t++){const e=I[t-15],s=I[t-2],i=o(e,7)^o(e,18)^e>>>3,r=o(s,17)^o(s,19)^s>>>10;I[t]=r+I[t-7]+i+I[t-16]|0}let{A:s,B:i,C:r,D:n,E:h,F:a,G:l,H:c}=this;for(let t=0;t<64;t++){const e=c+(o(h,6)^o(h,11)^o(h,25))+g(h,a,l)+H[t]+I[t]|0,u=(o(s,2)^o(s,13)^o(s,22))+p(s,i,r)|0;c=l,l=a,a=h,h=n+e|0,n=r,r=i,i=s,s=e+u|0}s=s+this.A|0,i=i+this.B|0,r=r+this.C|0,n=n+this.D|0,h=h+this.E|0,a=a+this.F|0,l=l+this.G|0,c=c+this.H|0,this.set(s,i,r,n,h,a,l,c)}roundClean(){I.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}constructor(){super(64,32,8,!1),this.A=0|L[0],this.B=0|L[1],this.C=0|L[2],this.D=0|L[3],this.E=0|L[4],this.F=0|L[5],this.G=0|L[6],this.H=0|L[7]}}class B extends S{constructor(){super(),this.A=-1056596264,this.B=914150663,this.C=812702999,this.D=-150054599,this.E=-4191439,this.F=1750603025,this.G=1694076839,this.H=-1090891868,this.outputLen=28}}
|
||||
const E=d((()=>new S)),U=d((()=>new B)),C=BigInt(2**32-1),O=BigInt(32);function v(t,e=!1){return e?{h:Number(t&C),l:Number(t>>O&C)}:{h:0|Number(t>>O&C),l:0|Number(t&C)}}function k(t,e=!1){let s=new Uint32Array(t.length),i=new Uint32Array(t.length);for(let r=0;r<t.length;r++){const{h:n,l:o}=v(t[r],e);[s[r],i[r]]=[n,o]}return[s,i]}const T=(t,e,s)=>t<<s|e>>>32-s,$=(t,e,s)=>e<<s|t>>>32-s,D=(t,e,s)=>e<<s-32|t>>>64-s,_=(t,e,s)=>t<<s-32|e>>>64-s,F={fromBig:v,split:k,toBig:(t,e)=>BigInt(t>>>0)<<O|BigInt(e>>>0),shrSH:(t,e,s)=>t>>>s,shrSL:(t,e,s)=>t<<32-s|e>>>s,rotrSH:(t,e,s)=>t>>>s|e<<32-s,rotrSL:(t,e,s)=>t<<32-s|e>>>s,rotrBH:(t,e,s)=>t<<64-s|e>>>s-32,rotrBL:(t,e,s)=>t>>>s-32|e<<64-s,rotr32H:(t,e)=>e,rotr32L:(t,e)=>t,rotlSH:T,rotlSL:$,rotlBH:D,rotlBL:_,add:function(t,e,s,i){const r=(e>>>0)+(i>>>0);return{h:t+s+(r/2**32|0)|0,l:0|r}},add3L:(t,e,s)=>(t>>>0)+(e>>>0)+(s>>>0),add3H:(t,e,s,i)=>e+s+i+(t/2**32|0)|0,add4L:(t,e,s,i)=>(t>>>0)+(e>>>0)+(s>>>0)+(i>>>0),add4H:(t,e,s,i,r)=>e+s+i+r+(t/2**32|0)|0,add5H:(t,e,s,i,r,n)=>e+s+i+r+n+(t/2**32|0)|0,add5L:(t,e,s,i,r)=>(t>>>0)+(e>>>0)+(s>>>0)+(i>>>0)+(r>>>0)
|
||||
},[G,P]=(()=>F.split(["0x428a2f98d728ae22","0x7137449123ef65cd","0xb5c0fbcfec4d3b2f","0xe9b5dba58189dbbc","0x3956c25bf348b538","0x59f111f1b605d019","0x923f82a4af194f9b","0xab1c5ed5da6d8118","0xd807aa98a3030242","0x12835b0145706fbe","0x243185be4ee4b28c","0x550c7dc3d5ffb4e2","0x72be5d74f27b896f","0x80deb1fe3b1696b1","0x9bdc06a725c71235","0xc19bf174cf692694","0xe49b69c19ef14ad2","0xefbe4786384f25e3","0x0fc19dc68b8cd5b5","0x240ca1cc77ac9c65","0x2de92c6f592b0275","0x4a7484aa6ea6e483","0x5cb0a9dcbd41fbd4","0x76f988da831153b5","0x983e5152ee66dfab","0xa831c66d2db43210","0xb00327c898fb213f","0xbf597fc7beef0ee4","0xc6e00bf33da88fc2","0xd5a79147930aa725","0x06ca6351e003826f","0x142929670a0e6e70","0x27b70a8546d22ffc","0x2e1b21385c26c926","0x4d2c6dfc5ac42aed","0x53380d139d95b3df","0x650a73548baf63de","0x766a0abb3c77b2a8","0x81c2c92e47edaee6","0x92722c851482353b","0xa2bfe8a14cf10364","0xa81a664bbc423001","0xc24b8b70d0f89791","0xc76c51a30654be30","0xd192e819d6ef5218","0xd69906245565a910","0xf40e35855771202a","0x106aa07032bbd1b8","0x19a4c116b8d2d0c8","0x1e376c085141ab53","0x2748774cdf8eeb99","0x34b0bcb5e19b48a8","0x391c0cb3c5c95a63","0x4ed8aa4ae3418acb","0x5b9cca4f7763e373","0x682e6ff3d6b2b8a3","0x748f82ee5defb2fc","0x78a5636f43172f60","0x84c87814a1f0ab72","0x8cc702081a6439ec","0x90befffa23631e28","0xa4506cebde82bde9","0xbef9a3f7b2c67915","0xc67178f2e372532b","0xca273eceea26619c","0xd186b8c721c0c207","0xeada7dd6cde0eb1e","0xf57d4f7fee6ed178","0x06f067aa72176fba","0x0a637dc5a2c898a6","0x113f9804bef90dae","0x1b710b35131c471b","0x28db77f523047d84","0x32caab7b40c72493","0x3c9ebe0a15c9bebc","0x431d67c49c100d4c","0x4cc5d4becb3e42b6","0x597f299cfc657e2a","0x5fcb6fab3ad6faec","0x6c44198c4a475817"].map((t=>BigInt(t)))))(),j=new Uint32Array(80),M=new Uint32Array(80);class R extends w{get(){const{Ah:t,Al:e,Bh:s,Bl:i,Ch:r,Cl:n,Dh:o,Dl:h,Eh:a,El:l,Fh:c,Fl:u,Gh:d,Gl:f,Hh:b,Hl:g}=this;return[t,e,s,i,r,n,o,h,a,l,c,u,d,f,b,g]}set(t,e,s,i,r,n,o,h,a,l,c,u,d,f,b,g){this.Ah=0|t,this.Al=0|e,this.Bh=0|s,this.Bl=0|i,this.Ch=0|r,this.Cl=0|n,this.Dh=0|o,
|
||||
this.Dl=0|h,this.Eh=0|a,this.El=0|l,this.Fh=0|c,this.Fl=0|u,this.Gh=0|d,this.Gl=0|f,this.Hh=0|b,this.Hl=0|g}process(t,e){for(let s=0;s<16;s++,e+=4)j[s]=t.getUint32(e),M[s]=t.getUint32(e+=4);for(let t=16;t<80;t++){const e=0|j[t-15],s=0|M[t-15],i=F.rotrSH(e,s,1)^F.rotrSH(e,s,8)^F.shrSH(e,s,7),r=F.rotrSL(e,s,1)^F.rotrSL(e,s,8)^F.shrSL(e,s,7),n=0|j[t-2],o=0|M[t-2],h=F.rotrSH(n,o,19)^F.rotrBH(n,o,61)^F.shrSH(n,o,6),a=F.rotrSL(n,o,19)^F.rotrBL(n,o,61)^F.shrSL(n,o,6),l=F.add4L(r,a,M[t-7],M[t-16]),c=F.add4H(l,i,h,j[t-7],j[t-16]);j[t]=0|c,M[t]=0|l}let{Ah:s,Al:i,Bh:r,Bl:n,Ch:o,Cl:h,Dh:a,Dl:l,Eh:c,El:u,Fh:d,Fl:f,Gh:b,Gl:g,Hh:p,Hl:w}=this;for(let t=0;t<80;t++){const e=F.rotrSH(c,u,14)^F.rotrSH(c,u,18)^F.rotrBH(c,u,41),y=F.rotrSL(c,u,14)^F.rotrSL(c,u,18)^F.rotrBL(c,u,41),x=c&d^~c&b,A=u&f^~u&g,m=F.add5L(w,y,A,P[t],M[t]),H=F.add5H(m,p,e,x,G[t],j[t]),L=0|m,I=F.rotrSH(s,i,28)^F.rotrBH(s,i,34)^F.rotrBH(s,i,39),S=F.rotrSL(s,i,28)^F.rotrBL(s,i,34)^F.rotrBL(s,i,39),B=s&r^s&o^r&o,E=i&n^i&h^n&h;p=0|b,w=0|g,b=0|d,g=0|f,d=0|c,f=0|u,({h:c,l:u}=F.add(0|a,0|l,0|H,0|L)),a=0|o,l=0|h,o=0|r,h=0|n,r=0|s,n=0|i;const U=F.add3L(L,S,E);s=F.add3H(U,H,I,B),i=0|U}({h:s,l:i}=F.add(0|this.Ah,0|this.Al,0|s,0|i)),({h:r,l:n}=F.add(0|this.Bh,0|this.Bl,0|r,0|n)),({h:o,l:h}=F.add(0|this.Ch,0|this.Cl,0|o,0|h)),({h:a,l}=F.add(0|this.Dh,0|this.Dl,0|a,0|l)),({h:c,l:u}=F.add(0|this.Eh,0|this.El,0|c,0|u)),({h:d,l:f}=F.add(0|this.Fh,0|this.Fl,0|d,0|f)),({h:b,l:g}=F.add(0|this.Gh,0|this.Gl,0|b,0|g)),({h:p,l:w}=F.add(0|this.Hh,0|this.Hl,0|p,0|w)),this.set(s,i,r,n,o,h,a,l,c,u,d,f,b,g,p,w)}roundClean(){j.fill(0),M.fill(0)}destroy(){this.buffer.fill(0),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}constructor(){super(128,64,16,!1),this.Ah=1779033703,this.Al=-205731576,this.Bh=-1150833019,this.Bl=-2067093701,this.Ch=1013904242,this.Cl=-23791573,this.Dh=-1521486534,this.Dl=1595750129,this.Eh=1359893119,this.El=-1377402159,this.Fh=-1694144372,this.Fl=725511199,this.Gh=528734635,this.Gl=-79577749,this.Hh=1541459225,this.Hl=327033209}}class N extends R{constructor(){super(),
|
||||
this.Ah=-876896931,this.Al=-1056596264,this.Bh=1654270250,this.Bl=914150663,this.Ch=-1856437926,this.Cl=812702999,this.Dh=355462360,this.Dl=-150054599,this.Eh=1731405415,this.El=-4191439,this.Fh=-1900787065,this.Fl=1750603025,this.Gh=-619958771,this.Gl=1694076839,this.Hh=1203062813,this.Hl=-1090891868,this.outputLen=48}}const X=d((()=>new R)),V=d((()=>new N)),Z=[],z=[],J=[],K=BigInt(0),Q=BigInt(1),W=BigInt(2),Y=BigInt(7),q=BigInt(256),tt=BigInt(113);for(let t=0,e=Q,s=1,i=0;t<24;t++){[s,i]=[i,(2*s+3*i)%5],Z.push(2*(5*i+s)),z.push((t+1)*(t+2)/2%64);let r=K;for(let t=0;t<7;t++)e=(e<<Q^(e>>Y)*tt)%q,e&W&&(r^=Q<<(Q<<BigInt(t))-Q);J.push(r)}const[et,st]=k(J,!0),it=(t,e,s)=>s>32?D(t,e,s):T(t,e,s),rt=(t,e,s)=>s>32?_(t,e,s):$(t,e,s);class nt extends u{keccak(){a||l(this.state32),function(t,e=24){const s=new Uint32Array(10);for(let i=24-e;i<24;i++){for(let e=0;e<10;e++)s[e]=t[e]^t[e+10]^t[e+20]^t[e+30]^t[e+40];for(let e=0;e<10;e+=2){const i=(e+8)%10,r=(e+2)%10,n=s[r],o=s[r+1],h=it(n,o,1)^s[i],a=rt(n,o,1)^s[i+1];for(let s=0;s<50;s+=10)t[e+s]^=h,t[e+s+1]^=a}let e=t[2],r=t[3];for(let s=0;s<24;s++){const i=z[s],n=it(e,r,i),o=rt(e,r,i),h=Z[s];e=t[h],r=t[h+1],t[h]=n,t[h+1]=o}for(let e=0;e<50;e+=10){for(let i=0;i<10;i++)s[i]=t[e+i];for(let i=0;i<10;i++)t[e+i]^=~s[(i+2)%10]&s[(i+4)%10]}t[0]^=et[i],t[1]^=st[i]}s.fill(0)}(this.state32,this.rounds),a||l(this.state32),this.posOut=0,this.pos=0}update(t){i(this);const{blockLen:e,state:s}=this,r=(t=c(t)).length;for(let i=0;i<r;){const n=Math.min(e-this.pos,r-i);for(let e=0;e<n;e++)s[this.pos++]^=t[i++];this.pos===e&&this.keccak()}return this}finish(){if(this.finished)return;this.finished=!0;const{state:t,suffix:e,pos:s,blockLen:i}=this;t[s]^=e,128&e&&s===i-1&&this.keccak(),t[i-1]^=128,this.keccak()}writeInto(t){i(this,!1),s(t),this.finish();const e=this.state,{blockLen:r}=this;for(let s=0,i=t.length;s<i;){this.posOut>=r&&this.keccak();const n=Math.min(r-this.posOut,i-s);t.set(e.subarray(this.posOut,this.posOut+n),s),this.posOut+=n,s+=n}return t}xofInto(t){
|
||||
if(!this.enableXOF)throw new Error("XOF is not possible for this instance");return this.writeInto(t)}xof(t){return e(t),this.xofInto(new Uint8Array(t))}digestInto(t){if(r(t,this),this.finished)throw new Error("digest() was already called");return this.writeInto(t),this.destroy(),t}digest(){return this.digestInto(new Uint8Array(this.outputLen))}destroy(){this.destroyed=!0,this.state.fill(0)}_cloneInto(t){const{blockLen:e,suffix:s,outputLen:i,rounds:r,enableXOF:n}=this;return t||(t=new nt(e,s,i,n,r)),t.state32.set(this.state32),t.pos=this.pos,t.posOut=this.posOut,t.finished=this.finished,t.rounds=r,t.suffix=s,t.outputLen=i,t.enableXOF=n,t.destroyed=this.destroyed,t}constructor(t,s,i,r=!1,n=24){if(super(),this.blockLen=t,this.suffix=s,this.outputLen=i,this.enableXOF=r,this.rounds=n,this.pos=0,this.posOut=0,this.finished=!1,this.destroyed=!1,e(i),0>=this.blockLen||this.blockLen>=200)throw new Error("Sha3 supports only keccak-f1600 function");var o;this.state=new Uint8Array(200),this.state32=(o=this.state,new Uint32Array(o.buffer,o.byteOffset,Math.floor(o.byteLength/4)))}}const ot=(t,e,s)=>d((()=>new nt(e,t,s))),ht=ot(6,144,28),at=ot(6,136,32),lt=ot(6,104,48),ct=ot(6,72,64),ut=(()=>{if("object"==typeof globalThis)return globalThis;Object.defineProperty(Object.prototype,"__GLOBALTHIS__",{get(){return this},configurable:!0});try{if("undefined"!=typeof __GLOBALTHIS__)return __GLOBALTHIS__}finally{delete Object.prototype.__GLOBALTHIS__}return"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:void 0})(),dt={SHA1:m,SHA224:U,SHA256:E,SHA384:V,SHA512:X,"SHA3-224":ht,"SHA3-256":at,"SHA3-384":lt,"SHA3-512":ct},ft=t=>{switch(!0){case/^(?:SHA-?1|SSL3-SHA1)$/i.test(t):return"SHA1";case/^SHA(?:2?-)?224$/i.test(t):return"SHA224";case/^SHA(?:2?-)?256$/i.test(t):return"SHA256";case/^SHA(?:2?-)?384$/i.test(t):return"SHA384";case/^SHA(?:2?-)?512$/i.test(t):return"SHA512";case/^SHA3-224$/i.test(t):return"SHA3-224";case/^SHA3-256$/i.test(t):return"SHA3-256";case/^SHA3-384$/i.test(t):
|
||||
return"SHA3-384";case/^SHA3-512$/i.test(t):return"SHA3-512";default:throw new TypeError(`Unknown hash algorithm: ${t}`)}},bt="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",gt=t=>{let e=(t=t.replace(/ /g,"")).length;for(;"="===t[e-1];)--e;t=(e<t.length?t.substring(0,e):t).toUpperCase();const s=new ArrayBuffer(5*t.length/8|0),i=new Uint8Array(s);let r=0,n=0,o=0;for(let e=0;e<t.length;e++){const s=bt.indexOf(t[e]);if(-1===s)throw new TypeError(`Invalid character found: ${t[e]}`);n=n<<5|s,r+=5,r>=8&&(r-=8,i[o++]=n>>>r)}return i},pt=t=>{let e=0,s=0,i="";for(let r=0;r<t.length;r++)for(s=s<<8|t[r],e+=8;e>=5;)i+=bt[s>>>e-5&31],e-=5;return e>0&&(i+=bt[s<<5-e&31]),i},wt=t=>{t=t.replace(/ /g,"");const e=new ArrayBuffer(t.length/2),s=new Uint8Array(e);for(let e=0;e<t.length;e+=2)s[e/2]=parseInt(t.substring(e,e+2),16);return s},yt=t=>{let e="";for(let s=0;s<t.length;s++){const i=t[s].toString(16);1===i.length&&(e+="0"),e+=i}return e.toUpperCase()},xt=t=>{const e=new ArrayBuffer(t.length),s=new Uint8Array(e);for(let e=0;e<t.length;e++)s[e]=255&t.charCodeAt(e);return s},At=t=>{let e="";for(let s=0;s<t.length;s++)e+=String.fromCharCode(t[s]);return e},mt=ut.TextEncoder?new ut.TextEncoder:null,Ht=ut.TextDecoder?new ut.TextDecoder:null,Lt=t=>{if(!mt)throw new Error("Encoding API not available");return mt.encode(t)},It=t=>{if(!Ht)throw new Error("Encoding API not available");return Ht.decode(t)};class St{static fromLatin1(t){return new St({buffer:xt(t).buffer})}static fromUTF8(t){return new St({buffer:Lt(t).buffer})}static fromBase32(t){return new St({buffer:gt(t).buffer})}static fromHex(t){return new St({buffer:wt(t).buffer})}get buffer(){return this.bytes.buffer}get latin1(){return Object.defineProperty(this,"latin1",{enumerable:!0,writable:!1,configurable:!1,value:At(this.bytes)}),this.latin1}get utf8(){return Object.defineProperty(this,"utf8",{enumerable:!0,writable:!1,configurable:!1,value:It(this.bytes)}),this.utf8}get base32(){return Object.defineProperty(this,"base32",{enumerable:!0,writable:!1,configurable:!1,value:pt(this.bytes)}),
|
||||
this.base32}get hex(){return Object.defineProperty(this,"hex",{enumerable:!0,writable:!1,configurable:!1,value:yt(this.bytes)}),this.hex}constructor({buffer:t,size:e=20}={}){this.bytes=void 0===t?(t=>{if(ut.crypto?.getRandomValues)return ut.crypto.getRandomValues(new Uint8Array(t));throw new Error("Cryptography API not available")})(e):new Uint8Array(t),Object.defineProperty(this,"bytes",{enumerable:!0,writable:!1,configurable:!1,value:this.bytes})}}class Bt{static get defaults(){return{issuer:"",label:"OTPAuth",issuerInLabel:!0,algorithm:"SHA1",digits:6,counter:0,window:1}}static generate({secret:t,algorithm:e=Bt.defaults.algorithm,digits:s=Bt.defaults.digits,counter:i=Bt.defaults.counter}){const r=((t,e,s)=>{if(b){const i=dt[t]??dt[ft(t)];return b(i,e,s)}throw new Error("Missing HMAC function")})(e,t.bytes,(t=>{const e=new ArrayBuffer(8),s=new Uint8Array(e);let i=t;for(let t=7;t>=0&&0!==i;t--)s[t]=255&i,i-=s[t],i/=256;return s})(i)),n=15&r[r.byteLength-1];return(((127&r[n])<<24|(255&r[n+1])<<16|(255&r[n+2])<<8|255&r[n+3])%10**s).toString().padStart(s,"0")}generate({counter:t=this.counter++}={}){return Bt.generate({secret:this.secret,algorithm:this.algorithm,digits:this.digits,counter:t})}static validate({token:t,secret:e,algorithm:s,digits:i=Bt.defaults.digits,counter:r=Bt.defaults.counter,window:n=Bt.defaults.window}){if(t.length!==i)return null;let o=null;const h=n=>{const h=Bt.generate({secret:e,algorithm:s,digits:i,counter:n});((t,e)=>{{if(t.length!==e.length)throw new TypeError("Input strings must have the same length");let s=-1,i=0;for(;++s<t.length;)i|=t.charCodeAt(s)^e.charCodeAt(s);return 0===i}})(t,h)&&(o=n-r)};h(r);for(let t=1;t<=n&&null===o&&(h(r-t),null===o)&&(h(r+t),null===o);++t);return o}validate({token:t,counter:e=this.counter,window:s}){return Bt.validate({token:t,secret:this.secret,algorithm:this.algorithm,digits:this.digits,counter:e,window:s})}toString(){const t=encodeURIComponent
|
||||
;return"otpauth://hotp/"+(this.issuer.length>0?this.issuerInLabel?`${t(this.issuer)}:${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?`)+`secret=${t(this.secret.base32)}&`+`algorithm=${t(this.algorithm)}&`+`digits=${t(this.digits)}&`+`counter=${t(this.counter)}`}constructor({issuer:t=Bt.defaults.issuer,label:e=Bt.defaults.label,issuerInLabel:s=Bt.defaults.issuerInLabel,secret:i=new St,algorithm:r=Bt.defaults.algorithm,digits:n=Bt.defaults.digits,counter:o=Bt.defaults.counter}={}){this.issuer=t,this.label=e,this.issuerInLabel=s,this.secret="string"==typeof i?St.fromBase32(i):i,this.algorithm=ft(r),this.digits=n,this.counter=o}}class Et{static get defaults(){return{issuer:"",label:"OTPAuth",issuerInLabel:!0,algorithm:"SHA1",digits:6,period:30,window:1}}static counter({period:t=Et.defaults.period,timestamp:e=Date.now()}={}){return Math.floor(e/1e3/t)}counter({timestamp:t=Date.now()}={}){return Et.counter({period:this.period,timestamp:t})}static remaining({period:t=Et.defaults.period,timestamp:e=Date.now()}={}){return 1e3*t-e%(1e3*t)}remaining({timestamp:t=Date.now()}={}){return Et.remaining({period:this.period,timestamp:t})}static generate({secret:t,algorithm:e,digits:s,period:i=Et.defaults.period,timestamp:r=Date.now()}){return Bt.generate({secret:t,algorithm:e,digits:s,counter:Et.counter({period:i,timestamp:r})})}generate({timestamp:t=Date.now()}={}){return Et.generate({secret:this.secret,algorithm:this.algorithm,digits:this.digits,period:this.period,timestamp:t})}static validate({token:t,secret:e,algorithm:s,digits:i,period:r=Et.defaults.period,timestamp:n=Date.now(),window:o}){return Bt.validate({token:t,secret:e,algorithm:s,digits:i,counter:Et.counter({period:r,timestamp:n}),window:o})}validate({token:t,timestamp:e,window:s}){return Et.validate({token:t,secret:this.secret,algorithm:this.algorithm,digits:this.digits,period:this.period,timestamp:e,window:s})}toString(){const t=encodeURIComponent
|
||||
;return"otpauth://totp/"+(this.issuer.length>0?this.issuerInLabel?`${t(this.issuer)}:${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?`)+`secret=${t(this.secret.base32)}&`+`algorithm=${t(this.algorithm)}&`+`digits=${t(this.digits)}&`+`period=${t(this.period)}`}constructor({issuer:t=Et.defaults.issuer,label:e=Et.defaults.label,issuerInLabel:s=Et.defaults.issuerInLabel,secret:i=new St,algorithm:r=Et.defaults.algorithm,digits:n=Et.defaults.digits,period:o=Et.defaults.period}={}){this.issuer=t,this.label=e,this.issuerInLabel=s,this.secret="string"==typeof i?St.fromBase32(i):i,this.algorithm=ft(r),this.digits=n,this.period=o}}const Ut=/^otpauth:\/\/([ht]otp)\/(.+)\?([A-Z0-9.~_-]+=[^?&]*(?:&[A-Z0-9.~_-]+=[^?&]*)*)$/i,Ct=/^[2-7A-Z]+=*$/i,Ot=/^SHA(?:1|224|256|384|512|3-224|3-256|3-384|3-512)$/i,vt=/^[+-]?\d+$/,kt=/^\+?[1-9]\d*$/;t.HOTP=Bt,t.Secret=St,t.TOTP=Et,t.URI=class{static parse(t){let e;try{e=t.match(Ut)}catch(t){}if(!Array.isArray(e))throw new URIError("Invalid URI format");const s=e[1].toLowerCase(),i=e[2].split(/(?::|%3A) *(.+)/i,2).map(decodeURIComponent),r=e[3].split("&").reduce(((t,e)=>{const s=e.split(/=(.*)/,2).map(decodeURIComponent),i=s[0].toLowerCase(),r=s[1],n=t;return n[i]=r,n}),{});let n;const o={};if("hotp"===s){if(n=Bt,void 0===r.counter||!vt.test(r.counter))throw new TypeError("Missing or invalid 'counter' parameter");o.counter=parseInt(r.counter,10)}else{if("totp"!==s)throw new TypeError("Unknown OTP type");if(n=Et,void 0!==r.period){if(!kt.test(r.period))throw new TypeError("Invalid 'period' parameter");o.period=parseInt(r.period,10)}}if(void 0!==r.issuer&&(o.issuer=r.issuer),2===i.length?(o.label=i[1],void 0===o.issuer||""===o.issuer?o.issuer=i[0]:""===i[0]&&(o.issuerInLabel=!1)):(o.label=i[0],void 0!==o.issuer&&""!==o.issuer&&(o.issuerInLabel=!1)),void 0===r.secret||!Ct.test(r.secret))throw new TypeError("Missing or invalid 'secret' parameter");if(o.secret=r.secret,void 0!==r.algorithm){
|
||||
if(!Ot.test(r.algorithm))throw new TypeError("Invalid 'algorithm' parameter");o.algorithm=r.algorithm}if(void 0!==r.digits){if(!kt.test(r.digits))throw new TypeError("Invalid 'digits' parameter");o.digits=parseInt(r.digits,10)}return new n(o)}static stringify(t){if(t instanceof Bt||t instanceof Et)return t.toString();throw new TypeError("Invalid 'HOTP/TOTP' object")}},t.version="9.4.0"}));
|
||||
//# sourceMappingURL=otpauth.umd.min.js.map
|
|
@ -16,7 +16,7 @@ import (
|
|||
type LoginForm struct {
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
LoginSecret string `json:"loginSecret" form:"loginSecret"`
|
||||
TwoFactorCode string `json:"twoFactorCode" form:"twoFactorCode"`
|
||||
}
|
||||
|
||||
type IndexController struct {
|
||||
|
@ -37,7 +37,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) {
|
|||
g.GET("/", a.index)
|
||||
g.POST("/login", a.login)
|
||||
g.GET("/logout", a.logout)
|
||||
g.POST("/getSecretStatus", a.getSecretStatus)
|
||||
g.POST("/getTwoFactorEnable", a.getTwoFactorEnable)
|
||||
}
|
||||
|
||||
func (a *IndexController) index(c *gin.Context) {
|
||||
|
@ -64,14 +64,13 @@ func (a *IndexController) login(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||
user := a.userService.CheckUser(form.Username, form.Password, form.TwoFactorCode)
|
||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||
safeUser := template.HTMLEscapeString(form.Username)
|
||||
safePass := template.HTMLEscapeString(form.Password)
|
||||
safeSecret := template.HTMLEscapeString(form.LoginSecret)
|
||||
|
||||
if user == nil {
|
||||
logger.Warningf("wrong username: \"%s\", password: \"%s\", secret: \"%s\", IP: \"%s\"", safeUser, safePass, safeSecret, getRemoteIp(c))
|
||||
logger.Warningf("wrong username: \"%s\", password: \"%s\", IP: \"%s\"", safeUser, safePass, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
return
|
||||
|
@ -108,8 +107,8 @@ func (a *IndexController) logout(c *gin.Context) {
|
|||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||
}
|
||||
|
||||
func (a *IndexController) getSecretStatus(c *gin.Context) {
|
||||
status, err := a.settingService.GetSecretStatus()
|
||||
func (a *IndexController) getTwoFactorEnable(c *gin.Context) {
|
||||
status, err := a.settingService.GetTwoFactorEnable()
|
||||
if err == nil {
|
||||
jsonObj(c, status, nil)
|
||||
}
|
||||
|
|
|
@ -19,10 +19,6 @@ type updateUserForm struct {
|
|||
NewPassword string `json:"newPassword" form:"newPassword"`
|
||||
}
|
||||
|
||||
type updateSecretForm struct {
|
||||
LoginSecret string `json:"loginSecret" form:"loginSecret"`
|
||||
}
|
||||
|
||||
type SettingController struct {
|
||||
settingService service.SettingService
|
||||
userService service.UserService
|
||||
|
@ -44,8 +40,6 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||
g.POST("/updateUser", a.updateUser)
|
||||
g.POST("/restartPanel", a.restartPanel)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
g.POST("/updateUserSecret", a.updateSecret)
|
||||
g.POST("/getUserSecret", a.getUserSecret)
|
||||
}
|
||||
|
||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||
|
@ -107,29 +101,6 @@ func (a *SettingController) restartPanel(c *gin.Context) {
|
|||
jsonMsg(c, I18nWeb(c, "pages.settings.restartPanel"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) updateSecret(c *gin.Context) {
|
||||
form := &updateSecretForm{}
|
||||
err := c.ShouldBind(form)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
||||
if err == nil {
|
||||
user.LoginSecret = form.LoginSecret
|
||||
session.SetLoginUser(c, user)
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||
}
|
||||
|
||||
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||
loginUser := session.GetLoginUser(c)
|
||||
user := a.userService.GetUserSecret(loginUser.Id)
|
||||
if user != nil {
|
||||
jsonObj(c, user, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
|
||||
if err != nil {
|
||||
|
|
|
@ -38,7 +38,8 @@ type AllSetting struct {
|
|||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||
TgLang string `json:"tgLang" form:"tgLang"`
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
|
||||
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||
SubTitle string `json:"subTitle" form:"subTitle"`
|
||||
SubListen string `json:"subListen" form:"subListen"`
|
||||
|
|
|
@ -512,11 +512,11 @@
|
|||
<a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="secretEnable">
|
||||
<a-input-password autocomplete="secret" name="secret" v-model.trim="user.loginSecret"
|
||||
placeholder='{{ i18n "secretToken" }}' @keydown.enter.native="login">
|
||||
<a-form-item v-if="twoFactorEnable">
|
||||
<a-input autocomplete="totp" name="twoFactorCode" v-model.trim="user.twoFactorCode"
|
||||
placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login">
|
||||
<a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon>
|
||||
</a-input-password>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-row justify="center" class="centered">
|
||||
|
@ -549,14 +549,14 @@
|
|||
user: {
|
||||
username: "",
|
||||
password: "",
|
||||
loginSecret: ""
|
||||
twoFactorCode: ""
|
||||
},
|
||||
secretEnable: false,
|
||||
twoFactorEnable: false,
|
||||
lang: ""
|
||||
},
|
||||
async mounted() {
|
||||
this.lang = LanguageManager.getLanguage();
|
||||
this.secretEnable = await this.getSecretStatus();
|
||||
this.twoFactorEnable = await this.getTwoFactorEnable();
|
||||
},
|
||||
methods: {
|
||||
async login() {
|
||||
|
@ -567,12 +567,12 @@
|
|||
location.href = basePath + 'panel/';
|
||||
}
|
||||
},
|
||||
async getSecretStatus() {
|
||||
async getTwoFactorEnable() {
|
||||
this.loading = true;
|
||||
const msg = await HttpUtil.post('/getSecretStatus');
|
||||
const msg = await HttpUtil.post('/getTwoFactorEnable');
|
||||
this.loading = false;
|
||||
if (msg.success) {
|
||||
this.secretEnable = msg.obj;
|
||||
this.twoFactorEnable = msg.obj;
|
||||
return msg.obj;
|
||||
}
|
||||
},
|
||||
|
|
118
web/html/modals/two_factor_modal.html
Normal file
118
web/html/modals/two_factor_modal.html
Normal file
|
@ -0,0 +1,118 @@
|
|||
{{define "modals/twoFactorModal"}}
|
||||
<a-modal id="two-factor-modal" v-model="twoFactorModal.visible" :title="twoFactorModal.title" :closable="true"
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<template v-if="twoFactorModal.type === 'set'">
|
||||
<p>{{ i18n "pages.settings.security.twoFactorModalSteps" }}</p>
|
||||
<a-divider></a-divider>
|
||||
<p>{{ i18n "pages.settings.security.twoFactorModalFirstStep" }}</p>
|
||||
<div :style="{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: '12px' }">
|
||||
<div
|
||||
:style="{ border: '1px solid', borderRadius: '1rem', borderColor: themeSwitcher.isDarkTheme ? 'var(--dark-color-surface-300)' : '#d9d9d9', padding: 0 }">
|
||||
<img :src="twoFactorModal.qrImage"
|
||||
:style="{ filter: themeSwitcher.isDarkTheme ? 'invert(1)' : 'none'}"
|
||||
:alt="twoFactorModal.token">
|
||||
</div>
|
||||
<span :style="{ fontSize: '12px', fontFamily: 'monospace' }">[[ twoFactorModal.token ]]</span>
|
||||
</div>
|
||||
<a-divider></a-divider>
|
||||
<p>{{ i18n "pages.settings.security.twoFactorModalSecondStep" }}</p>
|
||||
<a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input>
|
||||
</template>
|
||||
<template v-if="twoFactorModal.type === 'remove'">
|
||||
<p>{{ i18n "pages.settings.security.twoFactorModalRemoveStep" }}</p>
|
||||
<a-input v-model.trim="twoFactorModal.enteredCode" :style="{ width: '100%' }"></a-input>
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<a-button @click="twoFactorModal.cancel">
|
||||
<span>{{ i18n "cancel" }}</span>
|
||||
</a-button>
|
||||
<a-button type="primary" :disabled="twoFactorModal.enteredCode.length < 6" @click="twoFactorModal.ok">
|
||||
<span>{{ i18n "confirm" }}</span>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
const twoFactorModal = {
|
||||
title: '',
|
||||
fileName: '',
|
||||
token: '',
|
||||
enteredCode: '',
|
||||
visible: false,
|
||||
type: 'set',
|
||||
confirm: null,
|
||||
totpObject: null,
|
||||
qrImage: "",
|
||||
ok() {
|
||||
if (twoFactorModal.totpObject.generate() === twoFactorModal.enteredCode) {
|
||||
ObjectUtil.execute(twoFactorModal.confirm, true)
|
||||
|
||||
twoFactorModal.close()
|
||||
|
||||
switch (twoFactorModal.type) {
|
||||
case 'set':
|
||||
Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalSetSuccess" }}')
|
||||
break;
|
||||
case 'remove':
|
||||
Vue.prototype.$message['success']('{{ i18n "pages.settings.security.twoFactorModalDeleteSuccess" }}')
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Vue.prototype.$message['error']('{{ i18n "pages.settings.security.twoFactorModalError" }}')
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
ObjectUtil.execute(twoFactorModal.confirm, false)
|
||||
|
||||
twoFactorModal.close()
|
||||
},
|
||||
show: function ({
|
||||
title = '',
|
||||
token = '',
|
||||
type = 'set',
|
||||
confirm = (success) => { }
|
||||
}) {
|
||||
this.title = title;
|
||||
this.token = token;
|
||||
this.visible = true;
|
||||
this.confirm = confirm;
|
||||
this.type = type;
|
||||
|
||||
this.totpObject = new OTPAuth.TOTP({
|
||||
issuer: "3x-ui",
|
||||
label: "Administrator",
|
||||
algorithm: "SHA1",
|
||||
digits: 6,
|
||||
period: 30,
|
||||
secret: twoFactorModal.token,
|
||||
});
|
||||
|
||||
if (type === 'set') {
|
||||
this.qrImage = new QRious({
|
||||
size: 150,
|
||||
value: twoFactorModal.totpObject.toString(),
|
||||
background: 'white',
|
||||
backgroundAlpha: 0,
|
||||
foreground: 'black',
|
||||
padding: 12,
|
||||
level: 'L'
|
||||
}).toDataURL()
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
twoFactorModal.enteredCode = "";
|
||||
twoFactorModal.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
const twoFactorModalApp = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#two-factor-modal',
|
||||
data: {
|
||||
twoFactorModal: twoFactorModal,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
|
@ -122,10 +122,13 @@
|
|||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/otpauth/otpauth.umd.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/aSidebar" .}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
{{template "component/aSettingListItem" .}}
|
||||
{{template "modals/twoFactorModal"}}
|
||||
<script>
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
|
@ -133,7 +136,6 @@
|
|||
data: {
|
||||
themeSwitcher,
|
||||
spinning: false,
|
||||
changeSecret: false,
|
||||
oldAllSetting: new AllSetting(),
|
||||
allSetting: new AllSetting(),
|
||||
saveBtnDisable: true,
|
||||
|
@ -258,7 +260,6 @@
|
|||
app.changeRemarkSample();
|
||||
this.saveBtnDisable = true;
|
||||
}
|
||||
await this.fetchUserSecret();
|
||||
},
|
||||
async updateAllSetting() {
|
||||
this.loading(true);
|
||||
|
@ -302,38 +303,34 @@
|
|||
window.location.replace(url);
|
||||
}
|
||||
},
|
||||
async fetchUserSecret() {
|
||||
this.loading(true);
|
||||
const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
|
||||
if (userMessage.success) {
|
||||
this.user = userMessage.obj;
|
||||
toggleTwoFactor(newValue) {
|
||||
if (newValue) {
|
||||
const newTwoFactorToken = RandomUtil.randomBase32String()
|
||||
|
||||
twoFactorModal.show({
|
||||
title: '{{ i18n "pages.settings.security.twoFactorModalSetTitle" }}',
|
||||
token: newTwoFactorToken,
|
||||
type: 'set',
|
||||
confirm: (success) => {
|
||||
if (success) {
|
||||
this.allSetting.twoFactorToken = newTwoFactorToken
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
async updateSecret() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
||||
if (msg && msg.obj) {
|
||||
this.user = msg.obj;
|
||||
|
||||
this.allSetting.twoFactorEnable = success
|
||||
}
|
||||
this.loading(false);
|
||||
await this.updateAllSetting();
|
||||
},
|
||||
async getNewSecret() {
|
||||
if (!this.changeSecret) {
|
||||
this.changeSecret = true;
|
||||
this.user.loginSecret = '';
|
||||
const newSecret = RandomUtil.randomSeq(64);
|
||||
await PromiseUtil.sleep(1000);
|
||||
this.user.loginSecret = newSecret;
|
||||
this.changeSecret = false;
|
||||
}
|
||||
},
|
||||
async toggleToken(value) {
|
||||
if (value) {
|
||||
await this.getNewSecret();
|
||||
})
|
||||
} else {
|
||||
this.user.loginSecret = "";
|
||||
twoFactorModal.show({
|
||||
title: '{{ i18n "pages.settings.security.twoFactorModalDeleteTitle" }}',
|
||||
token: this.allSetting.twoFactorToken,
|
||||
type: 'remove',
|
||||
confirm: (success) => {
|
||||
if (success) {
|
||||
this.allSetting.twoFactorEnable = false
|
||||
this.allSetting.twoFactorToken = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
addNoise() {
|
||||
|
@ -526,6 +523,7 @@
|
|||
},
|
||||
async mounted() {
|
||||
await this.getAllSetting();
|
||||
|
||||
while (true) {
|
||||
await PromiseUtil.sleep(1000);
|
||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||
|
|
|
@ -31,30 +31,14 @@
|
|||
</a-space>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header='{{ i18n "pages.settings.security.secret"}}'>
|
||||
<a-collapse-panel key="2" header='{{ i18n "pages.settings.security.twoFactor" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.security.loginSecurity" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.security.loginSecurityDesc" }}</template>
|
||||
<template #title>{{ i18n "pages.settings.security.twoFactorEnable" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.security.twoFactorEnableDesc" }}</template>
|
||||
<template #control>
|
||||
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
||||
<a-icon :style="{ marginLeft: '1rem' }" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync"
|
||||
@click="getNewSecret"></a-icon>
|
||||
<a-switch @click="toggleTwoFactor" :checked="allSetting.twoFactorEnable"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.security.secretToken" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.security.secretTokenDesc" }}</template>
|
||||
<template #control>
|
||||
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item>
|
||||
<a-space direction="horizontal" :style="{ padding: '0 20px' }">
|
||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">
|
||||
<span>{{ i18n "confirm"}}</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
|
@ -48,7 +48,8 @@ var defaultValueMap = map[string]string{
|
|||
"tgBotLoginNotify": "true",
|
||||
"tgCpu": "80",
|
||||
"tgLang": "en-US",
|
||||
"secretEnable": "false",
|
||||
"twoFactorEnable": "false",
|
||||
"twoFactorToken": "",
|
||||
"subEnable": "false",
|
||||
"subTitle": "",
|
||||
"subListen": "",
|
||||
|
@ -166,8 +167,7 @@ func (s *SettingService) ResetSettings() error {
|
|||
return err
|
||||
}
|
||||
return db.Model(model.User{}).
|
||||
Where("1 = 1").
|
||||
Update("login_secret", "").Error
|
||||
Where("1 = 1").Error
|
||||
}
|
||||
|
||||
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
||||
|
@ -318,6 +318,14 @@ func (s *SettingService) GetTgLang() (string, error) {
|
|||
return s.getString("tgLang")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTwoFactorEnable() (bool, error) {
|
||||
return s.getBool("twoFactorEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTwoFactorToken() (string, error) {
|
||||
return s.getString("twoFactorToken")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetPort() (int, error) {
|
||||
return s.getInt("webPort")
|
||||
}
|
||||
|
@ -358,14 +366,6 @@ func (s *SettingService) GetRemarkModel() (string, error) {
|
|||
return s.getString("remarkModel")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSecretStatus() (bool, error) {
|
||||
return s.getBool("secretEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetSecretStatus(value bool) error {
|
||||
return s.setBool("secretEnable", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||
secret, err := s.getString("secret")
|
||||
if secret == defaultValueMap["secret"] {
|
||||
|
|
|
@ -8,10 +8,13 @@ import (
|
|||
"x-ui/logger"
|
||||
"x-ui/util/crypto"
|
||||
|
||||
"github.com/xlzd/gotp"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserService struct{}
|
||||
type UserService struct {
|
||||
settingService SettingService
|
||||
}
|
||||
|
||||
func (s *UserService) GetFirstUser() (*model.User, error) {
|
||||
db := database.GetDB()
|
||||
|
@ -26,13 +29,13 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
|
|||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) CheckUser(username string, password string, secret string) *model.User {
|
||||
func (s *UserService) CheckUser(username string, password string, twoFactorCode string) *model.User {
|
||||
db := database.GetDB()
|
||||
|
||||
user := &model.User{}
|
||||
|
||||
err := db.Model(model.User{}).
|
||||
Where("username = ? and login_secret = ?", username, secret).
|
||||
Where("username = ?", username).
|
||||
First(user).
|
||||
Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
|
@ -42,13 +45,32 @@ func (s *UserService) CheckUser(username string, password string, secret string)
|
|||
return nil
|
||||
}
|
||||
|
||||
if crypto.CheckPasswordHash(user.Password, password) {
|
||||
return user
|
||||
if !crypto.CheckPasswordHash(user.Password, password) {
|
||||
return nil
|
||||
}
|
||||
|
||||
twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
|
||||
if err != nil {
|
||||
logger.Warning("check two factor err:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if twoFactorEnable {
|
||||
twoFactorToken, err := s.settingService.GetTwoFactorToken()
|
||||
|
||||
if err != nil {
|
||||
logger.Warning("check two factor token err:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateUser(id int, username string, password string) error {
|
||||
db := database.GetDB()
|
||||
hashedPassword, err := crypto.HashPasswordAsBcrypt(password)
|
||||
|
@ -63,50 +85,6 @@ func (s *UserService) UpdateUser(id int, username string, password string) error
|
|||
Error
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateUserSecret(id int, secret string) error {
|
||||
db := database.GetDB()
|
||||
return db.Model(model.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("login_secret", secret).
|
||||
Error
|
||||
}
|
||||
|
||||
func (s *UserService) RemoveUserSecret() error {
|
||||
db := database.GetDB()
|
||||
return db.Model(model.User{}).
|
||||
Where("1 = 1").
|
||||
Update("login_secret", "").
|
||||
Error
|
||||
}
|
||||
|
||||
func (s *UserService) GetUserSecret(id int) *model.User {
|
||||
db := database.GetDB()
|
||||
user := &model.User{}
|
||||
err := db.Model(model.User{}).
|
||||
Where("id = ?", id).
|
||||
First(user).
|
||||
Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (s *UserService) CheckSecretExistence() (bool, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
var count int64
|
||||
err := db.Model(model.User{}).
|
||||
Where("login_secret IS NOT NULL").
|
||||
Count(&count).
|
||||
Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
||||
if username == "" {
|
||||
return errors.New("username can not be empty")
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "تثبيت"
|
||||
"clients" = "عملاء"
|
||||
"usage" = "استخدام"
|
||||
"secretToken" = "توكن سري"
|
||||
"twoFactorCode" = "الكود"
|
||||
"remained" = "المتبقي"
|
||||
"security" = "أمان"
|
||||
"secAlertTitle" = "تنبيه أمني"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "تنسيق البيانات المدخلة مش صحيح."
|
||||
"emptyUsername" = "اسم المستخدم مطلوب"
|
||||
"emptyPassword" = "الباسورد مطلوب"
|
||||
"wrongUsernameOrPassword" = "اسم المستخدم أو الباسورد أو السر مش صحيح."
|
||||
"wrongUsernameOrPassword" = "اسم المستخدم أو كلمة المرور أو كود المصادقة الثنائية غير صحيح."
|
||||
"successLogin" = "تسجيل دخول ناجح"
|
||||
|
||||
[pages.index]
|
||||
|
@ -501,11 +501,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "بيانات الأدمن"
|
||||
"secret" = "توكن سري"
|
||||
"loginSecurity" = "أمان تسجيل الدخول"
|
||||
"loginSecurityDesc" = "بيضيف طبقة مصادقة إضافية لزيادة الأمان."
|
||||
"secretToken" = "توكن سري"
|
||||
"secretTokenDesc" = "احتفظ بالتوكن ده في مكان آمن. التوكن ده مطلوب لتسجيل الدخول ومش ممكن تسترجعه لو ضاع."
|
||||
"twoFactor" = "المصادقة الثنائية"
|
||||
"twoFactorEnable" = "تفعيل المصادقة الثنائية"
|
||||
"twoFactorEnableDesc" = "يضيف طبقة إضافية من المصادقة لتعزيز الأمان."
|
||||
"twoFactorModalSetTitle" = "تفعيل المصادقة الثنائية"
|
||||
"twoFactorModalDeleteTitle" = "تعطيل المصادقة الثنائية"
|
||||
"twoFactorModalSteps" = "لإعداد المصادقة الثنائية، قم ببعض الخطوات:"
|
||||
"twoFactorModalFirstStep" = "1. امسح رمز QR هذا في تطبيق المصادقة أو انسخ الرمز الموجود بجانب رمز QR والصقه في التطبيق"
|
||||
"twoFactorModalSecondStep" = "2. أدخل الرمز من التطبيق"
|
||||
"twoFactorModalRemoveStep" = "أدخل الرمز من التطبيق لإزالة المصادقة الثنائية."
|
||||
"twoFactorModalSetSuccess" = "تم إنشاء المصادقة الثنائية بنجاح"
|
||||
"twoFactorModalDeleteSuccess" = "تم حذف المصادقة الثنائية بنجاح"
|
||||
"twoFactorModalError" = "رمز خاطئ"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "تعديل الإعدادات"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Install"
|
||||
"clients" = "Clients"
|
||||
"usage" = "Usage"
|
||||
"secretToken" = "Secret Token"
|
||||
"twoFactorCode" = "Code"
|
||||
"remained" = "Remained"
|
||||
"security" = "Security"
|
||||
"secAlertTitle" = "Security Alert"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "The Input data format is invalid."
|
||||
"emptyUsername" = "Username is required"
|
||||
"emptyPassword" = "Password is required"
|
||||
"wrongUsernameOrPassword" = "Invalid username or password or secret."
|
||||
"wrongUsernameOrPassword" = "Invalid username or password or two-factor code."
|
||||
"successLogin" = "Login"
|
||||
|
||||
[pages.index]
|
||||
|
@ -501,11 +501,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Admin credentials"
|
||||
"secret" = "Secret Token"
|
||||
"loginSecurity" = "Secure Login"
|
||||
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
|
||||
"secretToken" = "Secret Token"
|
||||
"secretTokenDesc" = "Please securely store this token in a safe place. This token is required for login and cannot be recovered."
|
||||
"twoFactor" = "Two-factor authentication"
|
||||
"twoFactorEnable" = "Enable 2FA"
|
||||
"twoFactorEnableDesc" = "Adds an additional layer of authentication to provide more security."
|
||||
"twoFactorModalSetTitle" = "Enable two-factor authentication"
|
||||
"twoFactorModalDeleteTitle" = "Disable two-factor authentication"
|
||||
"twoFactorModalSteps" = "To set up two-factor authentication, perform a few steps:"
|
||||
"twoFactorModalFirstStep" = "1. Scan this QR code in the app for authentication or copy the token near the QR code and paste it into the app"
|
||||
"twoFactorModalSecondStep" = "2. Enter the code from the app"
|
||||
"twoFactorModalRemoveStep" = "Enter the code from the application to remove two-factor authentication."
|
||||
"twoFactorModalSetSuccess" = "Two-factor authentication has been successfully established"
|
||||
"twoFactorModalDeleteSuccess" = "Two-factor authentication has been successfully deleted"
|
||||
"twoFactorModalError" = "Wrong code"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modify Settings"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Instalar"
|
||||
"clients" = "Clientes"
|
||||
"usage" = "Uso"
|
||||
"secretToken" = "Token Secreto"
|
||||
"twoFactorCode" = "Código"
|
||||
"remained" = "Restante"
|
||||
"security" = "Seguridad"
|
||||
"secAlertTitle" = "Alerta de Seguridad"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "El formato de los datos de entrada es inválido."
|
||||
"emptyUsername" = "Por favor ingresa el nombre de usuario."
|
||||
"emptyPassword" = "Por favor ingresa la contraseña."
|
||||
"wrongUsernameOrPassword" = "Nombre de usuario o contraseña inválidos."
|
||||
"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
|
||||
"successLogin" = "Inicio de Sesión Exitoso"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Credenciales de administrador"
|
||||
"secret" = "Token Secreto"
|
||||
"loginSecurity" = "Seguridad de Inicio de Sesión"
|
||||
"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios."
|
||||
"secretToken" = "Token Secreto"
|
||||
"secretTokenDesc" = "Por favor, copia y guarda este token de forma segura en un lugar seguro. Este token es necesario para iniciar sesión y no se puede recuperar con la herramienta de comando x-ui."
|
||||
"twoFactor" = "Autenticación de dos factores"
|
||||
"twoFactorEnable" = "Habilitar 2FA"
|
||||
"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
|
||||
"twoFactorModalSetTitle" = "Activar autenticación de dos factores"
|
||||
"twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores"
|
||||
"twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:"
|
||||
"twoFactorModalFirstStep" = "1. Escanea este código QR en la aplicación de autenticación o copia el token cerca del código QR y pégalo en la aplicación"
|
||||
"twoFactorModalSecondStep" = "2. Ingresa el código de la aplicación"
|
||||
"twoFactorModalRemoveStep" = "Ingresa el código de la aplicación para eliminar la autenticación de dos factores."
|
||||
"twoFactorModalSetSuccess" = "La autenticación de dos factores se ha establecido con éxito"
|
||||
"twoFactorModalDeleteSuccess" = "La autenticación de dos factores se ha eliminado con éxito"
|
||||
"twoFactorModalError" = "Código incorrecto"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modificar Configuraciones "
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "نصب"
|
||||
"clients" = "کاربران"
|
||||
"usage" = "استفاده"
|
||||
"secretToken" = "توکن امنیتی"
|
||||
"twoFactorCode" = "کد"
|
||||
"remained" = "باقیمانده"
|
||||
"security" = "امنیت"
|
||||
"secAlertTitle" = "هشدارامنیتی"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "اطلاعات بهدرستی وارد نشدهاست"
|
||||
"emptyUsername" = "لطفا یک نامکاربری وارد کنید"
|
||||
"emptyPassword" = "لطفا یک رمزعبور وارد کنید"
|
||||
"wrongUsernameOrPassword" = "نامکاربری یا رمزعبوراشتباهاست"
|
||||
"wrongUsernameOrPassword" = "نام کاربری، رمز عبور یا کد دو مرحلهای نامعتبر است."
|
||||
"successLogin" = "ورود"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "اعتبارنامههای ادمین"
|
||||
"secret" = "توکن مخفی"
|
||||
"loginSecurity" = "ورود ایمن"
|
||||
"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
|
||||
"secretToken" = "توکن مخفی"
|
||||
"secretTokenDesc" = "لطفاً این توکن را در مکانی امن ذخیره کنید. این توکن برای ورود به سیستم مورد نیاز است و قابل بازیابی نیست"
|
||||
"twoFactor" = "احراز هویت دو مرحلهای"
|
||||
"twoFactorEnable" = "فعالسازی 2FA"
|
||||
"twoFactorEnableDesc" = "یک لایه اضافی امنیتی برای احراز هویت فراهم میکند."
|
||||
"twoFactorModalSetTitle" = "فعالسازی احراز هویت دو مرحلهای"
|
||||
"twoFactorModalDeleteTitle" = "غیرفعالسازی احراز هویت دو مرحلهای"
|
||||
"twoFactorModalSteps" = "برای راهاندازی احراز هویت دو مرحلهای، مراحل زیر را انجام دهید:"
|
||||
"twoFactorModalFirstStep" = "1. این کد QR را در برنامه احراز هویت اسکن کنید یا توکن کنار کد QR را کپی کرده و در برنامه بچسبانید"
|
||||
"twoFactorModalSecondStep" = "2. کد را از برنامه وارد کنید"
|
||||
"twoFactorModalRemoveStep" = "برای حذف احراز هویت دو مرحلهای، کد را از برنامه وارد کنید."
|
||||
"twoFactorModalSetSuccess" = "احراز هویت دو مرحلهای با موفقیت برقرار شد"
|
||||
"twoFactorModalDeleteSuccess" = "احراز هویت دو مرحلهای با موفقیت حذف شد"
|
||||
"twoFactorModalError" = "کد نادرست"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "ویرایش تنظیمات"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Instal"
|
||||
"clients" = "Klien"
|
||||
"usage" = "Penggunaan"
|
||||
"secretToken" = "Token Rahasia"
|
||||
"twoFactorCode" = "Kode"
|
||||
"remained" = "Tersisa"
|
||||
"security" = "Keamanan"
|
||||
"secAlertTitle" = "Peringatan keamanan"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "Format data input tidak valid."
|
||||
"emptyUsername" = "Nama Pengguna diperlukan"
|
||||
"emptyPassword" = "Kata Sandi diperlukan"
|
||||
"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid."
|
||||
"wrongUsernameOrPassword" = "Username, kata sandi, atau kode dua faktor tidak valid."
|
||||
"successLogin" = "Login berhasil"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Kredensial admin"
|
||||
"secret" = "Token Rahasia"
|
||||
"loginSecurity" = "Login Aman"
|
||||
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
|
||||
"secretToken" = "Token Rahasia"
|
||||
"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan."
|
||||
"twoFactor" = "Autentikasi dua faktor"
|
||||
"twoFactorEnable" = "Aktifkan 2FA"
|
||||
"twoFactorEnableDesc" = "Menambahkan lapisan autentikasi tambahan untuk keamanan lebih."
|
||||
"twoFactorModalSetTitle" = "Aktifkan autentikasi dua faktor"
|
||||
"twoFactorModalDeleteTitle" = "Nonaktifkan autentikasi dua faktor"
|
||||
"twoFactorModalSteps" = "Untuk menyiapkan autentikasi dua faktor, lakukan beberapa langkah:"
|
||||
"twoFactorModalFirstStep" = "1. Pindai kode QR ini di aplikasi autentikasi atau salin token di dekat kode QR dan tempelkan ke aplikasi"
|
||||
"twoFactorModalSecondStep" = "2. Masukkan kode dari aplikasi"
|
||||
"twoFactorModalRemoveStep" = "Masukkan kode dari aplikasi untuk menghapus autentikasi dua faktor."
|
||||
"twoFactorModalSetSuccess" = "Autentikasi dua faktor telah berhasil dibuat"
|
||||
"twoFactorModalDeleteSuccess" = "Autentikasi dua faktor telah berhasil dihapus"
|
||||
"twoFactorModalError" = "Kode salah"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Ubah Pengaturan"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "インストール"
|
||||
"clients" = "クライアント"
|
||||
"usage" = "利用状況"
|
||||
"secretToken" = "シークレットトークン"
|
||||
"twoFactorCode" = "コード"
|
||||
"remained" = "残り"
|
||||
"security" = "セキュリティ"
|
||||
"secAlertTitle" = "セキュリティアラート"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "データ形式エラー"
|
||||
"emptyUsername" = "ユーザー名を入力してください"
|
||||
"emptyPassword" = "パスワードを入力してください"
|
||||
"wrongUsernameOrPassword" = "ユーザー名またはパスワードが間違っています"
|
||||
"wrongUsernameOrPassword" = "ユーザー名、パスワード、または二段階認証コードが無効です。"
|
||||
"successLogin" = "ログイン成功"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理者の資格情報"
|
||||
"secret" = "セキュリティトークン"
|
||||
"loginSecurity" = "ログインセキュリティ"
|
||||
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
|
||||
"secretToken" = "セキュリティトークン"
|
||||
"secretTokenDesc" = "このトークンを安全な場所に保管してください。このトークンはログインに使用され、紛失すると回復できません。"
|
||||
"twoFactor" = "二段階認証"
|
||||
"twoFactorEnable" = "2FAを有効化"
|
||||
"twoFactorEnableDesc" = "セキュリティを強化するために追加の認証層を追加します。"
|
||||
"twoFactorModalSetTitle" = "二段階認証を有効にする"
|
||||
"twoFactorModalDeleteTitle" = "二段階認証を無効にする"
|
||||
"twoFactorModalSteps" = "二段階認証を設定するには、次の手順を実行してください:"
|
||||
"twoFactorModalFirstStep" = "1. 認証アプリでこのQRコードをスキャンするか、QRコード近くのトークンをコピーしてアプリに貼り付けます"
|
||||
"twoFactorModalSecondStep" = "2. アプリからコードを入力してください"
|
||||
"twoFactorModalRemoveStep" = "二段階認証を削除するには、アプリからコードを入力してください。"
|
||||
"twoFactorModalSetSuccess" = "二要素認証が正常に設定されました"
|
||||
"twoFactorModalDeleteSuccess" = "二要素認証が正常に削除されました"
|
||||
"twoFactorModalError" = "コードが間違っています"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "設定を変更"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Instalar"
|
||||
"clients" = "Clientes"
|
||||
"usage" = "Uso"
|
||||
"secretToken" = "Token Secreto"
|
||||
"twoFactorCode" = "Código"
|
||||
"remained" = "Restante"
|
||||
"security" = "Segurança"
|
||||
"secAlertTitle" = "Alerta de Segurança"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "O formato dos dados de entrada é inválido."
|
||||
"emptyUsername" = "Nome de usuário é obrigatório"
|
||||
"emptyPassword" = "Senha é obrigatória"
|
||||
"wrongUsernameOrPassword" = "Nome de usuário, senha ou segredo inválidos."
|
||||
"wrongUsernameOrPassword" = "Nome de usuário, senha ou código de dois fatores inválido."
|
||||
"successLogin" = "Login realizado com sucesso"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Credenciais de administrador"
|
||||
"secret" = "Token Secreto"
|
||||
"loginSecurity" = "Login Seguro"
|
||||
"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança."
|
||||
"secretToken" = "Token Secreto"
|
||||
"secretTokenDesc" = "Por favor, armazene este token em um local seguro. Este token é necessário para o login e não pode ser recuperado."
|
||||
"twoFactor" = "Autenticação de dois fatores"
|
||||
"twoFactorEnable" = "Ativar 2FA"
|
||||
"twoFactorEnableDesc" = "Adiciona uma camada extra de autenticação para mais segurança."
|
||||
"twoFactorModalSetTitle" = "Ativar autenticação de dois fatores"
|
||||
"twoFactorModalDeleteTitle" = "Desativar autenticação de dois fatores"
|
||||
"twoFactorModalSteps" = "Para configurar a autenticação de dois fatores, siga alguns passos:"
|
||||
"twoFactorModalFirstStep" = "1. Escaneie este QR code no aplicativo de autenticação ou copie o token próximo ao QR code e cole no aplicativo"
|
||||
"twoFactorModalSecondStep" = "2. Digite o código do aplicativo"
|
||||
"twoFactorModalRemoveStep" = "Digite o código do aplicativo para remover a autenticação de dois fatores."
|
||||
"twoFactorModalSetSuccess" = "A autenticação de dois fatores foi estabelecida com sucesso"
|
||||
"twoFactorModalDeleteSuccess" = "A autenticação de dois fatores foi excluída com sucesso"
|
||||
"twoFactorModalError" = "Código incorreto"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modificar Configurações"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Установка"
|
||||
"clients" = "Клиенты"
|
||||
"usage" = "Использование"
|
||||
"secretToken" = "Секретный токен"
|
||||
"twoFactorCode" = "Код"
|
||||
"remained" = "Остаток"
|
||||
"security" = "Безопасность"
|
||||
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "Недопустимый формат данных"
|
||||
"emptyUsername" = "Введите имя пользователя"
|
||||
"emptyPassword" = "Введите пароль"
|
||||
"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или секретный токен."
|
||||
"wrongUsernameOrPassword" = "Неверное имя пользователя, пароль или код двухфакторной аутентификации."
|
||||
"successLogin" = "Успешный вход"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Учетные данные администратора"
|
||||
"secret" = "Секретный токен"
|
||||
"loginSecurity" = "Безопасность входа"
|
||||
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
|
||||
"secretToken" = "Секретный токен"
|
||||
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui"
|
||||
"twoFactor" = "Двухфакторная аутентификация"
|
||||
"twoFactorEnable" = "Включить 2FA"
|
||||
"twoFactorEnableDesc" = "Добавляет дополнительный уровень аутентификации для повышения безопасности."
|
||||
"twoFactorModalSetTitle" = "Включить двухфакторную аутентификацию"
|
||||
"twoFactorModalDeleteTitle" = "Отключить двухфакторную аутентификацию"
|
||||
"twoFactorModalSteps" = "Для настройки двухфакторной аутентификации выполните несколько шагов:"
|
||||
"twoFactorModalFirstStep" = "1. Отсканируйте этот QR-код в приложении для аутентификации или скопируйте токен рядом с QR-кодом и вставьте его в приложение"
|
||||
"twoFactorModalSecondStep" = "2. Введите код из приложения"
|
||||
"twoFactorModalRemoveStep" = "Введите код из приложения, чтобы отключить двухфакторную аутентификацию."
|
||||
"twoFactorModalSetSuccess" = "Двухфакторная аутентификация была успешно установлена"
|
||||
"twoFactorModalDeleteSuccess" = "Двухфакторная аутентификация была успешно удалена"
|
||||
"twoFactorModalError" = "Неверный код"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Настройки изменены"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Yükle"
|
||||
"clients" = "Müşteriler"
|
||||
"usage" = "Kullanım"
|
||||
"secretToken" = "Gizli Anahtar"
|
||||
"twoFactorCode" = "Kod"
|
||||
"remained" = "Kalan"
|
||||
"security" = "Güvenlik"
|
||||
"secAlertTitle" = "Güvenlik Uyarısı"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "Girdi verisi formatı geçersiz."
|
||||
"emptyUsername" = "Kullanıcı adı gerekli"
|
||||
"emptyPassword" = "Şifre gerekli"
|
||||
"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı veya şifre veya gizli anahtar."
|
||||
"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı, şifre veya iki adımlı doğrulama kodu."
|
||||
"successLogin" = "Giriş Başarılı"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Yönetici kimlik bilgileri"
|
||||
"secret" = "Gizli Anahtar"
|
||||
"loginSecurity" = "Güvenli Giriş"
|
||||
"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler."
|
||||
"secretToken" = "Gizli Anahtar"
|
||||
"secretTokenDesc" = "Bu anahtarı güvenli bir yerde saklayın. Bu anahtar giriş için gereklidir ve geri alınamaz."
|
||||
"twoFactor" = "İki adımlı doğrulama"
|
||||
"twoFactorEnable" = "2FA'yı Etkinleştir"
|
||||
"twoFactorEnableDesc" = "Daha fazla güvenlik için ek bir doğrulama katmanı ekler."
|
||||
"twoFactorModalSetTitle" = "İki adımlı doğrulamayı etkinleştir"
|
||||
"twoFactorModalDeleteTitle" = "İki adımlı doğrulamayı devre dışı bırak"
|
||||
"twoFactorModalSteps" = "İki adımlı doğrulamayı ayarlamak için şu adımları izleyin:"
|
||||
"twoFactorModalFirstStep" = "1. Bu QR kodunu doğrulama uygulamasında tarayın veya QR kodunun yanındaki token'ı kopyalayıp uygulamaya yapıştırın"
|
||||
"twoFactorModalSecondStep" = "2. Uygulamadaki kodu girin"
|
||||
"twoFactorModalRemoveStep" = "İki adımlı doğrulamayı kaldırmak için uygulamadaki kodu girin."
|
||||
"twoFactorModalSetSuccess" = "İki faktörlü kimlik doğrulama başarıyla kuruldu"
|
||||
"twoFactorModalDeleteSuccess" = "İki faktörlü kimlik doğrulama başarıyla silindi"
|
||||
"twoFactorModalError" = "Yanlış kod"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Ayarları Değiştir"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Встановити"
|
||||
"clients" = "Клієнти"
|
||||
"usage" = "Використання"
|
||||
"secretToken" = "Секретний маркер"
|
||||
"twoFactorCode" = "Код"
|
||||
"remained" = "Залишилося"
|
||||
"security" = "Беспека"
|
||||
"secAlertTitle" = "Попередження системи безпеки"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "Формат вхідних даних недійсний."
|
||||
"emptyUsername" = "Потрібне ім'я користувача"
|
||||
"emptyPassword" = "Потрібен пароль"
|
||||
"wrongUsernameOrPassword" = "Невірне ім'я користувача або пароль."
|
||||
"wrongUsernameOrPassword" = "Невірне ім’я користувача, пароль або код двофакторної аутентифікації."
|
||||
"successLogin" = "Вхід"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Облікові дані адміністратора"
|
||||
"secret" = "Секретний маркер"
|
||||
"loginSecurity" = "Безпечний вхід"
|
||||
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
|
||||
"secretToken" = "Секретний маркер"
|
||||
"secretTokenDesc" = "Будь ласка, надійно зберігайте цей маркер у безпечному місці. Цей маркер потрібен для входу, і його неможливо відновити."
|
||||
"twoFactor" = "Двофакторна аутентифікація"
|
||||
"twoFactorEnable" = "Увімкнути 2FA"
|
||||
"twoFactorEnableDesc" = "Додає додатковий рівень аутентифікації для підвищення безпеки."
|
||||
"twoFactorModalSetTitle" = "Увімкнути двофакторну аутентифікацію"
|
||||
"twoFactorModalDeleteTitle" = "Вимкнути двофакторну аутентифікацію"
|
||||
"twoFactorModalSteps" = "Щоб налаштувати двофакторну аутентифікацію, виконайте кілька кроків:"
|
||||
"twoFactorModalFirstStep" = "1. Відскануйте цей QR-код у програмі для аутентифікації або скопіюйте токен біля QR-коду та вставте його в програму"
|
||||
"twoFactorModalSecondStep" = "2. Введіть код з програми"
|
||||
"twoFactorModalRemoveStep" = "Введіть код з програми, щоб вимкнути двофакторну аутентифікацію."
|
||||
"twoFactorModalSetSuccess" = "Двофакторна аутентифікація була успішно встановлена"
|
||||
"twoFactorModalDeleteSuccess" = "Двофакторна аутентифікація була успішно видалена"
|
||||
"twoFactorModalError" = "Невірний код"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Змінити налаштування"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "Cài đặt"
|
||||
"clients" = "Các khách hàng"
|
||||
"usage" = "Sử dụng"
|
||||
"secretToken" = "Mã bí mật"
|
||||
"twoFactorCode" = "Mã"
|
||||
"remained" = "Còn lại"
|
||||
"security" = "Bảo vệ"
|
||||
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
|
||||
"emptyUsername" = "Vui lòng nhập tên người dùng."
|
||||
"emptyPassword" = "Vui lòng nhập mật khẩu."
|
||||
"wrongUsernameOrPassword" = "Tên người dùng hoặc mật khẩu không đúng."
|
||||
"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
|
||||
"successLogin" = "Đăng nhập thành công."
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "Thông tin đăng nhập quản trị viên"
|
||||
"secret" = "Mã thông báo bí mật"
|
||||
"loginSecurity" = "Bảo mật đăng nhập"
|
||||
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
|
||||
"secretToken" = "Mã bí mật"
|
||||
"secretTokenDesc" = "Vui lòng sao chép và lưu trữ mã này một cách an toàn ở nơi an toàn. Mã này cần thiết để đăng nhập và không thể phục hồi từ công cụ lệnh x-ui."
|
||||
"twoFactor" = "Xác thực hai yếu tố"
|
||||
"twoFactorEnable" = "Bật 2FA"
|
||||
"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
|
||||
"twoFactorModalSetTitle" = "Bật xác thực hai yếu tố"
|
||||
"twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố"
|
||||
"twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:"
|
||||
"twoFactorModalFirstStep" = "1. Quét mã QR này trong ứng dụng xác thực hoặc sao chép mã token gần mã QR và dán vào ứng dụng"
|
||||
"twoFactorModalSecondStep" = "2. Nhập mã từ ứng dụng"
|
||||
"twoFactorModalRemoveStep" = "Nhập mã từ ứng dụng để xóa xác thực hai yếu tố."
|
||||
"twoFactorModalSetSuccess" = "Xác thực hai yếu tố đã được thiết lập thành công"
|
||||
"twoFactorModalDeleteSuccess" = "Xác thực hai yếu tố đã được xóa thành công"
|
||||
"twoFactorModalError" = "Mã sai"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Chỉnh sửa cài đặt "
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "安装"
|
||||
"clients" = "客户端"
|
||||
"usage" = "使用情况"
|
||||
"secretToken" = "安全密钥"
|
||||
"twoFactorCode" = "代码"
|
||||
"remained" = "剩余"
|
||||
"security" = "安全"
|
||||
"secAlertTitle" = "安全警报"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "数据格式错误"
|
||||
"emptyUsername" = "请输入用户名"
|
||||
"emptyPassword" = "请输入密码"
|
||||
"wrongUsernameOrPassword" = "用户名或密码错误"
|
||||
"wrongUsernameOrPassword" = "用户名、密码或双重验证码无效。"
|
||||
"successLogin" = "登录"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理员凭据"
|
||||
"secret" = "安全令牌"
|
||||
"loginSecurity" = "登录安全"
|
||||
"loginSecurityDesc" = "添加额外的身份验证以提高安全性"
|
||||
"secretToken" = "安全令牌"
|
||||
"secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。"
|
||||
"twoFactor" = "双重验证"
|
||||
"twoFactorEnable" = "启用2FA"
|
||||
"twoFactorEnableDesc" = "增加额外的验证层以提高安全性。"
|
||||
"twoFactorModalSetTitle" = "啟用雙重認證"
|
||||
"twoFactorModalDeleteTitle" = "停用雙重認證"
|
||||
"twoFactorModalSteps" = "要設定雙重認證,請執行以下步驟:"
|
||||
"twoFactorModalFirstStep" = "1. 在認證應用程式中掃描此QR碼,或複製QR碼附近的令牌並貼到應用程式中"
|
||||
"twoFactorModalSecondStep" = "2. 輸入應用程式中的驗證碼"
|
||||
"twoFactorModalRemoveStep" = "輸入應用程式中的驗證碼以移除雙重認證。"
|
||||
"twoFactorModalSetSuccess" = "双因素认证已成功建立"
|
||||
"twoFactorModalDeleteSuccess" = "双因素认证已成功删除"
|
||||
"twoFactorModalError" = "驗證碼錯誤"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "修改设置"
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"install" = "安裝"
|
||||
"clients" = "客戶端"
|
||||
"usage" = "使用情況"
|
||||
"secretToken" = "安全金鑰"
|
||||
"twoFactorCode" = "代碼"
|
||||
"remained" = "剩餘"
|
||||
"security" = "安全"
|
||||
"secAlertTitle" = "安全警報"
|
||||
|
@ -87,7 +87,7 @@
|
|||
"invalidFormData" = "資料格式錯誤"
|
||||
"emptyUsername" = "請輸入使用者名稱"
|
||||
"emptyPassword" = "請輸入密碼"
|
||||
"wrongUsernameOrPassword" = "使用者名稱或密碼錯誤"
|
||||
"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。"
|
||||
"successLogin" = "登入"
|
||||
|
||||
[pages.index]
|
||||
|
@ -503,11 +503,18 @@
|
|||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理員憑證"
|
||||
"secret" = "安全令牌"
|
||||
"loginSecurity" = "登入安全"
|
||||
"loginSecurityDesc" = "新增額外的身份驗證以提高安全性"
|
||||
"secretToken" = "安全令牌"
|
||||
"secretTokenDesc" = "請將此令牌儲存在安全的地方。此令牌用於登入,丟失無法恢復。"
|
||||
"twoFactor" = "雙重驗證"
|
||||
"twoFactorEnable" = "啟用2FA"
|
||||
"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。"
|
||||
"twoFactorModalSetTitle" = "啟用雙重認證"
|
||||
"twoFactorModalDeleteTitle" = "停用雙重認證"
|
||||
"twoFactorModalSteps" = "要設定雙重認證,請執行以下步驟:"
|
||||
"twoFactorModalFirstStep" = "1. 在認證應用程式中掃描此QR碼,或複製QR碼附近的令牌並貼到應用程式中"
|
||||
"twoFactorModalSecondStep" = "2. 輸入應用程式中的驗證碼"
|
||||
"twoFactorModalRemoveStep" = "輸入應用程式中的驗證碼以移除雙重認證。"
|
||||
"twoFactorModalSetSuccess" = "雙重身份驗證已成功建立"
|
||||
"twoFactorModalDeleteSuccess" = "雙重身份驗證已成功刪除"
|
||||
"twoFactorModalError" = "驗證碼錯誤"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "修改設定"
|
||||
|
|
4
x-ui.sh
4
x-ui.sh
|
@ -184,10 +184,8 @@ reset_user() {
|
|||
read -rp "Please set the login password [default is a random password]: " config_password
|
||||
[[ -z $config_password ]] && config_password=$(date +%s%N | md5sum | cut -c 1-8)
|
||||
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} >/dev/null 2>&1
|
||||
/usr/local/x-ui/x-ui setting -remove_secret >/dev/null 2>&1
|
||||
echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
|
||||
echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
|
||||
echo -e "${yellow} Panel login secret token disabled ${plain}"
|
||||
echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
|
||||
confirm_restart
|
||||
}
|
||||
|
@ -1731,7 +1729,7 @@ show_menu() {
|
|||
│ ${green}4.${plain} Legacy Version │
|
||||
│ ${green}5.${plain} Uninstall │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}6.${plain} Reset Username & Password & Secret Token │
|
||||
│ ${green}6.${plain} Reset Username & Password │
|
||||
│ ${green}7.${plain} Reset Web Base Path │
|
||||
│ ${green}8.${plain} Reset Settings │
|
||||
│ ${green}9.${plain} Change Port │
|
||||
|
|
Loading…
Reference in a new issue