Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c794e9d4b | ||
|
|
01f697d279 | ||
|
|
6cdc37333b | ||
|
|
ae32915565 | ||
|
|
f49457dc5b | ||
|
|
d9e2e247ea |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -203,6 +203,7 @@ jobs:
|
||||
- name: Build package for ${{ matrix.job.platform }} arch
|
||||
run: |
|
||||
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
|
||||
mv ${{ matrix.job.platform }}/release/resources/admin resources
|
||||
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
|
||||
mkdir -p data
|
||||
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
|
||||
|
||||
@@ -193,6 +193,7 @@ jwt:
|
||||
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
|
||||
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
|
||||
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
|
||||
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长(秒) | `3600` |
|
||||
| -----ADMIN配置----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
|
||||
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
|
||||
|
||||
@@ -194,6 +194,7 @@ The prefix for variable names is `RUSTDESK_API`. If environment variables exist,
|
||||
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
|
||||
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
|
||||
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
|
||||
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration(second) | `3600` |
|
||||
| ----- ADMIN Configuration----- | ---------- | ---------- |
|
||||
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
|
||||
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
|
||||
|
||||
@@ -3,6 +3,7 @@ app:
|
||||
web-client: 1 # 1:启用 0:禁用
|
||||
register: false #是否开启注册
|
||||
show-swagger: 0 # 1:启用 0:禁用
|
||||
token-expire: 360000
|
||||
admin:
|
||||
title: "RustDesk Api Admin"
|
||||
hello-file: "./conf/admin/hello.html" #优先使用file
|
||||
@@ -57,3 +58,23 @@ oss:
|
||||
expire-time: 30
|
||||
max-byte: 10240
|
||||
|
||||
ldap:
|
||||
enable: false
|
||||
url: "ldap://ldap.example.com:389"
|
||||
tls: false
|
||||
tls-verify: false
|
||||
base-dn: "dc=example,dc=com"
|
||||
bind-dn: "cn=admin,dc=example,dc=com"
|
||||
bind-password: "password"
|
||||
|
||||
user:
|
||||
base-dn: "ou=users,dc=example,dc=com"
|
||||
enable-attr: "" #The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
|
||||
enable-attr-value: "" # The value of the enable attribute when the user is enabled. If you are using AD, just set random value, it will be ignored.
|
||||
filter: "(cn=*)"
|
||||
username: "uid" # The attribute name of the user for usernamem if you are using AD, it should be "sAMAccountName"
|
||||
email: "mail"
|
||||
first-name: "givenName"
|
||||
last-name: "sn"
|
||||
sync: false # If true, the user will be synchronized to the database when the user logs in. If false, the user will be synchronized to the database when the user be created.
|
||||
admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin.
|
||||
|
||||
@@ -17,6 +17,7 @@ type App struct {
|
||||
WebClient int `mapstructure:"web-client"`
|
||||
Register bool `mapstructure:"register"`
|
||||
ShowSwagger int `mapstructure:"show-swagger"`
|
||||
TokenExpire int `mapstructure:"token-expire"`
|
||||
}
|
||||
type Admin struct {
|
||||
Title string `mapstructure:"title"`
|
||||
@@ -37,6 +38,7 @@ type Config struct {
|
||||
Jwt Jwt
|
||||
Rustdesk Rustdesk
|
||||
Proxy Proxy
|
||||
Ldap Ldap
|
||||
}
|
||||
|
||||
// Init 初始化配置
|
||||
|
||||
36
config/ldap.go
Normal file
36
config/ldap.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
type LdapUser struct {
|
||||
BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching
|
||||
EnableAttr string `mapstructure:"enable-attr"` // The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
|
||||
EnableAttrValue string `mapstructure:"enable-attr-value"` // The value of the enable attribute when the user is enabled. If you are using AD, just leave it random str, it will be ignored.
|
||||
Filter string `mapstructure:"filter"`
|
||||
Username string `mapstructure:"username"`
|
||||
Email string `mapstructure:"email"`
|
||||
FirstName string `mapstructure:"first-name"`
|
||||
LastName string `mapstructure:"last-name"`
|
||||
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
|
||||
AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
|
||||
}
|
||||
|
||||
// type LdapGroup struct {
|
||||
// BaseDn string `mapstructure:"base-dn"` // The base DN of the group for searching
|
||||
// Name string `mapstructure:"name"` // The attribute name of the group
|
||||
// Filter string `mapstructure:"filter"`
|
||||
// Admin string `mapstructure:"admin"` // Which group is the admin group
|
||||
// Member string `mapstructure:"member"` // How to get the member of the group: member, uniqueMember, or memberOf (default: member)
|
||||
// Mode string `mapstructure:"mode"`
|
||||
// Map map[string]string `mapstructure:"map"` // If mode is "map", map the LDAP group to the internal group
|
||||
// }
|
||||
|
||||
type Ldap struct {
|
||||
Enable bool `mapstructure:"enable"`
|
||||
Url string `mapstructure:"url"`
|
||||
TLS bool `mapstructure:"tls"`
|
||||
TlsVerify bool `mapstructure:"tls-verify"`
|
||||
BaseDn string `mapstructure:"base-dn"`
|
||||
BindDn string `mapstructure:"bind-dn"`
|
||||
BindPassword string `mapstructure:"bind-password"`
|
||||
User LdapUser `mapstructure:"user"`
|
||||
// Group LdapGroup `mapstructure:"group"`
|
||||
}
|
||||
13
go.mod
13
go.mod
@@ -13,7 +13,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.11.2
|
||||
github.com/go-redis/redis/v8 v8.11.4
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
@@ -22,13 +22,14 @@ require (
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/text v0.21.0
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
@@ -37,6 +38,8 @@ require (
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/go-ldap/ldap/v3 v3.4.10 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
@@ -70,10 +73,10 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
||||
|
||||
4
resources/web2/index.html
vendored
4
resources/web2/index.html
vendored
@@ -32,7 +32,7 @@
|
||||
<title>RustDesk</title>
|
||||
<script src="/webclient-config/index.js"></script>
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<script type="module" crossorigin src="js/dist/index.js?v=1bbc8b94"></script>
|
||||
<script type="module" crossorigin src="js/dist/index.js?v=cabfd933"></script>
|
||||
<link rel="modulepreload" href="js/dist/vendor.js?v=0b990c6e" />
|
||||
<style>
|
||||
html,
|
||||
@@ -259,7 +259,7 @@
|
||||
}
|
||||
scriptLoaded = true;
|
||||
var scriptTag = document.createElement("script");
|
||||
scriptTag.src = "main.dart.js?v=f6f842b3";
|
||||
scriptTag.src = "main.dart.js?v=060a626e";
|
||||
scriptTag.type = "application/javascript";
|
||||
document.body.append(scriptTag);
|
||||
}
|
||||
|
||||
175
resources/web2/js/dist/index.js
vendored
175
resources/web2/js/dist/index.js
vendored
@@ -18,7 +18,6 @@ var h = (u, e, i) => (ue(u, e, "read from private field"), i ? i.call(u) : e.get
|
||||
}
|
||||
}), je = (u, e, i) => (ue(u, e, "access private method"), i);
|
||||
|
||||
|
||||
const sa = function () {
|
||||
const e = document.createElement("link").relList;
|
||||
if (e && e.supports && e.supports("modulepreload")) return;
|
||||
@@ -7590,7 +7589,7 @@ class N4 {
|
||||
let i = new Uint8Array(e.data);
|
||||
this._recvDataCount += i.length;
|
||||
const o = this._secretKey;
|
||||
o && (o[2] += 1, i = Mn(i, o[2], o[0]));
|
||||
o && (o[2] += 1, i = Kn(i, o[2], o[0]));
|
||||
let a;
|
||||
i.length == 0 ? a = new Uint8Array : a = this._isRendezvous ? this.parseRendezvous(i) : this.parseMessage(i), this._buf.push(a), this._eventHandlers.message && (this._isProcessing || this.processQueue())
|
||||
}
|
||||
@@ -7817,7 +7816,7 @@ const Co = {
|
||||
RShift: "RShift",
|
||||
CTRL_ALT_DEL: "CtrlAltDel",
|
||||
LOCK_SCREEN: "LockScreen"
|
||||
}, se = "1.3.6", po = "2024-12-22 23:23";
|
||||
}, se = "1.3.7", po = "2025-01-21 01:12";
|
||||
|
||||
class A {
|
||||
static setItem(e, i) {
|
||||
@@ -8036,7 +8035,7 @@ async function zo() {
|
||||
}
|
||||
|
||||
function Z(u) {
|
||||
return Tn(u)
|
||||
return Ln(u)
|
||||
}
|
||||
|
||||
function c4(u) {
|
||||
@@ -8044,7 +8043,7 @@ function c4(u) {
|
||||
}
|
||||
|
||||
async function Po() {
|
||||
return await Jn()
|
||||
return await $n()
|
||||
}
|
||||
|
||||
function O4() {
|
||||
@@ -9176,7 +9175,7 @@ async function rn(u) {
|
||||
function xe(u, e = void 0) {
|
||||
const i = () => {
|
||||
try {
|
||||
Kn(new TextDecoder().decode(u.content)), fe(), e == null || e()
|
||||
Xn(new TextDecoder().decode(u.content)), fe(), e == null || e()
|
||||
} catch (o) {
|
||||
console.error("Failed to copy to clipboard, ", o), document.hasFocus() || (q4 = u)
|
||||
}
|
||||
@@ -9240,7 +9239,7 @@ async function Nt(u, e, i = void 0) {
|
||||
}
|
||||
|
||||
function $i() {
|
||||
In("info", "Clipboard is synchronized", 2e3)
|
||||
Un("info", "Clipboard is synchronized", 2e3)
|
||||
}
|
||||
|
||||
window.addEventListener("focus", function () {
|
||||
@@ -9650,7 +9649,7 @@ const hn = async (u, e) => {
|
||||
o = !1
|
||||
}
|
||||
return o && i.push(K4(e, "")), i
|
||||
}, Ut = 21116, Lt = "rs-ny.rustdesk.com", tt = 100, w4 = "trust-this-device";
|
||||
}, Ut = 21116, defaultIdServerPort = 21116, Lt = "rs-ny.rustdesk.com", tt = 100, w4 = "trust-this-device";
|
||||
|
||||
class Wt {
|
||||
constructor() {
|
||||
@@ -9887,7 +9886,7 @@ class Wt {
|
||||
(f4 = this._ws) == null || f4.sendMessage({public_key: T});
|
||||
return
|
||||
}
|
||||
const [E, c] = jn(), C = Nn(), D = On(C, l, E), B = K.fromPartial({asymmetric_value: c, symmetric_value: D});
|
||||
const [E, c] = Wn(), C = Vn(), D = qn(C, l, E), B = K.fromPartial({asymmetric_value: c, symmetric_value: D});
|
||||
return (x4 = this._ws) == null || x4.sendMessage({public_key: B}), (Ie = this._ws) == null || Ie.setSecretKey(C), console.log("secured"), !0
|
||||
}
|
||||
|
||||
@@ -9905,7 +9904,7 @@ class Wt {
|
||||
Re(o.colors, !1, a => {
|
||||
a && (o.colors = a, m("cursor_data", o))
|
||||
})
|
||||
} else if (e != null && e.cursor_id) m("cursor_id", {id: e == null ? void 0 : e.cursor_id}); else if (e != null && e.cursor_position) m("cursor_position", e == null ? void 0 : e.cursor_position); else if (e != null && e.misc) this.handleMisc(e == null ? void 0 : e.misc); else if (e != null && e.audio_frame) Vn(e == null ? void 0 : e.audio_frame.data); else if (e != null && e.message_box) this.handleMsgBox(e == null ? void 0 : e.message_box); else if (e != null && e.peer_info) this.handleSyncPeerInfo(e.peer_info); else if (e.file_response) await this.handleFileResponse(e.file_response); else if (e.file_action) {
|
||||
} else if (e != null && e.cursor_id) m("cursor_id", {id: e == null ? void 0 : e.cursor_id}); else if (e != null && e.cursor_position) m("cursor_position", e == null ? void 0 : e.cursor_position); else if (e != null && e.misc) this.handleMisc(e == null ? void 0 : e.misc); else if (e != null && e.audio_frame) Zn(e == null ? void 0 : e.audio_frame.data); else if (e != null && e.message_box) this.handleMsgBox(e == null ? void 0 : e.message_box); else if (e != null && e.peer_info) this.handleSyncPeerInfo(e.peer_info); else if (e.file_response) await this.handleFileResponse(e.file_response); else if (e.file_action) {
|
||||
const o = e.file_action;
|
||||
await this.handleFileAction(o)
|
||||
}
|
||||
@@ -10324,7 +10323,7 @@ class Wt {
|
||||
}
|
||||
|
||||
handleMisc(e) {
|
||||
if (e.audio_format) Wn(e.audio_format.channels, e.audio_format.sample_rate); else if (e.chat_message) m("chat_client_mode", {text: e.chat_message.text}); else if (e.permission_info) {
|
||||
if (e.audio_format) Gn(e.audio_format.channels, e.audio_format.sample_rate); else if (e.chat_message) m("chat_client_mode", {text: e.chat_message.text}); else if (e.permission_info) {
|
||||
const i = e.permission_info;
|
||||
console.info("Change permission " + i.permission + " -> " + i.enabled);
|
||||
let o;
|
||||
@@ -10494,7 +10493,7 @@ class Wt {
|
||||
|
||||
inputKey(e, i, o, a, t, s, l) {
|
||||
var c;
|
||||
const E = So(e, Rn());
|
||||
const E = So(e, Mn());
|
||||
!E || (a && (e == "VK_MENU" || e == "RAlt") && (a = !1), t && (e == "VK_CONTROL" || e == "RControl") && (t = !1), s && (e == "VK_SHIFT" || e == "RShift") && (s = !1), l && (e == "Meta" || e == "RWin") && (l = !1), E.down = i, E.press = o, E.modifiers = this.getMod(a, t, s, l), (c = this._ws) == null || c.sendMessage({key_event: E}))
|
||||
}
|
||||
|
||||
@@ -11088,7 +11087,7 @@ function R4(u = !1) {
|
||||
return I4(e || Lt, u)
|
||||
}
|
||||
|
||||
function getrUriFromRs(uri, isRelay = false, roffset = 0) {
|
||||
function getUriFromRs(uri, isRelay = false, roffset = 0) {
|
||||
const p = isHttps() ? "wss://" : "ws://"
|
||||
const [domain, uriport] = uri.split(":")
|
||||
if (isHttps() && (!uriport)) {
|
||||
@@ -11097,7 +11096,7 @@ function getrUriFromRs(uri, isRelay = false, roffset = 0) {
|
||||
if (uriport) {
|
||||
const port = parseInt(uriport);
|
||||
uri = domain + ":" + (port + (isRelay ? roffset || 3 : 2))
|
||||
} else uri += ":" + (Ut + (isRelay ? 3 : 2));
|
||||
} else uri += ":" + (defaultIdServerPort + (isRelay ? 3 : 2));
|
||||
return p + uri
|
||||
}
|
||||
|
||||
@@ -11106,7 +11105,7 @@ function isHttps() {
|
||||
}
|
||||
|
||||
function I4(u, e = !1, i = 0) {
|
||||
return getrUriFromRs(u, e, i)
|
||||
return getUriFromRs(u, e, i)
|
||||
}
|
||||
|
||||
function wn() {
|
||||
@@ -11117,7 +11116,6 @@ function Sn(u) {
|
||||
return u.indexOf(":") > 0 ? u.split(":")[0] : u
|
||||
}
|
||||
|
||||
|
||||
const at = (u, e, i) => e && u.type == "SharedAb" ? Z(Zu([u.value, i.salt])) === Z(e) : !1,
|
||||
ot = (u, e) => e && u.type == "PersonalAb" ? Z(u.value) === Z(e) : !1;
|
||||
|
||||
@@ -11244,11 +11242,63 @@ async function Pn(u) {
|
||||
|
||||
}
|
||||
|
||||
const Rn = "rustdesk-client";
|
||||
|
||||
function In() {
|
||||
if (typeof navigator != "undefined") {
|
||||
const u = navigator.platform.toLowerCase();
|
||||
return u.includes("win") ? "windows" : u.includes("mac") ? "macos" : u.includes("linux") ? "linux" : u
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
function Tn() {
|
||||
const u = In();
|
||||
return u === "windows" ? navigator.userAgent.includes("Win64") ? "x86_64" : "x86" : u === "macos" ? navigator.userAgent.includes("Intel") ? "x86_64" : "arm64" : navigator.userAgent.includes("x64") ? "x86_64" : "x86"
|
||||
}
|
||||
|
||||
function jn() {
|
||||
const u = navigator.userAgent;
|
||||
let e = "", i = "";
|
||||
if (u.includes("Windows")) {
|
||||
e = "windows";
|
||||
const o = u.match(/Windows NT (\d+\.\d+)/);
|
||||
o && (i = o[1])
|
||||
} else if (u.includes("Mac OS X")) {
|
||||
e = "macos";
|
||||
const o = u.match(/Mac OS X (\d+[._]\d+[._]\d+)/);
|
||||
o && (i = o[1].replace(/_/g, "."))
|
||||
} else if (u.includes("Linux")) {
|
||||
e = "linux";
|
||||
const o = u.match(/Linux\s*([\d.]+)?/);
|
||||
o && o[1] && (i = o[1])
|
||||
} else e = "unknown", i = "";
|
||||
return e += "-" + navigator.userAgent, {os: e, os_version: i}
|
||||
}
|
||||
|
||||
async function Nn(u) {
|
||||
const e = "https://api.rustdesk.com/version/latest", {os: i, os_version: o} = jn(), a = Tn();
|
||||
return [{os: i, os_version: o, arch: a, device_id: [], typ: u}, e]
|
||||
}
|
||||
|
||||
async function On() {
|
||||
try {
|
||||
const [u, e] = await Nn(Rn);
|
||||
return await (await fetch(e, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify(u)
|
||||
})).json()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
window.curConn = void 0;
|
||||
window.isMobile = () => /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0, 4));
|
||||
const ye = zt(), Yu = ye === Y4, H4 = ye === Be, Qu = ye === me;
|
||||
|
||||
function Rn() {
|
||||
function Mn() {
|
||||
return !isMobile()
|
||||
}
|
||||
|
||||
@@ -11267,7 +11317,7 @@ function Vt(u, e, i, o) {
|
||||
}
|
||||
}
|
||||
|
||||
function In(u, e, i) {
|
||||
function Un(u, e, i) {
|
||||
onGlobalEvent(JSON.stringify({name: "toast", type: u, text: e, dur_msec: i}))
|
||||
}
|
||||
|
||||
@@ -11340,20 +11390,20 @@ function Zt(u) {
|
||||
return q.from_base64(u, q.base64_variants.ORIGINAL)
|
||||
}
|
||||
|
||||
function Tn(u) {
|
||||
function Ln(u) {
|
||||
return q.to_base64(u, q.base64_variants.ORIGINAL)
|
||||
}
|
||||
|
||||
function jn() {
|
||||
function Wn() {
|
||||
const u = q.crypto_box_keypair(), e = u.privateKey, i = u.publicKey;
|
||||
return [e, i]
|
||||
}
|
||||
|
||||
function Nn() {
|
||||
function Vn() {
|
||||
return q.crypto_secretbox_keygen()
|
||||
}
|
||||
|
||||
function On(u, e, i) {
|
||||
function qn(u, e, i) {
|
||||
const o = Uint8Array.from(Array(24).fill(0));
|
||||
return q.crypto_box_easy(u, o, e, i)
|
||||
}
|
||||
@@ -11370,7 +11420,7 @@ function st(u, e, i) {
|
||||
return q.crypto_secretbox_easy(u, $4(e), i)
|
||||
}
|
||||
|
||||
function Mn(u, e, i) {
|
||||
function Kn(u, e, i) {
|
||||
return q.crypto_secretbox_open_easy(u, $4(e), i)
|
||||
}
|
||||
|
||||
@@ -11428,7 +11478,7 @@ window.setByName = (u, e) => {
|
||||
e = JSON.parse(e), Ho(curConn, e.usb_hid, e.down == "true", e.lock_modes);
|
||||
break;
|
||||
case"send_mouse":
|
||||
Un(e);
|
||||
Hn(e);
|
||||
break;
|
||||
case"send_2fa":
|
||||
curConn == null || curConn.send2fa(e);
|
||||
@@ -11455,7 +11505,7 @@ window.setByName = (u, e) => {
|
||||
e = JSON.parse(e), curConn.setFlutterUiOption(e.name, e.value);
|
||||
break;
|
||||
case"option:user:default":
|
||||
Gn(e);
|
||||
ur(e);
|
||||
break;
|
||||
case"option:session":
|
||||
e = JSON.parse(e), curConn.setOption(e.name, e.value);
|
||||
@@ -11473,12 +11523,12 @@ window.setByName = (u, e) => {
|
||||
curConn.inputOsPassword(e);
|
||||
break;
|
||||
case"session_add_sync":
|
||||
return Xn(e);
|
||||
return tr(e);
|
||||
case"session_start":
|
||||
Yn();
|
||||
ar();
|
||||
break;
|
||||
case"session_close":
|
||||
$n();
|
||||
or();
|
||||
break;
|
||||
case"elevate_direct":
|
||||
curConn.elevateDirect();
|
||||
@@ -11500,13 +11550,13 @@ window.setByName = (u, e) => {
|
||||
curConn.changePreferCodec(e);
|
||||
break;
|
||||
case"cursor":
|
||||
Hn(e);
|
||||
Yn(e);
|
||||
break;
|
||||
case"enter_or_leave":
|
||||
curConn == null || curConn.enterOrLeave(e);
|
||||
break;
|
||||
case"fullscreen":
|
||||
e == "Y" ? er() : ir();
|
||||
e == "Y" ? rr() : sr();
|
||||
break;
|
||||
case"send_note":
|
||||
const i = Yt("conn");
|
||||
@@ -11549,7 +11599,7 @@ window.setByName = (u, e) => {
|
||||
curConn == null || curConn.sendChat(e);
|
||||
break;
|
||||
case"load_ab":
|
||||
or();
|
||||
dr();
|
||||
break;
|
||||
case"save_ab":
|
||||
_o(e);
|
||||
@@ -11558,7 +11608,7 @@ window.setByName = (u, e) => {
|
||||
vo();
|
||||
break;
|
||||
case"load_group":
|
||||
nr();
|
||||
cr();
|
||||
break;
|
||||
case"save_group":
|
||||
ko(e);
|
||||
@@ -11575,7 +11625,7 @@ window.setByName = (u, e) => {
|
||||
}
|
||||
};
|
||||
|
||||
function Un(u) {
|
||||
function Hn(u) {
|
||||
if (!curConn) return;
|
||||
let e = 0;
|
||||
switch (u = JSON.parse(u), u.type) {
|
||||
@@ -11618,11 +11668,11 @@ function Un(u) {
|
||||
}
|
||||
|
||||
window.getByName = (u, e) => {
|
||||
let i = Ln(u, e);
|
||||
let i = Jn(u, e);
|
||||
return typeof i == "string" || i instanceof String ? i : i == null || i == null ? "" : JSON.stringify(i)
|
||||
};
|
||||
|
||||
function Ln(u, e) {
|
||||
function Jn(u, e) {
|
||||
var o, a, t, s;
|
||||
switch (u) {
|
||||
case"remember":
|
||||
@@ -11669,10 +11719,10 @@ function Ln(u, e) {
|
||||
case"version":
|
||||
return se;
|
||||
case"load_recent_peers":
|
||||
Zn();
|
||||
er();
|
||||
break;
|
||||
case"load_fav_peers":
|
||||
Qn();
|
||||
ir();
|
||||
break;
|
||||
case"fav":
|
||||
return (a = A.getItem("fav")) != null ? a : "[]";
|
||||
@@ -11726,7 +11776,7 @@ function Ln(u, e) {
|
||||
case"peer_has_password":
|
||||
return ((t = (Cu()[e] || {}).password) != null ? t : "") !== "";
|
||||
case"fullscreen":
|
||||
return tr() ? "Y" : "N";
|
||||
return lr() ? "Y" : "N";
|
||||
case"platform":
|
||||
return curConn.getPlatform();
|
||||
case"enable_trusted_devices":
|
||||
@@ -11737,11 +11787,11 @@ function Ln(u, e) {
|
||||
|
||||
let ze = new Worker("./libopus.js?v=02816afa"), Qt;
|
||||
|
||||
function Wn(u, e) {
|
||||
Qt = qn(u, e), ze.postMessage({channels: u, sampleRate: e})
|
||||
function Gn(u, e) {
|
||||
Qt = Qn(u, e), ze.postMessage({channels: u, sampleRate: e})
|
||||
}
|
||||
|
||||
function Vn(u) {
|
||||
function Zn(u) {
|
||||
ze.postMessage(u, [u.buffer])
|
||||
}
|
||||
|
||||
@@ -11749,7 +11799,7 @@ window.init = async () => {
|
||||
try {
|
||||
ze.onmessage = u => {
|
||||
Qt.feed(u.data)
|
||||
}, await Jt(), await zo(), await Pa(), await N.init(), console.log("init done"), onInitFinished()
|
||||
}, await Jt(), await zo(), await Pa(), await N.init(), console.log("init done"), onInitFinished(), await On()
|
||||
} catch (u) {
|
||||
console.error("Failed to init: " + u.message), onInitFinished()
|
||||
}
|
||||
@@ -11758,11 +11808,11 @@ window.onunload = () => {
|
||||
console.log("window close"), Ia()
|
||||
};
|
||||
|
||||
function qn(u, e) {
|
||||
function Qn(u, e) {
|
||||
return new ra({channels: u, sampleRate: e, flushingTime: 2e3})
|
||||
}
|
||||
|
||||
function Kn(u) {
|
||||
function Xn(u) {
|
||||
if (window.clipboardData && window.clipboardData.setData) return window.clipboardData.setData("Text", u);
|
||||
if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
|
||||
var e = document.createElement("textarea");
|
||||
@@ -11791,7 +11841,7 @@ function Q(u) {
|
||||
}
|
||||
}
|
||||
|
||||
function Hn(u) {
|
||||
function Yn(u) {
|
||||
let e = "auto";
|
||||
if (u != "auto") try {
|
||||
const t = JSON.parse(u);
|
||||
@@ -11807,13 +11857,13 @@ function Hn(u) {
|
||||
}
|
||||
}
|
||||
|
||||
async function Jn() {
|
||||
async function $n() {
|
||||
await T4.ready;
|
||||
const u = T4.crypto_sign_keypair();
|
||||
return {publicKey: u.publicKey, privateKey: u.privateKey}
|
||||
}
|
||||
|
||||
function Gn(u) {
|
||||
function ur(u) {
|
||||
try {
|
||||
const e = JSON.parse(u), i = JSON.parse(A.getItem("user-default-options")) || {};
|
||||
i[e.name] = e.value, A.setItem("user-default-options", JSON.stringify(i))
|
||||
@@ -11855,22 +11905,23 @@ function Pe() {
|
||||
return u.sort().reverse().map(e => e[2])
|
||||
}
|
||||
|
||||
function Zn() {
|
||||
function er() {
|
||||
const u = Pe();
|
||||
u && be("load_recent_peers", {peers: JSON.stringify(u)})
|
||||
}
|
||||
|
||||
function Qn() {
|
||||
function ir() {
|
||||
var u;
|
||||
try {
|
||||
const e = (u = A.getItem("fav")) != null ? u : "[]", i = JSON.parse(e), o = Pe().filter(a => i.includes(a.id));
|
||||
const e = (u = A.getItem("fav")) != null ? u : "[]", i = JSON.parse(e),
|
||||
o = Pe().filter(a => i.includes(a.id));
|
||||
o && be("load_fav_peers", {peers: JSON.stringify(o)})
|
||||
} catch (e) {
|
||||
console.error("Failed to load fav peers: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
function Xn(u) {
|
||||
function tr(u) {
|
||||
var e;
|
||||
try {
|
||||
const i = JSON.parse(u), o = i.id;
|
||||
@@ -11884,7 +11935,7 @@ function Xn(u) {
|
||||
}
|
||||
}
|
||||
|
||||
function Yn(u) {
|
||||
function ar(u) {
|
||||
try {
|
||||
if (!e0()) return;
|
||||
Kt()
|
||||
@@ -11893,11 +11944,11 @@ function Yn(u) {
|
||||
}
|
||||
}
|
||||
|
||||
function $n(u) {
|
||||
function or(u) {
|
||||
Se()
|
||||
}
|
||||
|
||||
function ur(u, e) {
|
||||
function nr(u, e) {
|
||||
function i(o) {
|
||||
return /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(o)
|
||||
}
|
||||
@@ -11925,7 +11976,7 @@ function Xt() {
|
||||
if (u) return u;
|
||||
const e = A.getItem("custom-rendezvous-server");
|
||||
if (e) {
|
||||
let i = ur(e, -2);
|
||||
let i = nr(e, -2);
|
||||
return i == e ? `http://${i}:${Ut - 2}` : `http://${i}`
|
||||
}
|
||||
return "https://admin.rustdesk.com"
|
||||
@@ -11985,28 +12036,28 @@ async function ea(u, e) {
|
||||
})
|
||||
}
|
||||
|
||||
function er() {
|
||||
function rr() {
|
||||
const u = document.documentElement;
|
||||
u.requestFullscreen ? u.requestFullscreen() : u.mozRequestFullScreen ? u.mozRequestFullScreen() : u.webkitRequestFullscreen ? u.webkitRequestFullscreen() : u.msRequestFullscreen && u.msRequestFullscreen()
|
||||
}
|
||||
|
||||
function ir() {
|
||||
function sr() {
|
||||
document.exitFullscreen ? document.exitFullscreen() : document.mozCancelFullScreen ? document.mozCancelFullScreen() : document.webkitExitFullscreen ? document.webkitExitFullscreen() : document.msExitFullscreen && document.msExitFullscreen()
|
||||
}
|
||||
|
||||
function tr() {
|
||||
function lr() {
|
||||
return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement
|
||||
}
|
||||
|
||||
var lt = !1;
|
||||
|
||||
function ar() {
|
||||
function Er() {
|
||||
lt || (console.log("listen fullscreen"), lt = !0, document.addEventListener("fullscreenchange", () => onFullscreenChanged(!!document.fullscreenElement)), document.addEventListener("mozfullscreenchange", () => onFullscreenChanged(!!document.mozFullScreen)), document.addEventListener("webkitfullscreenchange", () => onFullscreenChanged(!!document.webkitFullscreenElement)), document.addEventListener("msfullscreenchange", () => onFullscreenChanged(!!document.msFullscreenElement)))
|
||||
}
|
||||
|
||||
ar();
|
||||
Er();
|
||||
|
||||
async function or() {
|
||||
async function dr() {
|
||||
try {
|
||||
let u = await xt();
|
||||
onLoadAbFinished(JSON.stringify(u))
|
||||
@@ -12015,7 +12066,7 @@ async function or() {
|
||||
}
|
||||
}
|
||||
|
||||
async function nr() {
|
||||
async function cr() {
|
||||
try {
|
||||
let u = await go();
|
||||
onLoadGroupFinished(JSON.stringify(u))
|
||||
|
||||
36
resources/web2/js/dist/lang.js
vendored
36
resources/web2/js/dist/lang.js
vendored
@@ -4685,7 +4685,7 @@ Kui soovid juurdep\xE4\xE4su seadmele avalikus serveris, sisesta "<id>@public",
|
||||
}, sl: {
|
||||
Status: "Stanje",
|
||||
"Your Desktop": "Va\u0161e namizje",
|
||||
desk_tip: "Do va\u0161ega namizja lahko dostopate s spodnjim IDjem in geslom",
|
||||
desk_tip: "S spodnjim IDjem in geslom omogo\u010Dite oddaljeni nadzor va\u0161ega ra\u010Dunalnika",
|
||||
Password: "Geslo",
|
||||
Ready: "Pripravljen",
|
||||
Established: "Povezava vzpostavljena",
|
||||
@@ -4872,7 +4872,7 @@ Kui soovid juurdep\xE4\xE4su seadmele avalikus serveris, sisesta "<id>@public",
|
||||
"Logging in...": "Prijavljanje...",
|
||||
"Enable RDP session sharing": "Omogo\u010Di deljenje RDP seje",
|
||||
"Auto Login": "Samodejna prijava",
|
||||
"Enable direct IP access": "Omogo\u010Di neposredni dostop preko IP",
|
||||
"Enable direct IP access": "Omogo\u010Di neposredni dostop preko IP naslova",
|
||||
Rename: "Preimenuj",
|
||||
Space: "Prazno",
|
||||
"Create desktop shortcut": "Ustvari bli\u017Enjico na namizju",
|
||||
@@ -5046,7 +5046,7 @@ Kui soovid juurdep\xE4\xE4su seadmele avalikus serveris, sisesta "<id>@public",
|
||||
Recording: "Snemanje",
|
||||
Directory: "Imenik",
|
||||
"Automatically record incoming sessions": "Samodejno snemaj vhodne seje",
|
||||
"Automatically record outgoing sessions": "",
|
||||
"Automatically record outgoing sessions": "Samodejno snemaj odhodne seje",
|
||||
Change: "Spremeni",
|
||||
"Start session recording": "Za\u010Dni snemanje seje",
|
||||
"Stop session recording": "Ustavi snemanje seje",
|
||||
@@ -5094,8 +5094,8 @@ Kui soovid juurdep\xE4\xE4su seadmele avalikus serveris, sisesta "<id>@public",
|
||||
"Select local keyboard type": "Izberite lokalno vrsto tipkovnice",
|
||||
software_render_tip: "\u010Ce na Linuxu uporabljate Nvidino grafi\u010Dno kartico in se oddaljeno okno zapre takoj po vzpostavitvi povezave, lahko pomaga preklop na odprtokodni gonilnik Nouveau in uporaba programskega upodabljanja. Potreben je ponovni zagon programa.",
|
||||
"Always use software rendering": "Vedno uporabi programsko upodabljanje",
|
||||
config_input: "Za nadzor oddaljenega namizja s tipkovnico, rabi RustDesk pravico \xBBNadzor vnosa\xAB.",
|
||||
config_microphone: "Za zajem zvoka, rabi RustDesk pravico \xBBSnemanje zvoka\xAB.",
|
||||
config_input: "RustDesk potrebuje pravico \xBBNadzor vnosa\xAB za nadzor oddaljenega namizja s tipkovnico.",
|
||||
config_microphone: "RustDesk potrebuje pravico \xBBSnemanje zvoka\xAB za zajemanje zvoka.",
|
||||
request_elevation_tip: "Lahko tudi zaprosite za dvig pravic, \u010De je kdo na oddaljeni strani.",
|
||||
Wait: "\u010Cakaj",
|
||||
"Elevation Error": "Napaka pri povzdigovanju",
|
||||
@@ -5128,7 +5128,7 @@ Kui soovid juurdep\xE4\xE4su seadmele avalikus serveris, sisesta "<id>@public",
|
||||
"Voice call": "Glasovni klic",
|
||||
"Text chat": "Besedilni klepet",
|
||||
"Stop voice call": "Prekini glasovni klic",
|
||||
relay_hint_tip: "Morda neposredna povezava ni mo\u017Ena; lahko se poikusite povezati preko posrednika. \u010Ce \u017Eelite uporabiti posrednika ob prvem poizkusu vzpotavljanja povezave, lahko na konec IDja dodate \xBB/r\xAB, ali pa izberete mo\u017Enost \xBBVedno pove\u017Ei preko posrednika\xAB v kartici nedavnih sej, \u010De le-ta obstja.",
|
||||
relay_hint_tip: "Morda neposredna povezava ni mo\u017Ena; lahko se poizkusite povezati preko posrednika. \u010Ce \u017Eelite uporabiti posrednika ob prvem poizkusu vzpotavljanja povezave, lahko na konec IDja dodate \xBB/r\xAB, ali pa izberete mo\u017Enost \xBBVedno pove\u017Ei preko posrednika\xAB v kartici nedavnih sej, \u010De le-ta obstja.",
|
||||
Reconnect: "Ponovna povezava",
|
||||
Codec: "Kodek",
|
||||
Resolution: "Lo\u010Dljivost",
|
||||
@@ -5343,13 +5343,13 @@ Lahko se pove\u017Eete na druge naprave, druge naprave pa se k vam ne morejo pov
|
||||
web_id_input_tip: `Vnesete lahko ID iz istega stre\u017Enika, neposredni dostop preko IP naslova v spletnem odjemalcu ni podprt.
|
||||
\u010Ce \u017Eelite dostopati do naprave na drugem stre\u017Eniku, pripnite naslov stre\u017Enika (<id>@<naslov_stre\u017Enika>?key=<klju\u010D>), npr. 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.
|
||||
\u010Ce \u017Eelite dostopati do naprave na javnem stre\u017Eniku, vnesite \xBB<id>@public\xAB; klju\u010D za javni stre\u017Enik ni potreben.`,
|
||||
Download: "",
|
||||
"Upload folder": "",
|
||||
"Upload files": "",
|
||||
"Clipboard is synchronized": "",
|
||||
"Update client clipboard": "",
|
||||
Untagged: "",
|
||||
"new-version-of-{}-tip": ""
|
||||
Download: "Prenos",
|
||||
"Upload folder": "Nalo\u017Ei mapo",
|
||||
"Upload files": "Nalo\u017Ei datoteke",
|
||||
"Clipboard is synchronized": "Odlo\u017Ei\u0161\u010De je usklajeno",
|
||||
"Update client clipboard": "Osve\u017Ei odjemal\u010Devo odlo\u017Ei\u0161\u010De",
|
||||
Untagged: "Neozna\u010Deno",
|
||||
"new-version-of-{}-tip": "Na voljo je nova razli\u010Dica {}"
|
||||
}, ko: {
|
||||
Status: "\uC0C1\uD0DC",
|
||||
"Your Desktop": "\uB0B4 \uB370\uC2A4\uD06C\uD0D1",
|
||||
@@ -6683,7 +6683,7 @@ Ja v\u0113laties piek\u013C\u016Bt ier\u012Bcei publiskaj\u0101 server\u012B, l\
|
||||
"Clipboard is synchronized": "Starpliktuve ir sinhroniz\u0113ta",
|
||||
"Update client clipboard": "Atjaunin\u0101t klienta starpliktuvi",
|
||||
Untagged: "Neatz\u012Bm\u0113ts",
|
||||
"new-version-of-{}-tip": ""
|
||||
"new-version-of-{}-tip": "Ir pieejama jauna {} versija"
|
||||
}, pl: {
|
||||
Status: "Status",
|
||||
"Your Desktop": "Tw\xF3j pulpit",
|
||||
@@ -18966,7 +18966,7 @@ H\xE3y t\xECm ai \u0111\xF3 \u0111\u1EC3 k\u1EBFt n\u1ED1i c\xF9ng v\xE0 th\xEAm
|
||||
Dark: "\u9ED1\u6697",
|
||||
Light: "\u660E\u4EAE",
|
||||
"Follow System": "\u8DDF\u968F\u7CFB\u7EDF",
|
||||
"Enable hardware codec": "\u4F7F\u80FD\u786C\u4EF6\u7F16\u89E3\u7801",
|
||||
"Enable hardware codec": "\u542F\u7528\u786C\u4EF6\u7F16\u89E3\u7801",
|
||||
"Unlock Security Settings": "\u89E3\u9501\u5B89\u5168\u8BBE\u7F6E",
|
||||
"Enable audio": "\u5141\u8BB8\u4F20\u8F93\u97F3\u9891",
|
||||
"Unlock Network Settings": "\u89E3\u9501\u7F51\u7EDC\u8BBE\u7F6E",
|
||||
@@ -21967,8 +21967,8 @@ Si quieres accedder a un dispositivo en un servidor p\xFAblico, por favor, intro
|
||||
"Upload files": "Subir archivos",
|
||||
"Clipboard is synchronized": "Portapapeles sincronizado",
|
||||
"Update client clipboard": "Actualizar portapapeles del cliente",
|
||||
Untagged: "",
|
||||
"new-version-of-{}-tip": ""
|
||||
Untagged: "Sin itiquetar",
|
||||
"new-version-of-{}-tip": "Hay una nueva versi\xF3n de {} disponible"
|
||||
}, sr: {
|
||||
Status: "Status",
|
||||
"Your Desktop": "Va\u0161a radna povr\u0161ina",
|
||||
@@ -23662,7 +23662,7 @@ Ha egy nyilv\xE1nos kiszolg\xE1l\xF3n l\xE9v\u0151 eszk\xF6zh\xF6z szeretne hozz
|
||||
Recording: "\u9304\u88FD",
|
||||
Directory: "\u8DEF\u5F91",
|
||||
"Automatically record incoming sessions": "\u81EA\u52D5\u9304\u88FD\u9023\u5165\u7684\u5DE5\u4F5C\u968E\u6BB5",
|
||||
"Automatically record outgoing sessions": "",
|
||||
"Automatically record outgoing sessions": "\u81EA\u52D5\u9304\u88FD\u9023\u51FA\u7684\u5DE5\u4F5C\u968E\u6BB5",
|
||||
Change: "\u8B8A\u66F4",
|
||||
"Start session recording": "\u958B\u59CB\u9304\u5F71",
|
||||
"Stop session recording": "\u505C\u6B62\u9304\u5F71",
|
||||
|
||||
14212
resources/web2/main.dart.js
vendored
14212
resources/web2/main.dart.js
vendored
File diff suppressed because one or more lines are too long
425
service/ldap.go
Normal file
425
service/ldap.go
Normal file
@@ -0,0 +1,425 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"Gwen/config"
|
||||
"Gwen/global"
|
||||
"Gwen/model"
|
||||
)
|
||||
|
||||
// LdapService is responsible for LDAP authentication and user synchronization.
|
||||
type LdapService struct {
|
||||
}
|
||||
|
||||
// LdapUser represents the user attributes retrieved from LDAP.
|
||||
type LdapUser struct {
|
||||
Dn string
|
||||
Username string
|
||||
Email string
|
||||
FirstName string
|
||||
LastName string
|
||||
MemberOf []string
|
||||
EnableAttrValue string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// Name returns the full name of an LDAP user.
|
||||
func (lu *LdapUser) Name() string {
|
||||
return fmt.Sprintf("%s %s", lu.FirstName, lu.LastName)
|
||||
}
|
||||
|
||||
// ToUser merges the LdapUser data into a provided *model.User.
|
||||
// If 'u' is nil, it creates and returns a new *model.User.
|
||||
func (lu *LdapUser) ToUser(u *model.User) *model.User {
|
||||
if u == nil {
|
||||
u = &model.User{}
|
||||
}
|
||||
u.Username = lu.Username
|
||||
u.Email = lu.Email
|
||||
u.Nickname = lu.Name()
|
||||
return u
|
||||
}
|
||||
|
||||
// connectAndBind creates an LDAP connection, optionally starts TLS, and then binds using the provided credentials.
|
||||
func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) {
|
||||
conn, err := ldap.DialURL(cfg.Url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial LDAP: %w", err)
|
||||
}
|
||||
|
||||
if cfg.TLS {
|
||||
// WARNING: InsecureSkipVerify: true is not recommended for production
|
||||
if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to start TLS: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Bind as the "service" user
|
||||
if err = conn.Bind(username, password); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to bind with service account: %w", err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// connectAndBindAdmin creates an LDAP connection, optionally starts TLS, and then binds using the admin credentials.
|
||||
func (ls *LdapService) connectAndBindAdmin(cfg *config.Ldap) (*ldap.Conn, error) {
|
||||
return ls.connectAndBind(cfg, cfg.BindDn, cfg.BindPassword)
|
||||
}
|
||||
|
||||
// verifyCredentials checks the provided username and password against LDAP.
|
||||
func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error {
|
||||
ldapConn, err := ls.connectAndBind(cfg, username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ldapConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate checks the provided username and password against LDAP.
|
||||
// Returns the corresponding *model.User if successful, or an error if not.
|
||||
func (ls *LdapService) Authenticate(username, password string) (*model.User, error) {
|
||||
cfg := &global.Config.Ldap
|
||||
// 1. Use a service bind to search for the user DN
|
||||
sr, err := ls.usernameSearchResult(cfg, username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LDAP search request failed: %w", err)
|
||||
}
|
||||
if len(sr.Entries) != 1 {
|
||||
return nil, errors.New("user does not exist or too many entries returned")
|
||||
}
|
||||
entry := sr.Entries[0]
|
||||
userDN := entry.DN
|
||||
|
||||
err = ls.verifyCredentials(cfg, userDN, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LDAP authentication failed: %w", err)
|
||||
}
|
||||
ldapUser := ls.userResultToLdapUser(cfg, entry)
|
||||
if !ldapUser.Enabled {
|
||||
return nil, errors.New("UserDisabledAtLdap")
|
||||
}
|
||||
user, err := ls.mapToLocalUser(cfg, ldapUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to map LDAP user to local user: %w", err)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// mapToLocalUser checks whether the user exists locally; if not, creates one.
|
||||
// If the user exists and Ldap.Sync is enabled, it updates local info.
|
||||
func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.User, error) {
|
||||
userService := &UserService{}
|
||||
localUser := userService.InfoByUsername(lu.Username)
|
||||
isAdmin := ls.isUserAdmin(cfg, lu)
|
||||
// If the user doesn't exist in local DB, create a new one
|
||||
if localUser.Id == 0 {
|
||||
newUser := lu.ToUser(nil)
|
||||
// Typically, you don’t store LDAP user passwords locally.
|
||||
// If needed, you can set a random password here.
|
||||
newUser.IsAdmin = &isAdmin
|
||||
if err := global.DB.Create(newUser).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to create new user: %w", err)
|
||||
}
|
||||
return userService.InfoByUsername(lu.Username), nil
|
||||
}
|
||||
|
||||
// If the user already exists and sync is enabled, update local info
|
||||
if cfg.User.Sync {
|
||||
originalEmail := localUser.Email
|
||||
originalNickname := localUser.Nickname
|
||||
originalIsAdmin := localUser.IsAdmin
|
||||
lu.ToUser(localUser) // merges LDAP data into the existing user
|
||||
localUser.IsAdmin = &isAdmin
|
||||
if err := userService.Update(localUser); err != nil {
|
||||
// If the update fails, revert to original data
|
||||
localUser.Email = originalEmail
|
||||
localUser.Nickname = originalNickname
|
||||
localUser.IsAdmin = originalIsAdmin
|
||||
}
|
||||
}
|
||||
|
||||
return localUser, nil
|
||||
}
|
||||
|
||||
// IsUsernameExists checks if a username exists in LDAP (can be useful for local registration checks).
|
||||
func (ls *LdapService) IsUsernameExists(username string) bool {
|
||||
|
||||
cfg := &global.Config.Ldap
|
||||
if !cfg.Enable {
|
||||
return false
|
||||
}
|
||||
sr, err := ls.usernameSearchResult(cfg, username)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(sr.Entries) > 0
|
||||
}
|
||||
|
||||
// IsEmailExists checks if an email exists in LDAP (can be useful for local registration checks).
|
||||
func (ls *LdapService) IsEmailExists(email string) bool {
|
||||
cfg := &global.Config.Ldap
|
||||
if !cfg.Enable {
|
||||
return false
|
||||
}
|
||||
sr, err := ls.emailSearchResult(cfg, email)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(sr.Entries) > 0
|
||||
}
|
||||
|
||||
// usernameSearchResult returns the search result for the given username.
|
||||
func (ls *LdapService) usernameSearchResult(cfg *config.Ldap, username string) (*ldap.SearchResult, error) {
|
||||
// Build the combined filter for the username
|
||||
filter := ls.filterField(ls.fieldUsername(cfg), username)
|
||||
// Create the *ldap.SearchRequest
|
||||
searchRequest := ls.buildUserSearchRequest(cfg, filter)
|
||||
return ls.searchResult(cfg, searchRequest)
|
||||
}
|
||||
|
||||
// emailSearchResult returns the search result for the given email.
|
||||
func (ls *LdapService) emailSearchResult(cfg *config.Ldap, email string) (*ldap.SearchResult, error) {
|
||||
filter := ls.filterField(ls.fieldEmail(cfg), email)
|
||||
searchRequest := ls.buildUserSearchRequest(cfg, filter)
|
||||
return ls.searchResult(cfg, searchRequest)
|
||||
}
|
||||
|
||||
func (ls *LdapService) searchResult(cfg *config.Ldap, searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||
ldapConn, err := ls.connectAndBindAdmin(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ldapConn.Close()
|
||||
return ldapConn.Search(searchRequest)
|
||||
}
|
||||
|
||||
// buildUserSearchRequest constructs an LDAP SearchRequest for users given a filter.
|
||||
func (ls *LdapService) buildUserSearchRequest(cfg *config.Ldap, filter string) *ldap.SearchRequest {
|
||||
baseDn := ls.baseDnUser(cfg) // user-specific base DN, or fallback
|
||||
filterConfig := cfg.User.Filter
|
||||
if filterConfig == "" {
|
||||
filterConfig = "(cn=*)"
|
||||
}
|
||||
|
||||
// Combine the default filter with our field filter, e.g. (&(cn=*)(uid=jdoe))
|
||||
combinedFilter := fmt.Sprintf("(&%s%s)", filterConfig, filter)
|
||||
|
||||
attributes := ls.buildUserAttributes(cfg)
|
||||
|
||||
return ldap.NewSearchRequest(
|
||||
baseDn,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0, // unlimited search results
|
||||
0, // no server-side time limit
|
||||
false, // typesOnly
|
||||
combinedFilter,
|
||||
attributes,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// buildUserAttributes returns the list of attributes we want from LDAP user searches.
|
||||
func (ls *LdapService) buildUserAttributes(cfg *config.Ldap) []string {
|
||||
return []string{
|
||||
"dn",
|
||||
ls.fieldUsername(cfg),
|
||||
ls.fieldEmail(cfg),
|
||||
ls.fieldFirstName(cfg),
|
||||
ls.fieldLastName(cfg),
|
||||
ls.fieldMemberOf(),
|
||||
ls.fieldUserEnableAttr(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
// userResultToLdapUser maps an *ldap.Entry to our LdapUser struct.
|
||||
func (ls *LdapService) userResultToLdapUser(cfg *config.Ldap, entry *ldap.Entry) *LdapUser {
|
||||
lu := &LdapUser{
|
||||
Dn: entry.DN,
|
||||
Username: entry.GetAttributeValue(ls.fieldUsername(cfg)),
|
||||
Email: entry.GetAttributeValue(ls.fieldEmail(cfg)),
|
||||
FirstName: entry.GetAttributeValue(ls.fieldFirstName(cfg)),
|
||||
LastName: entry.GetAttributeValue(ls.fieldLastName(cfg)),
|
||||
MemberOf: entry.GetAttributeValues(ls.fieldMemberOf()),
|
||||
EnableAttrValue: entry.GetAttributeValue(ls.fieldUserEnableAttr(cfg)),
|
||||
}
|
||||
// Check if the user is enabled based on the LDAP configuration
|
||||
ls.isUserEnabled(cfg, lu)
|
||||
return lu
|
||||
}
|
||||
|
||||
// filterField helps build simple attribute filters, e.g. (uid=username).
|
||||
func (ls *LdapService) filterField(field, value string) string {
|
||||
return fmt.Sprintf("(%s=%s)", field, value)
|
||||
}
|
||||
|
||||
// fieldUsername returns the configured username attribute or "uid" if not set.
|
||||
func (ls *LdapService) fieldUsername(cfg *config.Ldap) string {
|
||||
if cfg.User.Username == "" {
|
||||
return "uid"
|
||||
}
|
||||
return cfg.User.Username
|
||||
}
|
||||
|
||||
// fieldEmail returns the configured email attribute or "mail" if not set.
|
||||
func (ls *LdapService) fieldEmail(cfg *config.Ldap) string {
|
||||
if cfg.User.Email == "" {
|
||||
return "mail"
|
||||
}
|
||||
return cfg.User.Email
|
||||
}
|
||||
|
||||
// fieldFirstName returns the configured first name attribute or "givenName" if not set.
|
||||
func (ls *LdapService) fieldFirstName(cfg *config.Ldap) string {
|
||||
if cfg.User.FirstName == "" {
|
||||
return "givenName"
|
||||
}
|
||||
return cfg.User.FirstName
|
||||
}
|
||||
|
||||
// fieldLastName returns the configured last name attribute or "sn" if not set.
|
||||
func (ls *LdapService) fieldLastName(cfg *config.Ldap) string {
|
||||
if cfg.User.LastName == "" {
|
||||
return "sn"
|
||||
}
|
||||
return cfg.User.LastName
|
||||
}
|
||||
|
||||
func (ls *LdapService) fieldMemberOf() string {
|
||||
return "memberOf"
|
||||
}
|
||||
|
||||
func (ls *LdapService) fieldUserEnableAttr(cfg *config.Ldap) string {
|
||||
if cfg.User.EnableAttr == "" {
|
||||
return "userAccountControl"
|
||||
}
|
||||
return cfg.User.EnableAttr
|
||||
}
|
||||
|
||||
// baseDnUser returns the user-specific base DN or the global base DN if none is set.
|
||||
func (ls *LdapService) baseDnUser(cfg *config.Ldap) string {
|
||||
if cfg.User.BaseDn == "" {
|
||||
return cfg.BaseDn
|
||||
}
|
||||
return cfg.User.BaseDn
|
||||
}
|
||||
|
||||
// isUserAdmin checks if the user is a member of the admin group.
|
||||
func (ls *LdapService) isUserAdmin(cfg *config.Ldap, ldapUser *LdapUser) bool {
|
||||
// Check if the admin group is configured
|
||||
adminGroup := cfg.User.AdminGroup
|
||||
if adminGroup == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check "memberOf" directly
|
||||
if len(ldapUser.MemberOf) > 0 {
|
||||
for _, group := range ldapUser.MemberOf {
|
||||
if group == adminGroup {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// For "member" attribute, perform a reverse search on the group
|
||||
member := "member"
|
||||
userDN := ldap.EscapeFilter(ldapUser.Dn)
|
||||
adminGroupDn := ldap.EscapeFilter(adminGroup)
|
||||
groupFilter := fmt.Sprintf("(%s=%s)", member, userDN)
|
||||
|
||||
// Create the LDAP search request
|
||||
groupSearchRequest := ldap.NewSearchRequest(
|
||||
adminGroupDn,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0, // Unlimited search results
|
||||
0, // No time limit
|
||||
false, // Return both attributes and DN
|
||||
groupFilter,
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Perform the group search
|
||||
groupResult, err := ls.searchResult(cfg, groupSearchRequest)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If any results are returned, the user is part of the admin group
|
||||
if len(groupResult.Entries) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
// isUserEnabled checks if the user is enabled based on the LDAP configuration.
|
||||
// If no enable attribute or value is set, all users are considered enabled by default.
|
||||
func (ls *LdapService) isUserEnabled(cfg *config.Ldap, ldapUser *LdapUser) bool {
|
||||
// Retrieve the enable attribute and expected value from the configuration
|
||||
enableAttr := cfg.User.EnableAttr
|
||||
enableAttrValue := cfg.User.EnableAttrValue
|
||||
|
||||
// If no enable attribute or value is configured, consider all users as enabled
|
||||
if enableAttr == "" || enableAttrValue == "" {
|
||||
ldapUser.Enabled = true
|
||||
return true
|
||||
}
|
||||
|
||||
// Normalize the enable attribute for comparison
|
||||
enableAttr = strings.ToLower(enableAttr)
|
||||
|
||||
// Handle Active Directory's userAccountControl attribute
|
||||
if enableAttr == "useraccountcontrol" {
|
||||
// Parse the userAccountControl value
|
||||
userAccountControl, err := strconv.Atoi(ldapUser.EnableAttrValue)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] Invalid userAccountControl value: %v\n", err)
|
||||
ldapUser.Enabled = false
|
||||
return false
|
||||
}
|
||||
|
||||
// Account is disabled if the ACCOUNTDISABLE flag (0x2) is set
|
||||
const ACCOUNTDISABLE = 0x2
|
||||
ldapUser.Enabled = (userAccountControl&ACCOUNTDISABLE == 0)
|
||||
return ldapUser.Enabled
|
||||
}
|
||||
|
||||
// For other attributes, perform a direct comparison with the expected value
|
||||
ldapUser.Enabled = (ldapUser.EnableAttrValue == enableAttrValue)
|
||||
return ldapUser.Enabled
|
||||
}
|
||||
|
||||
// getAttrOfDn retrieves the value of an attribute for a given DN.
|
||||
func (ls *LdapService) getAttrOfDn(cfg *config.Ldap, dn, attr string) string {
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
ldap.EscapeFilter(dn),
|
||||
ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases,
|
||||
0, // unlimited search results
|
||||
0, // no server-side time limit
|
||||
false, // typesOnly
|
||||
"(objectClass=*)",
|
||||
[]string{attr},
|
||||
nil,
|
||||
)
|
||||
sr, err := ls.searchResult(cfg, searchRequest)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if len(sr.Entries) == 0 {
|
||||
return ""
|
||||
}
|
||||
return sr.Entries[0].GetAttributeValue(attr)
|
||||
}
|
||||
@@ -18,6 +18,7 @@ type Service struct {
|
||||
*AuditService
|
||||
*ShareRecordService
|
||||
*ServerCmdService
|
||||
*LdapService
|
||||
}
|
||||
|
||||
func New() *Service {
|
||||
|
||||
@@ -46,6 +46,14 @@ func (us *UserService) InfoByOpenid(openid string) *model.User {
|
||||
|
||||
// InfoByUsernamePassword 根据用户名密码取用户信息
|
||||
func (us *UserService) InfoByUsernamePassword(username, password string) *model.User {
|
||||
if global.Config.Ldap.Enable {
|
||||
u, err := AllService.LdapService.Authenticate(username, password)
|
||||
if err == nil {
|
||||
return u
|
||||
}
|
||||
global.Logger.Error("LDAP authentication failed, %v", err)
|
||||
global.Logger.Warn("Fallback to local database")
|
||||
}
|
||||
u := &model.User{}
|
||||
global.DB.Where("username = ? and password = ?", username, us.EncryptPassword(password)).First(u)
|
||||
return u
|
||||
@@ -82,7 +90,7 @@ func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserTok
|
||||
Token: token,
|
||||
DeviceUuid: llog.Uuid,
|
||||
DeviceId: llog.DeviceId,
|
||||
ExpiredAt: time.Now().Add(time.Hour * 24 * 7).Unix(),
|
||||
ExpiredAt: us.UserTokenExpireTimestamp(),
|
||||
}
|
||||
global.DB.Create(ut)
|
||||
llog.UserTokenId = ut.UserId
|
||||
@@ -156,6 +164,9 @@ func (us *UserService) CheckUserEnable(u *model.User) bool {
|
||||
// Create 创建
|
||||
func (us *UserService) Create(u *model.User) error {
|
||||
// The initial username should be formatted, and the username should be unique
|
||||
if us.IsUsernameExists(u.Username) {
|
||||
return errors.New("UsernameExists")
|
||||
}
|
||||
u.Username = us.formatUsername(u.Username)
|
||||
u.Password = us.EncryptPassword(u.Password)
|
||||
res := global.DB.Create(u).Error
|
||||
@@ -343,13 +354,10 @@ func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (e
|
||||
|
||||
// GenerateUsernameByOauth 生成用户名
|
||||
func (us *UserService) GenerateUsernameByOauth(name string) string {
|
||||
u := &model.User{}
|
||||
global.DB.Where("username = ?", name).First(u)
|
||||
if u.Id == 0 {
|
||||
return name
|
||||
for us.IsUsernameExists(name) {
|
||||
name += strconv.Itoa(rand.Intn(10)) // Append a random digit (0-9)
|
||||
}
|
||||
name = name + strconv.FormatInt(rand.Int63n(10), 10)
|
||||
return us.GenerateUsernameByOauth(name)
|
||||
return name
|
||||
}
|
||||
|
||||
// UserThirdsByUserId
|
||||
@@ -394,15 +402,18 @@ func (us *UserService) IsPasswordEmptyByUser(u *model.User) bool {
|
||||
return us.IsPasswordEmptyById(u.Id)
|
||||
}
|
||||
|
||||
// Register 注册
|
||||
// Register 注册, 如果用户名已存在则返回nil
|
||||
func (us *UserService) Register(username string, email string, password string) *model.User {
|
||||
u := &model.User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Password: us.EncryptPassword(password),
|
||||
Password: password,
|
||||
GroupId: 1,
|
||||
}
|
||||
global.DB.Create(u)
|
||||
err := us.Create(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -451,8 +462,17 @@ func (us *UserService) getAdminUserCount() int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
// UserTokenExpireTimestamp 生成用户token过期时间
|
||||
func (us *UserService) UserTokenExpireTimestamp() int64 {
|
||||
exp := global.Config.App.TokenExpire
|
||||
if exp == 0 {
|
||||
exp = 3600 * 24 * 7
|
||||
}
|
||||
return time.Now().Add(time.Second * time.Duration(exp)).Unix()
|
||||
}
|
||||
|
||||
func (us *UserService) RefreshAccessToken(ut *model.UserToken) {
|
||||
ut.ExpiredAt = time.Now().Add(time.Hour * 24 * 7).Unix()
|
||||
ut.ExpiredAt = us.UserTokenExpireTimestamp()
|
||||
global.DB.Model(ut).Update("expired_at", ut.ExpiredAt)
|
||||
}
|
||||
func (us *UserService) AutoRefreshAccessToken(ut *model.UserToken) {
|
||||
@@ -468,3 +488,11 @@ func (us *UserService) BatchDeleteUserToken(ids []uint) error {
|
||||
func (us *UserService) VerifyJWT(token string) (uint, error) {
|
||||
return global.Jwt.ParseToken(token)
|
||||
}
|
||||
|
||||
// IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled)
|
||||
func (us *UserService) IsUsernameExists(username string) bool {
|
||||
u := &model.User{}
|
||||
global.DB.Where("username = ?", username).First(u)
|
||||
existsInLdap := AllService.LdapService.IsUsernameExists(username)
|
||||
return u.Id != 0 || existsInLdap
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user