├── data
└── .gitkeep
├── debian
├── compat
├── source
│ └── format
├── rules
├── changelog
├── rustdesk-api-server.install
├── rustdesk-api-server.prerm
├── control.tpl
├── rustdesk-api-server.postrm
├── rustdesk-api-server.postinst
└── copyright
├── resources
├── version
├── public
│ └── upload
│ │ └── .gitkeep
├── web
│ ├── .gitignore
│ ├── js
│ │ ├── .gitattributes
│ │ ├── .yarnrc.yml
│ │ ├── src
│ │ │ ├── vite-env.d.ts
│ │ │ ├── main.ts
│ │ │ ├── style.css
│ │ │ ├── codec.js
│ │ │ └── common.ts
│ │ ├── .gitignore
│ │ ├── dist
│ │ │ ├── index.css
│ │ │ └── index.html
│ │ ├── vite.config.js
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ ├── ts_proto.py
│ │ └── gen_js_from_hbb.py
│ ├── .last_build_id
│ ├── yuv.wasm
│ ├── libopus.wasm
│ ├── start-server.bat
│ ├── web_deps.tar.gz
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── version.json
│ ├── assets
│ │ ├── assets
│ │ │ ├── mac.png
│ │ │ ├── win.png
│ │ │ ├── linux.png
│ │ │ ├── android.png
│ │ │ ├── gestures.ttf
│ │ │ ├── insecure.png
│ │ │ ├── secure.png
│ │ │ ├── secure_relay.png
│ │ │ └── insecure_relay.png
│ │ ├── fonts
│ │ │ └── MaterialIcons-Regular.otf
│ │ ├── packages
│ │ │ └── cupertino_icons
│ │ │ │ └── assets
│ │ │ │ └── CupertinoIcons.ttf
│ │ ├── FontManifest.json
│ │ └── AssetManifest.json
│ ├── yarn.lock
│ ├── ogvjs-1.8.6
│ │ ├── ogv-demuxer-ogg-wasm.wasm
│ │ ├── ogv-demuxer-webm-wasm.wasm
│ │ ├── ogv-decoder-video-av1-wasm.wasm
│ │ ├── ogv-decoder-video-vp8-wasm.wasm
│ │ ├── ogv-decoder-video-vp9-wasm.wasm
│ │ ├── ogv-decoder-audio-opus-wasm.wasm
│ │ ├── ogv-decoder-audio-vorbis-wasm.wasm
│ │ ├── ogv-decoder-video-av1-mt-wasm.wasm
│ │ ├── ogv-decoder-video-theora-wasm.wasm
│ │ ├── ogv-decoder-video-vp8-mt-wasm.wasm
│ │ ├── ogv-decoder-video-vp9-mt-wasm.wasm
│ │ ├── ogv-decoder-video-av1-simd-wasm.wasm
│ │ ├── ogv-decoder-video-vp9-simd-wasm.wasm
│ │ ├── ogv-decoder-video-av1-simd-mt-wasm.wasm
│ │ ├── ogv-decoder-video-vp9-simd-mt-wasm.wasm
│ │ ├── LICENSE-nestegg.txt
│ │ ├── ogv-version.js
│ │ ├── COPYING
│ │ ├── COPYING-dav1d.txt
│ │ ├── PATENTS-vpx.txt
│ │ ├── COPYING-ogg.txt
│ │ ├── COPYING-theora.txt
│ │ ├── COPYING-vorbis.txt
│ │ ├── LICENSE-vpx.txt
│ │ ├── COPYING-opus.txt
│ │ ├── ogv-support.js
│ │ ├── ogv-decoder-video-av1-mt-wasm.worker.js
│ │ ├── ogv-decoder-video-vp8-mt-wasm.worker.js
│ │ ├── ogv-decoder-video-vp9-mt-wasm.worker.js
│ │ ├── ogv-decoder-video-av1-simd-mt-wasm.worker.js
│ │ └── ogv-decoder-video-vp9-simd-mt-wasm.worker.js
│ ├── manifest.json
│ ├── favicon.svg
│ └── yuv.js
└── templates
│ ├── oauth_success.html
│ └── oauth_fail.html
├── runtime
└── cache
│ └── .gitkeep
├── lib
├── upload
│ └── local.go
├── lock
│ ├── lock.go
│ ├── local.go
│ └── local_test.go
├── cache
│ ├── redis.go
│ ├── simple_cache.go
│ ├── cache.go
│ ├── cache_test.go
│ ├── file_test.go
│ ├── redis_test.go
│ ├── simple_cache_test.go
│ ├── file.go
│ └── memory_test.go
├── logger
│ └── logger.go
├── orm
│ ├── postgresql.go
│ ├── sqlite.go
│ └── mysql.go
└── jwt
│ ├── jwt.go
│ └── jwt_test.go
├── generate_run.go
├── docs
├── pc_ab.png
├── pc_gr.png
├── api_swag.png
├── pc_login.png
├── web_admin.png
├── webclientv2.png
├── en_img
│ ├── pc_ab.png
│ ├── pc_gr.png
│ ├── pc_login.png
│ ├── web_admin.png
│ ├── web_admin_gr.png
│ ├── web_admin_user.png
│ ├── web_resetpwd.png
│ ├── admin_webclient.png
│ ├── web_admin_oauth.png
│ ├── rustdesk_command_advance.png
│ └── rustdesk_command_simple.png
├── web_admin_gr.png
├── web_resetpwd.png
├── admin_webclient.png
├── init_admin_pwd.png
├── web_admin_oauth.png
├── web_admin_user.png
├── rustdesk_command_simple.png
└── rustdesk_command_advance.png
├── .gitattributes
├── conf
└── admin
│ └── hello.html
├── config
├── redis.go
├── proxy.go
├── logger.go
├── jwt.go
├── cache.go
├── gin.go
├── oss.go
├── oauth.go
├── gorm.go
├── rustdesk.go
├── ldap.go
└── config.go
├── .gitignore
├── model
├── version.go
├── userToken.go
├── custom_types
│ ├── auto_time.go
│ └── auto_json.go
├── group.go
├── tag.go
├── shareRecord.go
├── model.go
├── userThird.go
├── loginLog.go
├── peer.go
├── user.go
└── audit.go
├── http
├── run_win.go
├── run.go
├── request
│ ├── admin
│ │ ├── audit.go
│ │ ├── shareRecord.go
│ │ ├── login.go
│ │ ├── group.go
│ │ ├── tag.go
│ │ ├── oauth.go
│ │ ├── peer.go
│ │ └── user.go
│ └── api
│ │ ├── oauth.go
│ │ ├── user.go
│ │ ├── audit.go
│ │ └── peer.go
├── middleware
│ ├── logger.go
│ ├── admin_privilege.go
│ ├── limiter.go
│ ├── cors.go
│ ├── admin.go
│ ├── jwt.go
│ └── rustauth.go
├── response
│ ├── api
│ │ ├── ab.go
│ │ ├── webClient.go
│ │ ├── user.go
│ │ └── peer.go
│ ├── admin
│ │ └── user.go
│ └── response.go
├── router
│ └── router.go
├── controller
│ ├── web
│ │ └── index.go
│ ├── api
│ │ ├── user.go
│ │ ├── index.go
│ │ ├── peer.go
│ │ └── audit.go
│ └── admin
│ │ ├── my
│ │ └── peer.go
│ │ ├── file.go
│ │ └── config.go
└── http.go
├── Dockerfile
├── generate_api.go
├── systemd
└── rustdesk-api.service
├── .dockerignore
├── docker-compose.yaml
├── service
├── app_test.go
├── app.go
├── shareRecord.go
├── loginLog.go
├── service.go
├── group.go
├── serverCmd.go
├── tag.go
└── audit.go
├── docker-compose-dev.yaml
├── docker-dev.sh
├── LICENSE
├── utils
├── password_test.go
├── password.go
├── captcha.go
└── tools.go
├── global
├── global.go
└── i18n.go
└── Dockerfile_full_s6
/data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/debian/compat:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/resources/version:
--------------------------------------------------------------------------------
1 | v1.0.0
--------------------------------------------------------------------------------
/runtime/cache/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/debian/source/format:
--------------------------------------------------------------------------------
1 | 3.0 (native)
--------------------------------------------------------------------------------
/resources/public/upload/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/web/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/resources/web/js/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/resources/web/js/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/resources/web/.last_build_id:
--------------------------------------------------------------------------------
1 | c72c6e6149056c379540743392607228
--------------------------------------------------------------------------------
/lib/upload/local.go:
--------------------------------------------------------------------------------
1 | package upload
2 |
3 | type Local struct {
4 | }
5 |
--------------------------------------------------------------------------------
/resources/web/js/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
--------------------------------------------------------------------------------
/generate_run.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate go run cmd/apimain.go
4 |
--------------------------------------------------------------------------------
/docs/pc_ab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/pc_ab.png
--------------------------------------------------------------------------------
/docs/pc_gr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/pc_gr.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | resources/web/**/* linguist-vendored
2 | resources/web2/**/* linguist-vendored
--------------------------------------------------------------------------------
/docs/api_swag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/api_swag.png
--------------------------------------------------------------------------------
/docs/pc_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/pc_login.png
--------------------------------------------------------------------------------
/resources/web/js/src/main.ts:
--------------------------------------------------------------------------------
1 | import "./ljw";
2 | import "./globals";
3 | import "./ui";
4 |
--------------------------------------------------------------------------------
/docs/web_admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/web_admin.png
--------------------------------------------------------------------------------
/docs/webclientv2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/webclientv2.png
--------------------------------------------------------------------------------
/docs/en_img/pc_ab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/pc_ab.png
--------------------------------------------------------------------------------
/docs/en_img/pc_gr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/pc_gr.png
--------------------------------------------------------------------------------
/docs/web_admin_gr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/web_admin_gr.png
--------------------------------------------------------------------------------
/docs/web_resetpwd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/web_resetpwd.png
--------------------------------------------------------------------------------
/resources/web/yuv.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/yuv.wasm
--------------------------------------------------------------------------------
/docs/admin_webclient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/admin_webclient.png
--------------------------------------------------------------------------------
/docs/en_img/pc_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/pc_login.png
--------------------------------------------------------------------------------
/docs/en_img/web_admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/web_admin.png
--------------------------------------------------------------------------------
/docs/init_admin_pwd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/init_admin_pwd.png
--------------------------------------------------------------------------------
/docs/web_admin_oauth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/web_admin_oauth.png
--------------------------------------------------------------------------------
/docs/web_admin_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/web_admin_user.png
--------------------------------------------------------------------------------
/debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 | %:
3 | dh $@
4 |
5 | override_dh_builddeb:
6 | dh_builddeb -- -Zgzip
7 |
--------------------------------------------------------------------------------
/resources/web/libopus.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/libopus.wasm
--------------------------------------------------------------------------------
/resources/web/start-server.bat:
--------------------------------------------------------------------------------
1 | flutter run --release -d web-server --web-port 5005 --web-hostname 0.0.0.0
2 |
--------------------------------------------------------------------------------
/conf/admin/hello.html:
--------------------------------------------------------------------------------
1 | ### 👏👏👏 你好 ***{{username}}***, 欢迎使用 [RustDesk API](https://github.com/lejianwen/rustdesk-api)
--------------------------------------------------------------------------------
/docs/en_img/web_admin_gr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/web_admin_gr.png
--------------------------------------------------------------------------------
/docs/en_img/web_admin_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/web_admin_user.png
--------------------------------------------------------------------------------
/docs/en_img/web_resetpwd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/web_resetpwd.png
--------------------------------------------------------------------------------
/resources/web/web_deps.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/web_deps.tar.gz
--------------------------------------------------------------------------------
/docs/en_img/admin_webclient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/admin_webclient.png
--------------------------------------------------------------------------------
/docs/en_img/web_admin_oauth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/web_admin_oauth.png
--------------------------------------------------------------------------------
/docs/rustdesk_command_simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/rustdesk_command_simple.png
--------------------------------------------------------------------------------
/resources/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/resources/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/resources/web/version.json:
--------------------------------------------------------------------------------
1 | {"app_name":"flutter_hbb","version":"1.1.10-1","build_number":"28","package_name":"flutter_hbb"}
--------------------------------------------------------------------------------
/docs/rustdesk_command_advance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/rustdesk_command_advance.png
--------------------------------------------------------------------------------
/resources/web/assets/assets/mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/mac.png
--------------------------------------------------------------------------------
/resources/web/assets/assets/win.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/win.png
--------------------------------------------------------------------------------
/resources/web/js/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist-ssr
4 | *.local
5 | *log
6 | ogvjs
7 | .vscode
8 | .yarn
9 |
--------------------------------------------------------------------------------
/resources/web/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------
/config/redis.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Redis struct {
4 | Addr string
5 | Password string
6 | Db int
7 | }
8 |
--------------------------------------------------------------------------------
/resources/web/assets/assets/linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/linux.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | runtime/*
3 | !runtime
4 | !runtime/cache/.gitkeep
5 | go.sum
6 | resources/admin
7 | release
8 | data/rustdeskapi.db
--------------------------------------------------------------------------------
/docs/en_img/rustdesk_command_advance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/rustdesk_command_advance.png
--------------------------------------------------------------------------------
/docs/en_img/rustdesk_command_simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/docs/en_img/rustdesk_command_simple.png
--------------------------------------------------------------------------------
/resources/web/assets/assets/android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/android.png
--------------------------------------------------------------------------------
/resources/web/assets/assets/gestures.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/gestures.ttf
--------------------------------------------------------------------------------
/resources/web/assets/assets/insecure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/insecure.png
--------------------------------------------------------------------------------
/resources/web/assets/assets/secure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/secure.png
--------------------------------------------------------------------------------
/resources/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/resources/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/resources/web/assets/assets/secure_relay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/secure_relay.png
--------------------------------------------------------------------------------
/config/proxy.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Proxy struct {
4 | Enable bool `mapstructure:"enable"`
5 | Host string `mapstructure:"host"`
6 | }
7 |
--------------------------------------------------------------------------------
/resources/web/assets/assets/insecure_relay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/assets/insecure_relay.png
--------------------------------------------------------------------------------
/model/version.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type Version struct {
4 | IdModel
5 | Version uint `json:"version" gorm:"default:0;not null;"`
6 | TimeModel
7 | }
8 |
--------------------------------------------------------------------------------
/resources/web/assets/fonts/MaterialIcons-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/fonts/MaterialIcons-Regular.otf
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-demuxer-ogg-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-demuxer-ogg-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-demuxer-webm-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-demuxer-webm-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-wasm.wasm
--------------------------------------------------------------------------------
/config/logger.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Logger struct {
4 | Path string
5 | Level string
6 | ReportCaller bool `mapstructure:"report-caller"`
7 | }
8 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-audio-opus-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-audio-opus-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-audio-vorbis-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-audio-vorbis-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-mt-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-mt-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-theora-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-theora-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-mt-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-mt-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-mt-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-mt-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-wasm.wasm
--------------------------------------------------------------------------------
/lib/lock/lock.go:
--------------------------------------------------------------------------------
1 | package lock
2 |
3 | import "sync"
4 |
5 | type Locker interface {
6 | GetLock(key string) *sync.Mutex
7 | Lock(key string)
8 | UnLock(key string)
9 | }
10 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-mt-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-mt-wasm.wasm
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-mt-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-mt-wasm.wasm
--------------------------------------------------------------------------------
/http/run_win.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package http
4 |
5 | import (
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func Run(g *gin.Engine, addr string) {
10 | g.Run(addr)
11 | }
12 |
--------------------------------------------------------------------------------
/resources/web/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lejianwen/rustdesk-api/HEAD/resources/web/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf
--------------------------------------------------------------------------------
/resources/web/js/dist/index.css:
--------------------------------------------------------------------------------
1 | #app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px}
2 |
--------------------------------------------------------------------------------
/config/jwt.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "time"
4 |
5 | type Jwt struct {
6 | Key string `mapstructure:"key"`
7 | ExpireDuration time.Duration `mapstructure:"expire-duration"`
8 | }
9 |
--------------------------------------------------------------------------------
/debian/changelog:
--------------------------------------------------------------------------------
1 | rustdesk-api-server (1.3.6) UNRELEASED; urgency=medium
2 |
3 | * Update the version to 1.3.6 to match the client.
4 |
5 | -- rustdesk-api Tue, 24 Dec 2024 13:48:34 +0800
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine
2 |
3 | ARG BUILDARCH
4 | WORKDIR /app
5 | RUN apk add --no-cache tzdata
6 | COPY ./${BUILDARCH}/release /app/
7 | VOLUME /app/data
8 |
9 | EXPOSE 21114
10 | CMD ["./apimain"]
11 |
--------------------------------------------------------------------------------
/debian/rustdesk-api-server.install:
--------------------------------------------------------------------------------
1 | bin/rustdesk-api usr/bin
2 | systemd/rustdesk-api.service lib/systemd/system
3 | conf var/lib/rustdesk-api
4 | data var/lib/rustdesk-api
5 | resources var/lib/rustdesk-api
6 | runtime var/lib/rustdesk-api
--------------------------------------------------------------------------------
/http/run.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 |
3 | package http
4 |
5 | import (
6 | "github.com/fvbock/endless"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | func Run(g *gin.Engine, addr string) {
11 | endless.ListenAndServe(addr, g)
12 | }
13 |
--------------------------------------------------------------------------------
/resources/web/js/src/style.css:
--------------------------------------------------------------------------------
1 | #app {
2 | font-family: Avenir, Helvetica, Arial, sans-serif;
3 | -webkit-font-smoothing: antialiased;
4 | -moz-osx-font-smoothing: grayscale;
5 | text-align: center;
6 | color: #2c3e50;
7 | margin-top: 60px;
8 | }
9 |
--------------------------------------------------------------------------------
/config/cache.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Cache struct {
4 | Type string
5 | RedisAddr string `mapstructure:"redis-addr"`
6 | RedisPwd string `mapstructure:"redis-pwd"`
7 | RedisDb int `mapstructure:"redis-db"`
8 | FileDir string `mapstructure:"file-dir"`
9 | }
10 |
--------------------------------------------------------------------------------
/generate_api.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | //go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
4 | //go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
5 | //go:generate go run cmd/apimain.go
6 |
--------------------------------------------------------------------------------
/resources/web/assets/FontManifest.json:
--------------------------------------------------------------------------------
1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"GestureIcons","fonts":[{"asset":"assets/gestures.ttf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]
--------------------------------------------------------------------------------
/config/gin.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Gin struct {
4 | ApiAddr string `mapstructure:"api-addr"`
5 | AdminAddr string `mapstructure:"admin-addr"`
6 | Mode string
7 | ResourcesPath string `mapstructure:"resources-path"`
8 | TrustProxy string `mapstructure:"trust-proxy"`
9 | }
10 |
--------------------------------------------------------------------------------
/debian/rustdesk-api-server.prerm:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | SERVICE=rustdesk-api.service
5 |
6 | case "$1" in
7 | remove|deconfigure)
8 | deb-systemd-invoke stop "${SERVICE}" >/dev/null || true
9 | deb-systemd-invoke disable "${SERVICE}" >/dev/null || true
10 | ;;
11 | esac
12 |
13 | exit 0
14 |
--------------------------------------------------------------------------------
/http/request/admin/audit.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | type AuditQuery struct {
4 | PeerId string `form:"peer_id"`
5 | FromPeer string `form:"from_peer"`
6 | PageQuery
7 | }
8 |
9 | type AuditConnLogIds struct {
10 | Ids []uint `json:"ids" validate:"required"`
11 | }
12 | type AuditFileLogIds struct {
13 | Ids []uint `json:"ids" validate:"required"`
14 | }
15 |
--------------------------------------------------------------------------------
/config/oss.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type Oss struct {
4 | AccessKeyId string `mapstructure:"access-key-id"`
5 | AccessKeySecret string `mapstructure:"access-key-secret"`
6 | Host string `mapstructure:"host"`
7 | CallbackUrl string `mapstructure:"callback-url"`
8 | ExpireTime int64 `mapstructure:"expire-time"`
9 | MaxByte int64 `mapstructure:"max-byte"`
10 | }
11 |
--------------------------------------------------------------------------------
/http/request/admin/shareRecord.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | type ShareRecordQuery struct {
4 | UserId uint `json:"user_id" form:"user_id"`
5 | PageQuery
6 | }
7 |
8 | type ShareRecordForm struct {
9 | Id uint `json:"id" form:"id"`
10 | UserId uint `json:"user_id" form:"user_id"`
11 | }
12 |
13 | type PeerShareRecordBatchDeleteForm struct {
14 | Ids []uint `json:"ids" validate:"required"`
15 | }
16 |
--------------------------------------------------------------------------------
/resources/web/js/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 |
3 | export default defineConfig({
4 | build: {
5 | manifest: false,
6 | rollupOptions: {
7 | output: {
8 | entryFileNames: `[name].js`,
9 | chunkFileNames: `[name].js`,
10 | assetFileNames: `[name].[ext]`,
11 | }
12 | }
13 | },
14 | })
--------------------------------------------------------------------------------
/debian/control.tpl:
--------------------------------------------------------------------------------
1 | Source: rustdesk-api-server
2 | Section: net
3 | Priority: optional
4 | Maintainer: ymwl
5 | Build-Depends: debhelper (>= 10), pkg-config
6 | Standards-Version: 4.5.0
7 | Homepage: https://github.com/lejianwen/rustdesk-api/
8 |
9 | Package: rustdesk-api-server
10 | Architecture: {{ ARCH }}
11 | Depends: systemd ${misc:Depends}
12 | Description: RustDesk api server
13 | RustDesk api server, it is free and open source.
14 |
--------------------------------------------------------------------------------
/debian/rustdesk-api-server.postrm:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | SERVICE=rustdesk-api.service
5 |
6 | systemctl --system daemon-reload >/dev/null || true
7 |
8 | if [ "$1" = "purge" ]; then
9 | rm -rf /var/log/rustdesk-api/rustdesk-api.*
10 | deb-systemd-helper purge "${SERVICE}" >/dev/null || true
11 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
12 | fi
13 |
14 | if [ "$1" = "remove" ]; then
15 | deb-systemd-helper mask "${SERVICE}" >/dev/null || true
16 | fi
17 |
18 | exit 0
19 |
--------------------------------------------------------------------------------
/http/middleware/logger.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/sirupsen/logrus"
7 | )
8 |
9 | // Logger 日志中间件
10 | func Logger() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | global.Logger.WithFields(
13 | logrus.Fields{
14 | "uri": c.Request.URL,
15 | "ip": c.ClientIP(),
16 | "method": c.Request.Method,
17 | }).Debug("Request")
18 | c.Next()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/systemd/rustdesk-api.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Rustdesk api Server
3 |
4 | [Service]
5 | Type=simple
6 | LimitNOFILE=1000000
7 | ExecStart=/usr/bin/rustdesk-api
8 | WorkingDirectory=/var/lib/rustdesk-api/
9 | User=
10 | Group=
11 | Restart=always
12 | StandardOutput=append:/var/log/rustdesk-api/rustdesk-api.log
13 | StandardError=append:/var/log/rustdesk-api/rustdesk-api.error
14 | # Restart service after 10 seconds if node service crashes
15 | RestartSec=10
16 |
17 | [Install]
18 | WantedBy=multi-user.target
19 |
--------------------------------------------------------------------------------
/http/request/api/oauth.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | type OidcAuthRequest struct {
4 | DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"`
5 | Id string `json:"id" label:"id"`
6 | Op string `json:"op" label:"op"`
7 | Uuid string `json:"uuid" label:"uuid"`
8 | }
9 |
10 | type OidcAuthQuery struct {
11 | Code string `json:"code" form:"code" label:"code"`
12 | Id string `json:"id" form:"id" label:"id"`
13 | Uuid string `json:"uuid" form:"uuid" label:"uuid"`
14 | }
15 |
--------------------------------------------------------------------------------
/http/response/api/ab.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | type AbList struct {
6 | Peers []*model.AddressBook `json:"peers,omitempty"`
7 | Tags []string `json:"tags,omitempty"`
8 | TagColors string `json:"tag_colors,omitempty"`
9 | }
10 |
11 | type SharedProfilesPayload struct {
12 | Guid string `json:"guid"`
13 | Name string `json:"name"`
14 | Owner string `json:"owner"`
15 | Note string `json:"note"`
16 | Rule int `json:"rule"`
17 | }
18 |
--------------------------------------------------------------------------------
/resources/web/js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Vite App
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Ignore Docker Compose configuration files
2 | docker-compose.yaml
3 | docker-compose-dev.yaml
4 |
5 | # Ignore development Dockerfile
6 | Dockerfile
7 | Dockerfile.dev
8 | docker-dev.sh
9 |
10 | # Ignore the data directory
11 | data/
12 |
13 | # Ignore version control system directories
14 | .git/
15 |
16 | # Ignore log and temporary files
17 | *.log
18 | *.tmp
19 | *.swp
20 |
21 | # Ignore editor/IDE configuration files
22 | .vscode/
23 | .idea/
24 |
25 | # Ignore binaries and build cache
26 | release/
27 | bin/
28 | *.exe
29 | *.out
--------------------------------------------------------------------------------
/model/userToken.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type UserToken struct {
4 | IdModel
5 | UserId uint `json:"user_id" gorm:"default:0;not null;index"`
6 | DeviceUuid string `json:"device_uuid" gorm:"default:'';omitempty;"`
7 | DeviceId string `json:"device_id" gorm:"default:'';omitempty;"`
8 | Token string `json:"token" gorm:"default:'';not null;index"`
9 | ExpiredAt int64 `json:"expired_at" gorm:"default:0;not null;"`
10 | TimeModel
11 | }
12 |
13 | type UserTokenList struct {
14 | UserTokens []UserToken `json:"list"`
15 | Pagination
16 | }
17 |
--------------------------------------------------------------------------------
/resources/web/js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web_hbb",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "vite preview"
8 | },
9 | "devDependencies": {
10 | "typescript": "4.4.4",
11 | "vite": "2.8"
12 | },
13 | "dependencies": {
14 | "fast-sha256": "^1.3.0",
15 | "libsodium": "^0.7.9",
16 | "libsodium-wrappers": "^0.7.9",
17 | "pcm-player": "^0.0.11",
18 | "ts-proto": "^1.141.1",
19 | "wasm-feature-detect": "^1.2.11",
20 | "zstddec": "^0.0.2"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/http/middleware/admin_privilege.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/http/response"
6 | "github.com/lejianwen/rustdesk-api/v2/service"
7 | )
8 |
9 | // AdminPrivilege ...
10 | func AdminPrivilege() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | u := service.AllService.UserService.CurUser(c)
13 |
14 | if !service.AllService.UserService.IsAdmin(u) {
15 | response.Fail(c, 403, response.TranslateMsg(c, "NoAccess"))
16 | c.Abort()
17 | return
18 | }
19 |
20 | c.Next()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/resources/web/js/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "allowJs": true,
7 | "lib": [
8 | "ESNext",
9 | "DOM"
10 | ],
11 | "moduleResolution": "Node",
12 | "strict": true,
13 | "sourceMap": true,
14 | "resolveJsonModule": true,
15 | "esModuleInterop": true,
16 | "noEmit": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "noImplicitReturns": true
20 | },
21 | "include": [
22 | "./src"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/http/middleware/limiter.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/http/response"
7 | "net/http"
8 | )
9 |
10 | func Limiter() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | loginLimiter := global.LoginLimiter
13 | clientIp := c.ClientIP()
14 | banned, _ := loginLimiter.CheckSecurityStatus(clientIp)
15 | if banned {
16 | response.Fail(c, http.StatusLocked, response.TranslateMsg(c, "Banned"))
17 | c.Abort()
18 | return
19 | }
20 | c.Next()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/model/custom_types/auto_time.go:
--------------------------------------------------------------------------------
1 | package custom_types
2 |
3 | import (
4 | "database/sql/driver"
5 | "time"
6 | )
7 |
8 | // AutoTime 自定义时间格式
9 | type AutoTime time.Time
10 |
11 | func (mt AutoTime) Value() (driver.Value, error) {
12 | var zeroTime time.Time
13 | t := time.Time(mt)
14 | if t.UnixNano() == zeroTime.UnixNano() {
15 | return nil, nil
16 | }
17 | return t, nil
18 | }
19 |
20 | func (mt AutoTime) MarshalJSON() ([]byte, error) {
21 | //b := make([]byte, 0, len("2006-01-02 15:04:05")+2)
22 | b := time.Time(mt).AppendFormat([]byte{}, "\"2006-01-02 15:04:05\"")
23 | return b, nil
24 | }
25 |
--------------------------------------------------------------------------------
/resources/web/assets/AssetManifest.json:
--------------------------------------------------------------------------------
1 | {"assets/android.png":["assets/android.png"],"assets/gestures.ttf":["assets/gestures.ttf"],"assets/insecure.png":["assets/insecure.png"],"assets/insecure_relay.png":["assets/insecure_relay.png"],"assets/linux.png":["assets/linux.png"],"assets/mac.png":["assets/mac.png"],"assets/secure.png":["assets/secure.png"],"assets/secure_relay.png":["assets/secure_relay.png"],"assets/win.png":["assets/win.png"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/wakelock_web/assets/no_sleep.js":["packages/wakelock_web/assets/no_sleep.js"]}
--------------------------------------------------------------------------------
/lib/lock/local.go:
--------------------------------------------------------------------------------
1 | package lock
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type Local struct {
8 | Locks *sync.Map
9 | }
10 |
11 | func (l *Local) Lock(key string) {
12 | lock := l.GetLock(key)
13 | lock.Lock()
14 | }
15 |
16 | func (l *Local) UnLock(key string) {
17 | lock, ok := l.Locks.Load(key)
18 | if ok {
19 | lock.(*sync.Mutex).Unlock()
20 | }
21 | }
22 |
23 | func (l *Local) GetLock(key string) *sync.Mutex {
24 | lock, _ := l.Locks.LoadOrStore(key, &sync.Mutex{})
25 | return lock.(*sync.Mutex)
26 | }
27 |
28 | func NewLocal() *Local {
29 | return &Local{
30 | Locks: &sync.Map{},
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/model/group.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | const (
4 | GroupTypeDefault = 1 // 默认
5 | GroupTypeShare = 2 // 共享
6 | )
7 |
8 | type Group struct {
9 | IdModel
10 | Name string `json:"name" gorm:"default:'';not null;"`
11 | Type int `json:"type" gorm:"default:1;not null;"`
12 | TimeModel
13 | }
14 |
15 | type GroupList struct {
16 | Groups []*Group `json:"list"`
17 | Pagination
18 | }
19 |
20 | type DeviceGroup struct {
21 | IdModel
22 | Name string `json:"name" gorm:"default:'';not null;"`
23 | TimeModel
24 | }
25 |
26 | type DeviceGroupList struct {
27 | DeviceGroups []*DeviceGroup `json:"list"`
28 | Pagination
29 | }
30 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | rustdesk-api:
3 | image: lejianwen/rustdesk-api
4 | container_name: rustdesk-api
5 | environment:
6 | - TZ=Asia/Shanghai
7 | - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
8 | - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
9 | - RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
10 | - RUSTDESK_API_RUSTDESK_KEY=123456789
11 | ports:
12 | - 21114:21114
13 | volumes:
14 | - ./data/rustdesk/api:/app/data # database
15 | # - ./conf:/app/conf # config
16 | # - ./resources:/app/resources # 静态资源
17 | restart: unless-stopped
--------------------------------------------------------------------------------
/model/tag.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type Tag struct {
4 | IdModel
5 | Name string `json:"name" gorm:"default:'';not null;"`
6 | UserId uint `json:"user_id" gorm:"default:0;not null;index"`
7 | Color uint `json:"color" gorm:"default:0;not null;"` //color 是flutter的颜色值,从0x00000000 到 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色, 可以转成rgba
8 | CollectionId uint `json:"collection_id" gorm:"default:0;not null;index"`
9 | Collection *AddressBookCollection `json:"collection,omitempty"`
10 | TimeModel
11 | }
12 |
13 | type TagList struct {
14 | Tags []*Tag `json:"list"`
15 | Pagination
16 | }
17 |
--------------------------------------------------------------------------------
/resources/web/js/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Vite App
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/config/oauth.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type GithubOauth struct {
4 | ClientId string `mapstructure:"client-id"`
5 | ClientSecret string `mapstructure:"client-secret"`
6 | }
7 |
8 | type GoogleOauth struct {
9 | ClientId string `mapstructure:"client-id"`
10 | ClientSecret string `mapstructure:"client-secret"`
11 | }
12 |
13 | type OidcOauth struct {
14 | Issuer string `mapstructure:"issuer"`
15 | ClientId string `mapstructure:"client-id"`
16 | ClientSecret string `mapstructure:"client-secret"`
17 | }
18 |
19 | type LinuxdoOauth struct {
20 | ClientId string `mapstructure:"client-id"`
21 | ClientSecret string `mapstructure:"client-secret"`
22 | }
23 |
--------------------------------------------------------------------------------
/http/middleware/cors.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | // Cors 跨域
9 | func Cors() gin.HandlerFunc {
10 | return func(c *gin.Context) {
11 | origin := c.GetHeader("Origin")
12 | //fmt.Println("origin", origin)
13 | c.Header("Access-Control-Allow-Origin", origin)
14 | c.Header("Access-Control-Allow-Headers", "api-token,content-type,authorization ")
15 | c.Header("Access-Control-Allow-Methods", c.Request.Method)
16 | c.Header("Access-Control-Allow-Credentials", "true")
17 | if c.Request.Method == "OPTIONS" {
18 | c.AbortWithStatus(http.StatusNoContent)
19 | return
20 | }
21 | c.Next()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/model/shareRecord.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type ShareRecord struct {
4 | IdModel
5 | UserId uint `json:"user_id" gorm:"default:0;not null;index"`
6 | PeerId string `json:"peer_id" gorm:"default:'';not null;index"`
7 | ShareToken string `json:"share_token" gorm:"default:'';not null;index"`
8 | PasswordType string `json:"password_type" gorm:"default:'';not null;"`
9 | Password string `json:"password" gorm:"default:'';not null;"`
10 | Expire int64 `json:"expire" gorm:"default:0;not null;"`
11 | TimeModel
12 | }
13 |
14 | // ShareRecordList 分享记录列表
15 | type ShareRecordList struct {
16 | ShareRecords []*ShareRecord `json:"list,omitempty"`
17 | Pagination
18 | }
19 |
--------------------------------------------------------------------------------
/http/request/admin/login.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | type Login struct {
4 | Username string `json:"username" validate:"required" label:"用户名"`
5 | Password string `json:"password,omitempty" validate:"required" label:"密码"`
6 | Platform string `json:"platform" label:"平台"`
7 | Captcha string `json:"captcha,omitempty" label:"验证码"`
8 | CaptchaId string `json:"captcha_id,omitempty"`
9 | }
10 |
11 | type LoginLogQuery struct {
12 | UserId int `form:"user_id"`
13 | IsMy int `form:"is_my"`
14 | PageQuery
15 | }
16 | type LoginTokenQuery struct {
17 | UserId int `form:"user_id"`
18 | PageQuery
19 | }
20 |
21 | type LoginLogIds struct {
22 | Ids []uint `json:"ids" validate:"required"`
23 | }
24 |
--------------------------------------------------------------------------------
/http/response/admin/user.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | type LoginPayload struct {
6 | Username string `json:"username"`
7 | Email string `json:"email"`
8 | Avatar string `json:"avatar"`
9 | Token string `json:"token"`
10 | RouteNames []string `json:"route_names"`
11 | Nickname string `json:"nickname"`
12 | }
13 |
14 | func (lp *LoginPayload) FromUser(user *model.User) {
15 | lp.Username = user.Username
16 | lp.Email = user.Email
17 | lp.Avatar = user.Avatar
18 | lp.Nickname = user.Nickname
19 | }
20 |
21 | type UserOauthItem struct {
22 | Op string `json:"op"`
23 | Status int `json:"status"`
24 | }
25 |
--------------------------------------------------------------------------------
/http/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/http/controller/web"
7 | "net/http"
8 | )
9 |
10 | func WebInit(g *gin.Engine) {
11 | i := &web.Index{}
12 | g.GET("/", i.Index)
13 |
14 | if global.Config.App.WebClient == 1 {
15 | g.GET("/webclient-config/index.js", i.ConfigJs)
16 | }
17 |
18 | if global.Config.App.WebClient == 1 {
19 | g.StaticFS("/webclient", http.Dir(global.Config.Gin.ResourcesPath+"/web"))
20 | g.StaticFS("/webclient2", http.Dir(global.Config.Gin.ResourcesPath+"/web2"))
21 | }
22 | g.StaticFS("/_admin", http.Dir(global.Config.Gin.ResourcesPath+"/admin"))
23 | }
24 |
--------------------------------------------------------------------------------
/service/app_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "sync"
5 | "testing"
6 | )
7 |
8 | // TestGetAppVersion
9 | func TestGetAppVersion(t *testing.T) {
10 | s := &AppService{}
11 | v := s.GetAppVersion()
12 | // 打印结果
13 | t.Logf("App Version: %s", v)
14 | }
15 |
16 | func TestMultipleGetAppVersion(t *testing.T) {
17 | s := &AppService{}
18 | //并发测试
19 | // 使用 WaitGroup 等待所有 goroutine 完成
20 | wg := sync.WaitGroup{}
21 | wg.Add(10) // 启动 10 个 goroutine
22 | // 启动 10 个 goroutine
23 | for i := 0; i < 10; i++ {
24 | go func() {
25 | defer wg.Done() // 完成后减少计数
26 | v := s.GetAppVersion()
27 | // 打印结果
28 | t.Logf("App Version: %s", v)
29 | }()
30 | }
31 | // 等待所有 goroutine 完成
32 | wg.Wait()
33 | }
34 |
--------------------------------------------------------------------------------
/service/app.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "os"
5 | "sync"
6 | "time"
7 | )
8 |
9 | type AppService struct {
10 | }
11 |
12 | var version = ""
13 | var startTime = ""
14 | var once = &sync.Once{}
15 |
16 | func (a *AppService) GetAppVersion() string {
17 | if version != "" {
18 | return version
19 | }
20 | once.Do(func() {
21 | v, err := os.ReadFile("resources/version")
22 | if err != nil {
23 | return
24 | }
25 | version = string(v)
26 |
27 | })
28 | return version
29 | }
30 |
31 | func init() {
32 | // Initialize the AppService if needed
33 | startTime = time.Now().Format("2006-01-02 15:04:05")
34 | }
35 |
36 | // GetStartTime
37 | func (a *AppService) GetStartTime() string {
38 | return startTime
39 | }
40 |
--------------------------------------------------------------------------------
/model/model.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model/custom_types"
5 | )
6 |
7 | type StatusCode int
8 |
9 | const (
10 | COMMON_STATUS_ENABLE StatusCode = 1 //通用状态 启用
11 | COMMON_STATUS_DISABLED StatusCode = 2 //通用状态 禁用
12 | )
13 |
14 | type IdModel struct {
15 | Id uint `gorm:"primaryKey" json:"id"`
16 | }
17 | type TimeModel struct {
18 | CreatedAt custom_types.AutoTime `json:"created_at" gorm:"type:timestamp;"`
19 | UpdatedAt custom_types.AutoTime `json:"updated_at" gorm:"type:timestamp;"`
20 | }
21 |
22 | // Pagination
23 | type Pagination struct {
24 | Page int64 `form:"page" json:"page"`
25 | Total int64 `form:"total" json:"total"`
26 | PageSize int64 `form:"page_size" json:"page_size"`
27 | }
28 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/LICENSE-nestegg.txt:
--------------------------------------------------------------------------------
1 | Copyright © 2010 Mozilla Foundation
2 |
3 | Permission to use, copy, modify, and distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/model/userThird.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | type UserThird struct {
8 | IdModel
9 | UserId uint `json:"user_id" gorm:"not null;index"`
10 | OauthUser
11 | UnionId string `json:"union_id" gorm:"default:'';not null;"`
12 | // OauthType string `json:"oauth_type" gorm:"not null;"`
13 | ThirdType string `json:"third_type" gorm:"default:'';not null;"` //deprecated
14 | OauthType string `json:"oauth_type" gorm:"default:'';not null;"`
15 | Op string `json:"op" gorm:"default:'';not null;"`
16 | TimeModel
17 | }
18 |
19 | func (u *UserThird) FromOauthUser(userId uint, oauthUser *OauthUser, oauthType string, op string) {
20 | u.UserId = userId
21 | u.OauthUser = *oauthUser
22 | u.OauthType = oauthType
23 | u.Op = op
24 | // make sure email is lower case
25 | u.Email = strings.ToLower(u.Email)
26 | }
27 |
--------------------------------------------------------------------------------
/debian/rustdesk-api-server.postinst:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | SERVICE=rustdesk-api.service
5 |
6 | if [ "$1" = "configure" ]; then
7 | mkdir -p /var/log/rustdesk-api
8 | fi
9 |
10 | case "$1" in
11 | configure|abort-upgrade|abort-deconfigure|abort-remove)
12 | mkdir -p /var/lib/rustdesk-api/
13 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
14 | if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then
15 | deb-systemd-invoke enable "${SERVICE}" >/dev/null || true
16 | else
17 | deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true
18 | fi
19 | systemctl --system daemon-reload >/dev/null || true
20 | if [ -n "$2" ]; then
21 | deb-systemd-invoke restart "${SERVICE}" >/dev/null || true
22 | else
23 | deb-systemd-invoke start "${SERVICE}" >/dev/null || true
24 | fi
25 | ;;
26 | esac
27 |
28 | exit 0
29 |
--------------------------------------------------------------------------------
/docker-compose-dev.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | rustdesk-api:
3 | build:
4 | context: .
5 | dockerfile: Dockerfile.dev
6 | args:
7 | COUNTRY: CN
8 | FRONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
9 | FRONTEND_GIT_BRANCH: master
10 | # image: lejianwen/rustdesk-api
11 | container_name: rustdesk-api
12 | environment:
13 | - TZ=Asia/Shanghai
14 | - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
15 | - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
16 | - RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114
17 | - RUSTDESK_API_RUSTDESK_KEY=123456789
18 | ports:
19 | - 21114:21114
20 | volumes:
21 | - ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份
22 | - ./conf:/app/conf # config
23 | # - ./resources:/app/resources # 静态资源
24 | restart: unless-stopped
25 |
--------------------------------------------------------------------------------
/http/request/admin/group.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | type GroupForm struct {
6 | Id uint `json:"id"`
7 | Name string `json:"name" validate:"required"`
8 | Type int `json:"type"`
9 | }
10 |
11 | func (gf *GroupForm) FromGroup(group *model.Group) *GroupForm {
12 | gf.Id = group.Id
13 | gf.Name = group.Name
14 | gf.Type = group.Type
15 | return gf
16 | }
17 |
18 | func (gf *GroupForm) ToGroup() *model.Group {
19 | group := &model.Group{}
20 | group.Id = gf.Id
21 | group.Name = gf.Name
22 | group.Type = gf.Type
23 | return group
24 | }
25 |
26 | type DeviceGroupForm struct {
27 | Id uint `json:"id"`
28 | Name string `json:"name" validate:"required"`
29 | }
30 |
31 | func (gf *DeviceGroupForm) ToDeviceGroup() *model.DeviceGroup {
32 | group := &model.DeviceGroup{}
33 | group.Id = gf.Id
34 | group.Name = gf.Name
35 | return group
36 | }
37 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-version.js:
--------------------------------------------------------------------------------
1 | (()=>{var e={318:e=>{e.exports=function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}},e.exports.__esModule=!0,e.exports.default=e.exports},8:e=>{function _typeof(o){return e.exports=_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,_typeof(o)}e.exports=_typeof,e.exports.__esModule=!0,e.exports.default=e.exports}},o={};function __webpack_require__(t){var r=o[t];if(void 0!==r)return r.exports;var p=o[t]={exports:{}};return e[t](p,p.exports,__webpack_require__),p.exports}(()=>{"use strict";var e=__webpack_require__(318)(__webpack_require__(8)),o="1.8.6-20220111172545-1f60d9d";"object"===("undefined"==typeof window?"undefined":(0,e.default)(window))&&(window.OGVVersion=o)})()})();
--------------------------------------------------------------------------------
/http/request/admin/tag.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | type TagForm struct {
6 | Id uint `json:"id"`
7 | Name string `json:"name" validate:"required"`
8 | Color uint `json:"color" validate:"required"`
9 | UserId uint `json:"user_id"`
10 | CollectionId uint `json:"collection_id"`
11 | }
12 |
13 | func (f *TagForm) FromTag(group *model.Tag) *TagForm {
14 | f.Id = group.Id
15 | f.Name = group.Name
16 | f.Color = group.Color
17 | f.UserId = group.UserId
18 | f.CollectionId = group.CollectionId
19 | return f
20 | }
21 |
22 | func (f *TagForm) ToTag() *model.Tag {
23 | i := &model.Tag{}
24 | i.Id = f.Id
25 | i.Name = f.Name
26 | i.Color = f.Color
27 | i.UserId = f.UserId
28 | i.CollectionId = f.CollectionId
29 | return i
30 | }
31 |
32 | type TagQuery struct {
33 | UserId int `form:"user_id"`
34 | IsMy int `form:"is_my"`
35 | CollectionId *int `form:"collection_id"`
36 | PageQuery
37 | }
38 |
--------------------------------------------------------------------------------
/model/loginLog.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type LoginLog struct {
4 | IdModel
5 | UserId uint `json:"user_id" gorm:"default:0;not null;"`
6 | Client string `json:"client"` //webadmin,webclient,app,
7 | DeviceId string `json:"device_id"`
8 | Uuid string `json:"uuid"`
9 | Ip string `json:"ip"`
10 | Type string `json:"type"` //account,oauth
11 | Platform string `json:"platform"` //windows,linux,mac,android,ios
12 | UserTokenId uint `json:"user_token_id" gorm:"default:0;not null;"`
13 | IsDeleted uint `json:"is_deleted" gorm:"default:0;not null;"`
14 | TimeModel
15 | }
16 |
17 | const (
18 | LoginLogClientWebAdmin = "webadmin"
19 | LoginLogClientWeb = "webclient"
20 | LoginLogClientApp = "app"
21 | )
22 |
23 | const (
24 | LoginLogTypeAccount = "account"
25 | LoginLogTypeOauth = "oauth"
26 | )
27 |
28 | const (
29 | IsDeletedNo = 0
30 | IsDeletedYes = 1
31 | )
32 |
33 | type LoginLogList struct {
34 | LoginLogs []*LoginLog `json:"list"`
35 | Pagination
36 | }
37 |
--------------------------------------------------------------------------------
/docker-dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Define Docker Compose file and cache option
5 | COMPOSE_FILE_NAME="docker-compose-dev.yaml"
6 | CACHE=""
7 | # Uncomment the next line to enable no-cache option
8 | # CACHE="--no-cache"
9 |
10 | # Define the base Docker Compose command
11 | DCS="docker compose -f ${COMPOSE_FILE_NAME}"
12 |
13 | # Function to build and start services
14 | build_and_run() {
15 | echo "Building services..."
16 | if ! $DCS build ${CACHE}; then
17 | echo "Error: Failed to build services"
18 | exit 1
19 | fi
20 |
21 | echo "Starting services..."
22 | if ! $DCS up -d; then
23 | echo "Error: Failed to start services"
24 | exit 1
25 | fi
26 | echo "Services started successfully"
27 | echo "If you want to stop the services, run"
28 | echo "docker compose -f ${COMPOSE_FILE_NAME} down"
29 |
30 | echo "If you want to see the logs, run"
31 | echo "docker compose -f ${COMPOSE_FILE_NAME} logs -f"
32 | }
33 |
34 | # Execute build and start function
35 | build_and_run
--------------------------------------------------------------------------------
/resources/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rustdesk",
3 | "short_name": "rustdesk",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "Remote Desktop.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/config/gorm.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | const (
4 | TypeSqlite = "sqlite"
5 | TypeMysql = "mysql"
6 | TypePostgresql = "postgresql"
7 | )
8 |
9 | type Gorm struct {
10 | Type string `mapstructure:"type"`
11 | MaxIdleConns int `mapstructure:"max-idle-conns"`
12 | MaxOpenConns int `mapstructure:"max-open-conns"`
13 | }
14 |
15 | type Mysql struct {
16 | Addr string `mapstructure:"addr"`
17 | Username string `mapstructure:"username"`
18 | Password string `mapstructure:"password"`
19 | Dbname string `mapstructure:"dbname"`
20 | Tls string `mapstructure:"tls"` // true / false / skip-verify / custom
21 | }
22 |
23 | type Postgresql struct {
24 | Host string `mapstructure:"host"`
25 | Port string `mapstructure:"port"`
26 | User string `mapstructure:"user"`
27 | Password string `mapstructure:"password"`
28 | Dbname string `mapstructure:"dbname"`
29 | Sslmode string `mapstructure:"sslmode"` // "disable", "require", "verify-ca", "verify-full"
30 | TimeZone string `mapstructure:"time-zone"` // e.g., "Asia/Shanghai"
31 | }
32 |
--------------------------------------------------------------------------------
/http/middleware/admin.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/http/response"
6 | "github.com/lejianwen/rustdesk-api/v2/service"
7 | )
8 |
9 | // BackendUserAuth 后台权限验证中间件
10 | func BackendUserAuth() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 |
13 | //测试先关闭
14 | token := c.GetHeader("api-token")
15 | if token == "" {
16 | response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
17 | c.Abort()
18 | return
19 | }
20 | user, ut := service.AllService.UserService.InfoByAccessToken(token)
21 | if user.Id == 0 {
22 | response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
23 | c.Abort()
24 | return
25 | }
26 |
27 | if !service.AllService.UserService.CheckUserEnable(user) {
28 | c.JSON(401, gin.H{
29 | "error": "Unauthorized",
30 | })
31 | c.Abort()
32 | return
33 | }
34 |
35 | c.Set("curUser", user)
36 | c.Set("token", token)
37 | //如果时间小于1天,token自动续期
38 | service.AllService.UserService.AutoRefreshAccessToken(ut)
39 |
40 | c.Next()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/rustdesk.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | const (
8 | DefaultIdServerPort = 21116
9 | DefaultRelayServerPort = 21117
10 | )
11 |
12 | type Rustdesk struct {
13 | IdServer string `mapstructure:"id-server"`
14 | IdServerPort int `mapstructure:"-"`
15 | RelayServer string `mapstructure:"relay-server"`
16 | RelayServerPort int `mapstructure:"-"`
17 | ApiServer string `mapstructure:"api-server"`
18 | Key string `mapstructure:"key"`
19 | KeyFile string `mapstructure:"key-file"`
20 | Personal int `mapstructure:"personal"`
21 | //webclient-magic-queryonline
22 | WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
23 | WsHost string `mapstructure:"ws-host"`
24 | }
25 |
26 | func (rd *Rustdesk) LoadKeyFile() {
27 | // Load key file
28 | if rd.Key != "" {
29 | return
30 | }
31 | if rd.KeyFile != "" {
32 | // Load key from file
33 | b, err := os.ReadFile(rd.KeyFile)
34 | if err != nil {
35 | return
36 | }
37 | rd.Key = string(b)
38 | return
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/cache/redis.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | "time"
7 | )
8 |
9 | var ctx = context.Background()
10 |
11 | type RedisCache struct {
12 | rdb *redis.Client
13 | }
14 |
15 | func RedisCacheInit(conf *redis.Options) *RedisCache {
16 | c := &RedisCache{}
17 | c.rdb = redis.NewClient(conf)
18 | return c
19 | }
20 |
21 | func (c *RedisCache) Get(key string, value interface{}) error {
22 | data, err := c.rdb.Get(ctx, key).Result()
23 | if err != nil {
24 | return err
25 | }
26 | err1 := DecodeValue(data, value)
27 | return err1
28 | }
29 |
30 | func (c *RedisCache) Set(key string, value interface{}, exp int) error {
31 | str, err := EncodeValue(value)
32 | if err != nil {
33 | return err
34 | }
35 | if exp <= 0 {
36 | exp = MaxTimeOut
37 | }
38 | _, err1 := c.rdb.Set(ctx, key, str, time.Duration(exp)*time.Second).Result()
39 | return err1
40 | }
41 |
42 | func (c *RedisCache) Gc() error {
43 | return nil
44 | }
45 |
46 | func NewRedis(conf *redis.Options) *RedisCache {
47 | cache := RedisCacheInit(conf)
48 | return cache
49 | }
50 |
--------------------------------------------------------------------------------
/http/controller/web/index.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/lejianwen/rustdesk-api/v2/global"
7 | )
8 |
9 | type Index struct {
10 | }
11 |
12 | func (i *Index) Index(c *gin.Context) {
13 | c.Redirect(302, "/_admin/")
14 | }
15 |
16 | func (i *Index) ConfigJs(c *gin.Context) {
17 | apiServer := global.Config.Rustdesk.ApiServer
18 | magicQueryonline := global.Config.Rustdesk.WebclientMagicQueryonline
19 | tmp := fmt.Sprintf(`localStorage.setItem('api-server', '%v');
20 | const ws2_prefix = 'wc-';
21 | localStorage.setItem(ws2_prefix+'api-server', '%v');
22 |
23 | window.webclient_magic_queryonline = %d;
24 | window.ws_host = '%v';
25 | `, apiServer, apiServer, magicQueryonline, global.Config.Rustdesk.WsHost)
26 | // tmp := `
27 | //localStorage.setItem('api-server', "` + apiServer + `")
28 | //const ws2_prefix = 'wc-'
29 | //localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
30 | //
31 | //window.webclient_magic_queryonline = ` + magicQueryonline + ``
32 |
33 | c.Header("Content-Type", "application/javascript")
34 | c.String(200, tmp)
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-present Lejianwen and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/debian/copyright:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-present Lejianwen and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/resources/web/js/ts_proto.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 |
5 | path = os.path.abspath(os.path.join(os.getcwd(), '..', '..', '..', 'libs', 'hbb_common', 'protos'))
6 |
7 | if os.name == 'nt':
8 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd -I "%s" --ts_proto_out=./src/ rendezvous.proto'%path
9 | print(cmd)
10 | os.system(cmd)
11 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd -I "%s" --ts_proto_out=./src/ message.proto'%path
12 | print(cmd)
13 | os.system(cmd)
14 | else:
15 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=./node_modules/.bin/protoc-gen-ts_proto -I "%s" --ts_proto_out=./src/ rendezvous.proto'%path
16 | print(cmd)
17 | os.system(cmd)
18 | cmd = r'protoc --ts_proto_opt=esModuleInterop=true --ts_proto_opt=snakeToCamel=false --plugin=./node_modules/.bin/protoc-gen-ts_proto -I "%s" --ts_proto_out=./src/ message.proto'%path
19 | print(cmd)
20 | os.system(cmd)
21 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/COPYING:
--------------------------------------------------------------------------------
1 | ogv.js wrapper and player code
2 |
3 | Copyright (c) 2013-2019 Brion Vibber and other contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/http/controller/api/user.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
6 | "github.com/lejianwen/rustdesk-api/v2/service"
7 | "net/http"
8 | )
9 |
10 | type User struct {
11 | }
12 |
13 | // currentUser 当前用户
14 | // @Tags 用户
15 | // @Summary 用户信息
16 | // @Description 用户信息
17 | // @Accept json
18 | // @Produce json
19 | // @Success 200 {object} apiResp.UserPayload
20 | // @Failure 500 {object} response.Response
21 | // @Router /currentUser [get]
22 | // @Security token
23 | //func (u *User) currentUser(c *gin.Context) {
24 | // user := service.AllService.UserService.CurUser(c)
25 | // up := (&apiResp.UserPayload{}).FromName(user)
26 | // c.JSON(http.StatusOK, up)
27 | //}
28 |
29 | // Info 用户信息
30 | // @Tags 用户
31 | // @Summary 用户信息
32 | // @Description 用户信息
33 | // @Accept json
34 | // @Produce json
35 | // @Success 200 {object} apiResp.UserPayload
36 | // @Failure 500 {object} response.Response
37 | // @Router /currentUser [get]
38 | // @Security token
39 | func (u *User) Info(c *gin.Context) {
40 | user := service.AllService.UserService.CurUser(c)
41 | up := (&apiResp.UserPayload{}).FromUser(user)
42 | c.JSON(http.StatusOK, up)
43 | }
44 |
--------------------------------------------------------------------------------
/utils/password_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "testing"
5 |
6 | "golang.org/x/crypto/bcrypt"
7 | )
8 |
9 | func TestVerifyPasswordMD5(t *testing.T) {
10 | hash := Md5("secret" + "rustdesk-api")
11 | ok, newHash, err := VerifyPassword(hash, "secret")
12 | if err != nil {
13 | t.Fatalf("md5 verify failed: %v", err)
14 | }
15 | if !ok || newHash == "" {
16 | t.Fatalf("md5 migration failed")
17 | }
18 | if bcrypt.CompareHashAndPassword([]byte(newHash), []byte("secret")) != nil {
19 | t.Fatalf("invalid rehash")
20 | }
21 | }
22 |
23 | func TestVerifyPasswordBcrypt(t *testing.T) {
24 | b, _ := bcrypt.GenerateFromPassword([]byte("pass"), bcrypt.DefaultCost)
25 | ok, newHash, err := VerifyPassword(string(b), "pass")
26 | if err != nil || !ok || newHash != "" {
27 | t.Fatalf("bcrypt verify failed")
28 | }
29 | }
30 |
31 | func TestVerifyPasswordMigrate(t *testing.T) {
32 | md5hash := Md5("mypass" + "rustdesk-api")
33 | ok, newHash, err := VerifyPassword(md5hash, "mypass")
34 | if err != nil || !ok || newHash == "" {
35 | t.Fatalf("expected bcrypt rehash")
36 | }
37 | if bcrypt.CompareHashAndPassword([]byte(newHash), []byte("mypass")) != nil {
38 | t.Fatalf("rehash not valid bcrypt")
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | nested "github.com/antonfisher/nested-logrus-formatter"
5 | log "github.com/sirupsen/logrus"
6 | "io"
7 | "os"
8 | )
9 |
10 | const (
11 | DebugMode = "debug"
12 | ReleaseMode = "release"
13 | )
14 |
15 | type Config struct {
16 | Path string
17 | Level string
18 | ReportCaller bool
19 | }
20 |
21 | func New(c *Config) *log.Logger {
22 | log.SetFormatter(&nested.Formatter{
23 | // HideKeys: true,
24 | TimestampFormat: "[2006-01-02 15:04:05]",
25 | NoColors: true,
26 | NoFieldsColors: true,
27 | //FieldsOrder: []string{"name", "age"},
28 | })
29 |
30 | // 日志文件
31 | f := c.Path
32 | var write io.Writer
33 | if f != "" {
34 | fwriter, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
35 | if err != nil {
36 | panic("open log file fail!")
37 | }
38 | write = io.MultiWriter(fwriter, os.Stdout)
39 | } else {
40 | write = os.Stdout
41 | }
42 |
43 | log.SetOutput(write)
44 |
45 | log.SetReportCaller(c.ReportCaller)
46 |
47 | level, err2 := log.ParseLevel(c.Level)
48 | if err2 != nil {
49 | level = log.DebugLevel
50 | }
51 | log.SetLevel(level)
52 |
53 | return log.StandardLogger()
54 | }
55 |
--------------------------------------------------------------------------------
/model/peer.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type Peer struct {
4 | RowId uint `json:"row_id" gorm:"primaryKey;"`
5 | Id string `json:"id" gorm:"default:'';not null;index"`
6 | Cpu string `json:"cpu" gorm:"default:'';not null;"`
7 | Hostname string `json:"hostname" gorm:"default:'';not null;"`
8 | Memory string `json:"memory" gorm:"default:'';not null;"`
9 | Os string `json:"os" gorm:"default:'';not null;"`
10 | Username string `json:"username" gorm:"default:'';not null;"`
11 | Uuid string `json:"uuid" gorm:"default:'';not null;index"`
12 | Version string `json:"version" gorm:"default:'';not null;"`
13 | UserId uint `json:"user_id" gorm:"default:0;not null;index"`
14 | User *User `json:"user,omitempty"`
15 | LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
16 | LastOnlineIp string `json:"last_online_ip" gorm:"default:'';not null;"`
17 | GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
18 | Alias string `json:"alias" gorm:"default:'';not null;index"`
19 | TimeModel
20 | }
21 |
22 | type PeerList struct {
23 | Peers []*Peer `json:"list"`
24 | Pagination
25 | }
26 |
--------------------------------------------------------------------------------
/lib/orm/postgresql.go:
--------------------------------------------------------------------------------
1 | package orm
2 |
3 | import (
4 | "fmt"
5 | "gorm.io/driver/postgres"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "time"
9 | )
10 |
11 | type PostgresqlConfig struct {
12 | Dsn string
13 | MaxIdleConns int
14 | MaxOpenConns int
15 | }
16 |
17 | func NewPostgresql(conf *PostgresqlConfig, logwriter logger.Writer) *gorm.DB {
18 | db, err := gorm.Open(postgres.Open(conf.Dsn), &gorm.Config{
19 | DisableForeignKeyConstraintWhenMigrating: true,
20 | Logger: logger.New(
21 | logwriter, // io writer
22 | logger.Config{
23 | SlowThreshold: time.Second, // Slow SQL threshold
24 | LogLevel: logger.Warn, // Log level
25 | //IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
26 | ParameterizedQueries: true, // Don't include params in the SQL log
27 | Colorful: true,
28 | },
29 | ),
30 | })
31 | if err != nil {
32 | fmt.Println(err)
33 | }
34 | sqlDB, err2 := db.DB()
35 | if err2 != nil {
36 | fmt.Println(err2)
37 | }
38 | // SetMaxIdleConns 设置空闲连接池中连接的最大数量
39 | sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
40 |
41 | // SetMaxOpenConns 设置打开数据库连接的最大数量。
42 | sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
43 |
44 | return db
45 | }
46 |
--------------------------------------------------------------------------------
/lib/orm/sqlite.go:
--------------------------------------------------------------------------------
1 | package orm
2 |
3 | import (
4 | "fmt"
5 | "gorm.io/driver/sqlite"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "time"
9 | )
10 |
11 | type SqliteConfig struct {
12 | MaxIdleConns int
13 | MaxOpenConns int
14 | }
15 |
16 | func NewSqlite(sqliteConf *SqliteConfig, logwriter logger.Writer) *gorm.DB {
17 | db, err := gorm.Open(sqlite.Open("./data/rustdeskapi.db"), &gorm.Config{
18 | DisableForeignKeyConstraintWhenMigrating: true,
19 | Logger: logger.New(
20 | logwriter, // io writer
21 | logger.Config{
22 | SlowThreshold: time.Second, // Slow SQL threshold
23 | LogLevel: logger.Warn, // Log level
24 | IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
25 | ParameterizedQueries: true, // Don't include params in the SQL log
26 | Colorful: true,
27 | },
28 | ),
29 | })
30 | if err != nil {
31 | fmt.Println(err)
32 | }
33 | sqlDB, err2 := db.DB()
34 | if err2 != nil {
35 | fmt.Println(err2)
36 | }
37 | // SetMaxIdleConns 设置空闲连接池中连接的最大数量
38 | sqlDB.SetMaxIdleConns(sqliteConf.MaxIdleConns)
39 |
40 | // SetMaxOpenConns 设置打开数据库连接的最大数量。
41 | sqlDB.SetMaxOpenConns(sqliteConf.MaxOpenConns)
42 |
43 | return db
44 | }
45 |
--------------------------------------------------------------------------------
/http/http.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/http/middleware"
7 | "github.com/lejianwen/rustdesk-api/v2/http/router"
8 | "github.com/sirupsen/logrus"
9 | "net/http"
10 | "strings"
11 | )
12 |
13 | func ApiInit() {
14 | gin.SetMode(global.Config.Gin.Mode)
15 | g := gin.New()
16 |
17 | //[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
18 | //Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
19 | if global.Config.Gin.TrustProxy != "" {
20 | pro := strings.Split(global.Config.Gin.TrustProxy, ",")
21 | err := g.SetTrustedProxies(pro)
22 | if err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | if global.Config.Gin.Mode == gin.ReleaseMode {
28 | //修改gin Recovery日志 输出为logger的输出点
29 | if global.Logger != nil {
30 | gin.DefaultErrorWriter = global.Logger.WriterLevel(logrus.ErrorLevel)
31 | }
32 | }
33 | g.NoRoute(func(c *gin.Context) {
34 | c.String(http.StatusNotFound, "404 not found")
35 | })
36 | g.Use(middleware.Logger(), middleware.Limiter(), gin.Recovery())
37 | router.WebInit(g)
38 | router.Init(g)
39 | router.ApiInit(g)
40 | Run(g, global.Config.Gin.ApiAddr)
41 | }
42 |
--------------------------------------------------------------------------------
/model/user.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type User struct {
4 | IdModel
5 | Username string `json:"username" gorm:"default:'';not null;uniqueIndex"`
6 | Email string `json:"email" gorm:"default:'';not null;index"`
7 | // Email string `json:"email" `
8 | Password string `json:"-" gorm:"default:'';not null;"`
9 | Nickname string `json:"nickname" gorm:"default:'';not null;"`
10 | Avatar string `json:"avatar" gorm:"default:'';not null;"`
11 | GroupId uint `json:"group_id" gorm:"default:0;not null;index"`
12 | IsAdmin *bool `json:"is_admin" gorm:"default:0;not null;"`
13 | Status StatusCode `json:"status" gorm:"default:1;not null;"`
14 | Remark string `json:"remark" gorm:"default:'';not null;"`
15 | TimeModel
16 | }
17 |
18 | // BeforeSave 钩子用于确保 email 字段有合理的默认值
19 | //func (u *User) BeforeSave(tx *gorm.DB) (err error) {
20 | // // 如果 email 为空,设置为默认值
21 | // if u.Email == "" {
22 | // u.Email = fmt.Sprintf("%s@example.com", u.Username)
23 | // }
24 | // return nil
25 | //}
26 |
27 | type UserList struct {
28 | Users []*User `json:"list,omitempty"`
29 | Pagination
30 | }
31 |
32 | var UserRouteNames = []string{
33 | "MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyPeer", "MyShareRecordList", "MyLoginLog",
34 | }
35 | var AdminRouteNames = []string{"*"}
36 |
--------------------------------------------------------------------------------
/http/response/api/webClient.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | "time"
6 | )
7 |
8 | type WebClientPeerPayload struct {
9 | ViewStyle string `json:"view-style"`
10 | Tm int64 `json:"tm"`
11 | Info WebClientPeerInfoPayload `json:"info"`
12 | Tmppwd string `json:"tmppwd"`
13 | }
14 |
15 | type WebClientPeerInfoPayload struct {
16 | Username string `json:"username"`
17 | Hostname string `json:"hostname"`
18 | Platform string `json:"platform"`
19 | Hash string `json:"hash"`
20 | Id string `json:"id"`
21 | }
22 |
23 | func (wcpp *WebClientPeerPayload) FromAddressBook(a *model.AddressBook) {
24 | wcpp.ViewStyle = "shrink"
25 | //24小时前
26 | wcpp.Tm = time.Now().Add(-time.Hour * 24).UnixNano()
27 | wcpp.Info = WebClientPeerInfoPayload{
28 | Username: a.Username,
29 | Hostname: a.Hostname,
30 | Platform: a.Platform,
31 | Hash: a.Hash,
32 | }
33 | }
34 |
35 | func (wcpp *WebClientPeerPayload) FromShareRecord(sr *model.ShareRecord) {
36 | wcpp.ViewStyle = "shrink"
37 | //24小时前
38 | wcpp.Tm = time.Now().UnixNano()
39 | wcpp.Tmppwd = sr.Password
40 | wcpp.Info = WebClientPeerInfoPayload{
41 | Username: "",
42 | Hostname: "",
43 | Platform: "",
44 | Id: sr.PeerId,
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/http/middleware/jwt.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/http/response"
7 | "github.com/lejianwen/rustdesk-api/v2/service"
8 | )
9 |
10 | func JwtAuth() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | //测试先关闭
13 | token := c.GetHeader("api-token")
14 | if token == "" {
15 | response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
16 | c.Abort()
17 | return
18 | }
19 | uid, err := global.Jwt.ParseToken(token)
20 | if err != nil {
21 | response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
22 | c.Abort()
23 | return
24 | }
25 | if uid == 0 {
26 | response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
27 | c.Abort()
28 | return
29 | }
30 |
31 | user := service.AllService.UserService.InfoById(uid)
32 | //user := &model.User{
33 | // Id: uid,
34 | // Username: "测试用户",
35 | //}
36 | if user.Id == 0 {
37 | response.Fail(c, 403, response.TranslateMsg(c, "NeedLogin"))
38 | c.Abort()
39 | return
40 | }
41 | if !service.AllService.UserService.CheckUserEnable(user) {
42 | response.Fail(c, 101, response.TranslateMsg(c, "Banned"))
43 | c.Abort()
44 | return
45 | }
46 | c.Set("curUser", user)
47 |
48 | c.Next()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/resources/web/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/global/global.go:
--------------------------------------------------------------------------------
1 | package global
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | ut "github.com/go-playground/universal-translator"
6 | "github.com/go-playground/validator/v10"
7 | "github.com/go-redis/redis/v8"
8 | "github.com/lejianwen/rustdesk-api/v2/config"
9 | "github.com/lejianwen/rustdesk-api/v2/lib/cache"
10 | "github.com/lejianwen/rustdesk-api/v2/lib/jwt"
11 | "github.com/lejianwen/rustdesk-api/v2/lib/lock"
12 | "github.com/lejianwen/rustdesk-api/v2/lib/upload"
13 | "github.com/lejianwen/rustdesk-api/v2/utils"
14 | "github.com/nicksnyder/go-i18n/v2/i18n"
15 | "github.com/sirupsen/logrus"
16 | "github.com/spf13/viper"
17 | "gorm.io/gorm"
18 | )
19 |
20 | var (
21 | DB *gorm.DB
22 | Logger *logrus.Logger
23 | ConfigPath string = ""
24 | Config config.Config
25 | Viper *viper.Viper
26 | Redis *redis.Client
27 | Cache cache.Handler
28 | Validator struct {
29 | Validate *validator.Validate
30 | UT *ut.UniversalTranslator
31 | VTrans ut.Translator
32 | ValidStruct func(*gin.Context, interface{}) []string
33 | ValidVar func(ctx *gin.Context, field interface{}, tag string) []string
34 | }
35 | Oss *upload.Oss
36 | Jwt *jwt.Jwt
37 | Lock lock.Locker
38 | Localizer func(lang string) *i18n.Localizer
39 | LoginLimiter *utils.LoginLimiter
40 | )
41 |
--------------------------------------------------------------------------------
/service/shareRecord.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | "gorm.io/gorm"
6 | )
7 |
8 | type ShareRecordService struct {
9 | }
10 |
11 | // InfoById 根据用户id取用户信息
12 | func (srs *ShareRecordService) InfoById(id uint) *model.ShareRecord {
13 | u := &model.ShareRecord{}
14 | DB.Where("id = ?", id).First(u)
15 | return u
16 | }
17 |
18 | func (srs *ShareRecordService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.ShareRecordList) {
19 | res = &model.ShareRecordList{}
20 | res.Page = int64(page)
21 | res.PageSize = int64(pageSize)
22 | tx := DB.Model(&model.ShareRecord{})
23 | if where != nil {
24 | where(tx)
25 | }
26 | tx.Count(&res.Total)
27 | tx.Scopes(Paginate(page, pageSize))
28 | tx.Find(&res.ShareRecords)
29 | return
30 | }
31 |
32 | // Create 创建
33 | func (srs *ShareRecordService) Create(u *model.ShareRecord) error {
34 | res := DB.Create(u).Error
35 | return res
36 | }
37 | func (srs *ShareRecordService) Delete(u *model.ShareRecord) error {
38 | return DB.Delete(u).Error
39 | }
40 |
41 | // Update 更新
42 | func (srs *ShareRecordService) Update(u *model.ShareRecord) error {
43 | return DB.Model(u).Updates(u).Error
44 | }
45 |
46 | func (srs *ShareRecordService) BatchDelete(ids []uint) error {
47 | return DB.Where("id in (?)", ids).Delete(&model.ShareRecord{}).Error
48 | }
49 |
--------------------------------------------------------------------------------
/http/request/admin/oauth.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | )
6 |
7 | type BindOauthForm struct {
8 | Op string `json:"op" binding:"required"`
9 | }
10 |
11 | type OauthConfirmForm struct {
12 | Code string `json:"code" binding:"required"`
13 | }
14 | type UnBindOauthForm struct {
15 | Op string `json:"op" binding:"required"`
16 | }
17 | type OauthForm struct {
18 | Id uint `json:"id"`
19 | Op string `json:"op" validate:"omitempty"`
20 | OauthType string `json:"oauth_type" validate:"required"`
21 | Issuer string `json:"issuer" validate:"omitempty,url"`
22 | Scopes string `json:"scopes" validate:"omitempty"`
23 | ClientId string `json:"client_id" validate:"required"`
24 | ClientSecret string `json:"client_secret" validate:"required"`
25 | AutoRegister *bool `json:"auto_register"`
26 | PkceEnable *bool `json:"pkce_enable"`
27 | PkceMethod string `json:"pkce_method"`
28 | }
29 |
30 | func (of *OauthForm) ToOauth() *model.Oauth {
31 | oa := &model.Oauth{
32 | Op: of.Op,
33 | OauthType: of.OauthType,
34 | ClientId: of.ClientId,
35 | ClientSecret: of.ClientSecret,
36 | AutoRegister: of.AutoRegister,
37 | Issuer: of.Issuer,
38 | Scopes: of.Scopes,
39 | PkceEnable: of.PkceEnable,
40 | PkceMethod: of.PkceMethod,
41 | }
42 | oa.Id = of.Id
43 | return oa
44 | }
45 |
--------------------------------------------------------------------------------
/Dockerfile_full_s6:
--------------------------------------------------------------------------------
1 | FROM rustdesk/rustdesk-server-s6:latest AS server
2 |
3 | FROM alpine
4 |
5 | ARG BUILDARCH
6 | WORKDIR /app
7 | RUN apk add --no-cache tzdata
8 | COPY ./${BUILDARCH}/release /app/
9 |
10 | COPY --from=server /init /init
11 | COPY --from=server /etc/s6-overlay /etc/s6-overlay
12 | COPY --from=server /package /package
13 | COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh
14 | COPY --from=server /usr/bin/hbbr /usr/bin/hbbr
15 | COPY --from=server /usr/bin/hbbs /usr/bin/hbbs
16 | COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils
17 | COPY --from=server /command /command
18 |
19 | RUN \
20 | mkdir -p /etc/s6-overlay/s6-rc.d/api && \
21 | echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \
22 | echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
23 | echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \
24 | echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \
25 | echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \
26 | touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
27 | echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \
28 | ln -s /run /var/run
29 |
30 | ENV RELAY=relay.example.com
31 | ENV ENCRYPTED_ONLY=0
32 |
33 | VOLUME /data
34 | VOLUME /app/data
35 |
36 | EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119
37 |
38 | ENTRYPOINT ["/init"]
39 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/COPYING-dav1d.txt:
--------------------------------------------------------------------------------
1 | Copyright © 2018-2019, VideoLAN and dav1d authors
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/utils/password.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "golang.org/x/crypto/bcrypt"
6 | )
7 |
8 | // EncryptPassword hashes the input password using bcrypt.
9 | // An error is returned if hashing fails.
10 | func EncryptPassword(password string) (string, error) {
11 | bs, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
12 | if err != nil {
13 | return "", err
14 | }
15 | return string(bs), nil
16 | }
17 |
18 | // VerifyPassword checks the input password against the stored hash.
19 | // When a legacy MD5 hash is provided, the password is rehashed with bcrypt
20 | // and the new hash is returned. Any internal bcrypt error is returned.
21 | func VerifyPassword(hash, input string) (bool, string, error) {
22 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(input))
23 | if err == nil {
24 | return true, "", nil
25 | }
26 |
27 | var invalidPrefixErr bcrypt.InvalidHashPrefixError
28 | if errors.As(err, &invalidPrefixErr) || errors.Is(err, bcrypt.ErrHashTooShort) {
29 | // Try fallback to legacy MD5 hash verification
30 | if hash == Md5(input+"rustdesk-api") {
31 | newHash, err2 := bcrypt.GenerateFromPassword([]byte(input), bcrypt.DefaultCost)
32 | if err2 != nil {
33 | return true, "", err2
34 | }
35 | return true, string(newHash), nil
36 | }
37 | }
38 | if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
39 | return false, "", nil
40 | }
41 | return false, "", err
42 | }
43 |
--------------------------------------------------------------------------------
/lib/jwt/jwt.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "fmt"
5 | "github.com/golang-jwt/jwt/v5"
6 | "time"
7 | )
8 |
9 | type Jwt struct {
10 | Key []byte
11 | TokenExpireDuration time.Duration
12 | }
13 |
14 | type UserClaims struct {
15 | UserId uint `json:"user_id"`
16 | jwt.RegisteredClaims
17 | }
18 |
19 | func NewJwt(key string, tokenExpireDuration time.Duration) *Jwt {
20 | return &Jwt{
21 | Key: []byte(key),
22 | TokenExpireDuration: tokenExpireDuration,
23 | }
24 | }
25 |
26 | func (s *Jwt) GenerateToken(userId uint) string {
27 | if len(s.Key) == 0 {
28 | fmt.Println("jwt key is nil")
29 | return ""
30 | }
31 | t := jwt.NewWithClaims(jwt.SigningMethodHS256,
32 | UserClaims{
33 | UserId: userId,
34 | RegisteredClaims: jwt.RegisteredClaims{
35 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.TokenExpireDuration)),
36 | },
37 | })
38 | token, err := t.SignedString(s.Key)
39 | if err != nil {
40 | fmt.Printf("jwt token generate error: %v", err)
41 | return ""
42 | }
43 | return token
44 | }
45 |
46 | func (s *Jwt) ParseToken(tokenString string) (uint, error) {
47 | token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
48 | return s.Key, nil
49 | })
50 | if err != nil {
51 | return 0, err
52 | }
53 | if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
54 | return claims.UserId, nil
55 | }
56 | return 0, err
57 | }
58 |
--------------------------------------------------------------------------------
/lib/cache/simple_cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "sync"
7 | )
8 |
9 | // 此处实现了一个简单的缓存,用于测试
10 | // SimpleCache is a simple cache implementation
11 | type SimpleCache struct {
12 | data map[string]interface{}
13 | mu sync.Mutex
14 | maxBytes int64
15 | usedBytes int64
16 | }
17 |
18 | func (s *SimpleCache) Get(key string, value interface{}) error {
19 | s.mu.Lock()
20 | defer s.mu.Unlock()
21 |
22 | // 使用反射将存储的值设置到传入的指针变量中
23 | val := reflect.ValueOf(value)
24 | if val.Kind() != reflect.Ptr {
25 | return errors.New("value must be a pointer")
26 | }
27 | v, ok := s.data[key]
28 | if !ok {
29 | //设为空值
30 | val.Elem().Set(reflect.Zero(val.Elem().Type()))
31 | return nil
32 | }
33 |
34 | vval := reflect.ValueOf(v)
35 | if val.Elem().Type() != vval.Type() {
36 | //设为空值
37 | val.Elem().Set(reflect.Zero(val.Elem().Type()))
38 | return nil
39 | }
40 |
41 | val.Elem().Set(reflect.ValueOf(v))
42 | return nil
43 | }
44 |
45 | func (s *SimpleCache) Set(key string, value interface{}, exp int) error {
46 | s.mu.Lock()
47 | defer s.mu.Unlock()
48 | // 检查传入的值是否是指针,如果是则取其值
49 | val := reflect.ValueOf(value)
50 | if val.Kind() == reflect.Ptr {
51 | val = val.Elem()
52 | }
53 |
54 | s.data[key] = val.Interface()
55 | return nil
56 | }
57 | func (s *SimpleCache) Gc() error {
58 | return nil
59 | }
60 |
61 | func NewSimpleCache() *SimpleCache {
62 | return &SimpleCache{
63 | data: make(map[string]interface{}),
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/global/i18n.go:
--------------------------------------------------------------------------------
1 | package global
2 |
3 | import (
4 | "github.com/BurntSushi/toml"
5 | "github.com/nicksnyder/go-i18n/v2/i18n"
6 | "golang.org/x/text/language"
7 | "os"
8 | )
9 |
10 | func InitI18n() {
11 | bundle := i18n.NewBundle(language.English)
12 | bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
13 | //读取global.Config.Gin.ResourcesPath下的所有语言文件
14 | dir := Config.Gin.ResourcesPath + "/i18n"
15 | fileInfos, err := os.ReadDir(dir)
16 | if err != nil {
17 | panic(err)
18 | }
19 | for _, fileInfo := range fileInfos {
20 | //如果文件名不是.toml结尾
21 | if fileInfo.IsDir() || fileInfo.Name()[len(fileInfo.Name())-5:] != ".toml" {
22 | continue
23 | }
24 | bundle.LoadMessageFile(Config.Gin.ResourcesPath + "/i18n/" + fileInfo.Name())
25 | }
26 | Localizer = func(lang string) *i18n.Localizer {
27 | if lang == "" {
28 | lang = Config.Lang
29 | }
30 | if lang == "en" {
31 | return i18n.NewLocalizer(bundle, "en")
32 | } else {
33 | return i18n.NewLocalizer(bundle, lang, "en")
34 | }
35 | }
36 |
37 | //personUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{
38 | // DefaultMessage: &i18n.Message{
39 | // ID: "PersonUnreadEmails",
40 | // },
41 | // PluralCount: 6,
42 | // TemplateData: map[string]interface{}{
43 | // "Name": "LE",
44 | // "PluralCount": 6,
45 | // },
46 | //})
47 | //personUnreadEmails, err := global.Localizer.LocalizeMessage(&i18n.Message{
48 | // ID: "ParamsError",
49 | //})
50 | //fmt.Println(err, personUnreadEmails)
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/http/request/admin/peer.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | type PeerForm struct {
6 | RowId uint `json:"row_id" `
7 | Id string `json:"id"`
8 | Cpu string `json:"cpu"`
9 | Hostname string `json:"hostname"`
10 | Memory string `json:"memory"`
11 | Os string `json:"os"`
12 | Username string `json:"username"`
13 | Uuid string `json:"uuid"`
14 | Version string `json:"version"`
15 | GroupId uint `json:"group_id"`
16 | Alias string `json:"alias"`
17 | }
18 |
19 | type PeerBatchDeleteForm struct {
20 | RowIds []uint `json:"row_ids" validate:"required"`
21 | }
22 |
23 | // ToPeer
24 | func (f *PeerForm) ToPeer() *model.Peer {
25 | return &model.Peer{
26 | RowId: f.RowId,
27 | Id: f.Id,
28 | Cpu: f.Cpu,
29 | Hostname: f.Hostname,
30 | Memory: f.Memory,
31 | Os: f.Os,
32 | Username: f.Username,
33 | Uuid: f.Uuid,
34 | Version: f.Version,
35 | GroupId: f.GroupId,
36 | Alias: f.Alias,
37 | }
38 | }
39 |
40 | type PeerQuery struct {
41 | PageQuery
42 | TimeAgo int `json:"time_ago" form:"time_ago"`
43 | Id string `json:"id" form:"id"`
44 | Hostname string `json:"hostname" form:"hostname"`
45 | Uuids string `json:"uuids" form:"uuids"`
46 | Ip string `json:"ip" form:"ip"`
47 | Username string `json:"username" form:"username"`
48 | Alias string `json:"alias" form:"alias"`
49 | }
50 |
51 | type SimpleDataQuery struct {
52 | Ids []string `json:"ids" form:"ids"`
53 | }
54 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/PATENTS-vpx.txt:
--------------------------------------------------------------------------------
1 | Additional IP Rights Grant (Patents)
2 | ------------------------------------
3 |
4 | "These implementations" means the copyrightable works that implement the WebM
5 | codecs distributed by Google as part of the WebM Project.
6 |
7 | Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
8 | royalty-free, irrevocable (except as stated in this section) patent license to
9 | make, have made, use, offer to sell, sell, import, transfer, and otherwise
10 | run, modify and propagate the contents of these implementations of WebM, where
11 | such license applies only to those patent claims, both currently owned by
12 | Google and acquired in the future, licensable by Google that are necessarily
13 | infringed by these implementations of WebM. This grant does not include claims
14 | that would be infringed only as a consequence of further modification of these
15 | implementations. If you or your agent or exclusive licensee institute or order
16 | or agree to the institution of patent litigation or any other patent
17 | enforcement activity against any entity (including a cross-claim or
18 | counterclaim in a lawsuit) alleging that any of these implementations of WebM
19 | or any code incorporated within any of these implementations of WebM
20 | constitute direct or contributory patent infringement, or inducement of
21 | patent infringement, then any patent rights granted to you under this License
22 | for these implementations of WebM shall terminate as of the date such
23 | litigation is filed.
24 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/COPYING-ogg.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2002, Xiph.org Foundation
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 |
7 | - Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 |
10 | - Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | - Neither the name of the Xiph.org Foundation nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/COPYING-theora.txt:
--------------------------------------------------------------------------------
1 | Copyright (C) 2002-2009 Xiph.org Foundation
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 |
7 | - Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 |
10 | - Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | - Neither the name of the Xiph.org Foundation nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/COPYING-vorbis.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2002-2018 Xiph.org Foundation
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions
5 | are met:
6 |
7 | - Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 |
10 | - Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | - Neither the name of the Xiph.org Foundation nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
22 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/utils/captcha.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/mojocn/base64Captcha"
5 | "time"
6 | )
7 |
8 | var capdString = base64Captcha.NewDriverString(50, 150, 0, 5, 4, "123456789abcdefghijklmnopqrstuvwxyz", nil, nil,
9 | []string{"3Dumb.ttf", "ApothecaryFont.ttf", "Comismsh.ttf", "Flim-Flam.ttf", "RitaSmith.ttf", "wqy-microhei.ttc"})
10 |
11 | var capdMath = base64Captcha.NewDriverMath(50, 150, 3, 10, nil, nil, nil)
12 |
13 | type B64StringCaptchaProvider struct{}
14 |
15 | func (p B64StringCaptchaProvider) Generate() (string, string, string, error) {
16 | id, content, answer := capdString.GenerateIdQuestionAnswer()
17 | return id, content, answer, nil
18 | }
19 |
20 | func (p B64StringCaptchaProvider) Expiration() time.Duration {
21 | return 5 * time.Minute
22 | }
23 | func (p B64StringCaptchaProvider) Draw(content string) (string, error) {
24 | item, err := capdString.DrawCaptcha(content)
25 | if err != nil {
26 | return "", err
27 | }
28 | b64str := item.EncodeB64string()
29 | return b64str, nil
30 | }
31 |
32 | type B64MathCaptchaProvider struct{}
33 |
34 | func (p B64MathCaptchaProvider) Generate() (string, string, string, error) {
35 | id, content, answer := capdMath.GenerateIdQuestionAnswer()
36 | return id, content, answer, nil
37 | }
38 |
39 | func (p B64MathCaptchaProvider) Expiration() time.Duration {
40 | return 5 * time.Minute
41 | }
42 | func (p B64MathCaptchaProvider) Draw(content string) (string, error) {
43 | item, err := capdMath.DrawCaptcha(content)
44 | if err != nil {
45 | return "", err
46 | }
47 | b64str := item.EncodeB64string()
48 | return b64str, nil
49 | }
50 |
--------------------------------------------------------------------------------
/http/middleware/rustauth.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/service"
7 | )
8 |
9 | func RustAuth() gin.HandlerFunc {
10 | return func(c *gin.Context) {
11 | //fmt.Println(c.Request.URL, c.Request.Header)
12 | //获取HTTP_AUTHORIZATION
13 | token := c.GetHeader("Authorization")
14 | if token == "" {
15 | c.JSON(401, gin.H{
16 | "error": "Unauthorized",
17 | })
18 | c.Abort()
19 | return
20 | }
21 | if len(token) <= 7 {
22 | c.JSON(401, gin.H{
23 | "error": "Unauthorized",
24 | })
25 | c.Abort()
26 | return
27 | }
28 | //提取token,格式是Bearer {token}
29 | //这里只是简单的提取
30 | token = token[7:]
31 |
32 | //验证token
33 |
34 | //检查是否设置了jwt key
35 | if len(global.Jwt.Key) > 0 {
36 | uid, _ := service.AllService.UserService.VerifyJWT(token)
37 | if uid == 0 {
38 | c.JSON(401, gin.H{
39 | "error": "Unauthorized",
40 | })
41 | c.Abort()
42 | return
43 | }
44 | }
45 |
46 | user, ut := service.AllService.UserService.InfoByAccessToken(token)
47 | if user.Id == 0 {
48 | c.JSON(401, gin.H{
49 | "error": "Unauthorized",
50 | })
51 | c.Abort()
52 | return
53 | }
54 | if !service.AllService.UserService.CheckUserEnable(user) {
55 | c.JSON(401, gin.H{
56 | "error": "Unauthorized",
57 | })
58 | c.Abort()
59 | return
60 | }
61 |
62 | c.Set("curUser", user)
63 | c.Set("token", token)
64 |
65 | service.AllService.UserService.AutoRefreshAccessToken(ut)
66 |
67 | c.Next()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/cache/cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | type Handler interface {
8 | Get(key string, value interface{}) error
9 | Set(key string, value interface{}, exp int) error
10 | Gc() error
11 | }
12 |
13 | // MaxTimeOut 最大超时时间
14 |
15 | const (
16 | TypeMem = "memory"
17 | TypeRedis = "redis"
18 | TypeFile = "file"
19 | MaxTimeOut = 365 * 24 * 3600
20 | )
21 |
22 | func New(typ string) Handler {
23 | var cache Handler
24 | switch typ {
25 | case TypeFile:
26 | cache = NewFileCache()
27 | case TypeRedis:
28 | cache = new(RedisCache)
29 | case TypeMem: // memory
30 | cache = NewMemoryCache(0)
31 | default:
32 | cache = NewMemoryCache(0)
33 | }
34 | return cache
35 | }
36 |
37 | func EncodeValue(value interface{}) (string, error) {
38 | /*if v, ok := value.(string); ok {
39 | return v, nil
40 | }
41 | if v, ok := value.([]byte); ok {
42 | return string(v), nil
43 | }*/
44 | b, err := json.Marshal(value)
45 | if err != nil {
46 | return "", err
47 | }
48 | return string(b), nil
49 | }
50 |
51 | func DecodeValue(value string, rtv interface{}) error {
52 | //判断rtv的类型是否是string,如果是string,直接赋值并返回
53 | /*switch rtv.(type) {
54 | case *string:
55 | *(rtv.(*string)) = value
56 | return nil
57 | case *[]byte:
58 | *(rtv.(*[]byte)) = []byte(value)
59 | return nil
60 | //struct
61 | case *interface{}:
62 | err := json.Unmarshal(([]byte)(value), rtv)
63 | return err
64 | default:
65 | err := json.Unmarshal(([]byte)(value), rtv)
66 | return err
67 | }
68 | */
69 | err := json.Unmarshal(([]byte)(value), rtv)
70 | return err
71 | }
72 |
--------------------------------------------------------------------------------
/model/custom_types/auto_json.go:
--------------------------------------------------------------------------------
1 | package custom_types
2 |
3 | import (
4 | "database/sql/driver"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | )
9 |
10 | // AutoJson 数据类型
11 | type AutoJson json.RawMessage
12 |
13 | func (j *AutoJson) Scan(value interface{}) error {
14 |
15 | var strValue string
16 | switch v := value.(type) {
17 | case []byte:
18 | strValue = string(v)
19 | case string:
20 | strValue = v
21 | default:
22 | return errors.New(fmt.Sprintf("Failed Scan AutoJson value: %v", value))
23 | }
24 | bytes := []byte(strValue)
25 | //bytes, ok := value.([]byte)
26 | //if !ok {
27 | // return errors.New(fmt.Sprint("Failed Scan AutoJson value:", value))
28 | //}
29 |
30 | if bytes == nil || len(bytes) == 0 {
31 | *j = AutoJson(json.RawMessage{'[', ']'})
32 | return nil
33 | }
34 | result := &json.RawMessage{}
35 | err := json.Unmarshal(bytes, result)
36 | //解析json错误 返回空
37 | if err != nil {
38 | *j = AutoJson(json.RawMessage{'[', ']'})
39 | return nil
40 | }
41 | *j = AutoJson(*result)
42 | return err
43 | }
44 | func (j AutoJson) Value() (driver.Value, error) {
45 | bytes, err := json.RawMessage(j).MarshalJSON()
46 | return string(bytes), err
47 | }
48 | func (j AutoJson) MarshalJSON() ([]byte, error) {
49 | b, err := json.RawMessage(j).MarshalJSON()
50 | if err != nil {
51 | return nil, err
52 | }
53 | return b, err
54 | }
55 |
56 | func (j *AutoJson) UnmarshalJSON(b []byte) error {
57 | result := json.RawMessage{}
58 | err := result.UnmarshalJSON(b)
59 | *j = AutoJson(result)
60 | return err
61 | }
62 |
63 | func (j AutoJson) String() string {
64 | s, _ := j.MarshalJSON()
65 | return (string)(s)
66 | }
67 |
--------------------------------------------------------------------------------
/service/loginLog.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | "gorm.io/gorm"
6 | )
7 |
8 | type LoginLogService struct {
9 | }
10 |
11 | // InfoById 根据用户id取用户信息
12 | func (us *LoginLogService) InfoById(id uint) *model.LoginLog {
13 | u := &model.LoginLog{}
14 | DB.Where("id = ?", id).First(u)
15 | return u
16 | }
17 |
18 | func (us *LoginLogService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.LoginLogList) {
19 | res = &model.LoginLogList{}
20 | res.Page = int64(page)
21 | res.PageSize = int64(pageSize)
22 | tx := DB.Model(&model.LoginLog{})
23 | if where != nil {
24 | where(tx)
25 | }
26 | tx.Count(&res.Total)
27 | tx.Scopes(Paginate(page, pageSize))
28 | tx.Find(&res.LoginLogs)
29 | return
30 | }
31 |
32 | // Create 创建
33 | func (us *LoginLogService) Create(u *model.LoginLog) error {
34 | res := DB.Create(u).Error
35 | return res
36 | }
37 | func (us *LoginLogService) Delete(u *model.LoginLog) error {
38 | return DB.Delete(u).Error
39 | }
40 |
41 | // Update 更新
42 | func (us *LoginLogService) Update(u *model.LoginLog) error {
43 | return DB.Model(u).Updates(u).Error
44 | }
45 |
46 | func (us *LoginLogService) BatchDelete(ids []uint) error {
47 | return DB.Where("id in (?)", ids).Delete(&model.LoginLog{}).Error
48 | }
49 |
50 | func (us *LoginLogService) SoftDelete(l *model.LoginLog) error {
51 | l.IsDeleted = model.IsDeletedYes
52 | return us.Update(l)
53 | }
54 |
55 | func (us *LoginLogService) BatchSoftDelete(uid uint, ids []uint) error {
56 | return DB.Model(&model.LoginLog{}).Where("user_id = ? and id in (?)", uid, ids).Update("is_deleted", model.IsDeletedYes).Error
57 | }
58 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/LICENSE-vpx.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, The WebM Project authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 |
15 | * Neither the name of Google, nor the WebM Project, nor the names
16 | of its contributors may be used to endorse or promote products
17 | derived from this software without specific prior written
18 | permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 |
--------------------------------------------------------------------------------
/http/response/api/user.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | /*
6 | pub enum UserStatus {
7 | Disabled = 0,
8 | Normal = 1,
9 | Unverified = -1,
10 | }
11 | */
12 |
13 | /*
14 | UserPayload
15 | String name = ”;
16 | String email = ”;
17 | String note = ”;
18 | UserStatus status;
19 | bool isAdmin = false;
20 | */
21 | type UserPayload struct {
22 | Name string `json:"name"`
23 | Email string `json:"email"`
24 | Note string `json:"note"`
25 | IsAdmin *bool `json:"is_admin"`
26 | Status int `json:"status"`
27 | Info map[string]interface{} `json:"info"`
28 | }
29 |
30 | func (up *UserPayload) FromUser(user *model.User) *UserPayload {
31 | up.Name = user.Username
32 | up.Email = user.Email
33 | up.IsAdmin = user.IsAdmin
34 | up.Status = int(user.Status)
35 | up.Info = map[string]interface{}{}
36 | return up
37 | }
38 |
39 | /*
40 | class HttpType {
41 | static const kAuthReqTypeAccount = "account";
42 | static const kAuthReqTypeMobile = "mobile";
43 | static const kAuthReqTypeSMSCode = "sms_code";
44 | static const kAuthReqTypeEmailCode = "email_code";
45 | static const kAuthReqTypeTfaCode = "tfa_code";
46 |
47 | static const kAuthResTypeToken = "access_token";
48 | static const kAuthResTypeEmailCheck = "email_check";
49 | static const kAuthResTypeTfaCheck = "tfa_check";
50 | }
51 | */
52 | type LoginRes struct {
53 | Type string `json:"type"`
54 | AccessToken string `json:"access_token"`
55 | User UserPayload `json:"user"`
56 | Secret string `json:"secret,omitempty"`
57 | TfaType string `json:"tfa_type,omitempty"`
58 | }
59 |
--------------------------------------------------------------------------------
/service/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/config"
5 | "github.com/lejianwen/rustdesk-api/v2/lib/jwt"
6 | "github.com/lejianwen/rustdesk-api/v2/lib/lock"
7 | "github.com/lejianwen/rustdesk-api/v2/model"
8 | log "github.com/sirupsen/logrus"
9 | "gorm.io/gorm"
10 | )
11 |
12 | type Service struct {
13 | //AdminService *AdminService
14 | //AdminRoleService *AdminRoleService
15 | *UserService
16 | *AddressBookService
17 | *TagService
18 | *PeerService
19 | *GroupService
20 | *OauthService
21 | *LoginLogService
22 | *AuditService
23 | *ShareRecordService
24 | *ServerCmdService
25 | *LdapService
26 | *AppService
27 | }
28 |
29 | type Dependencies struct {
30 | Config *config.Config
31 | DB *gorm.DB
32 | Logger *log.Logger
33 | Jwt *jwt.Jwt
34 | Lock *lock.Locker
35 | }
36 |
37 | var Config *config.Config
38 | var DB *gorm.DB
39 | var Logger *log.Logger
40 | var Jwt *jwt.Jwt
41 | var Lock lock.Locker
42 |
43 | var AllService *Service
44 |
45 | func New(c *config.Config, g *gorm.DB, l *log.Logger, j *jwt.Jwt, lo lock.Locker) *Service {
46 | Config = c
47 | DB = g
48 | Logger = l
49 | Jwt = j
50 | Lock = lo
51 | AllService = new(Service)
52 | return AllService
53 | }
54 |
55 | func Paginate(page, pageSize uint) func(db *gorm.DB) *gorm.DB {
56 | return func(db *gorm.DB) *gorm.DB {
57 | if page == 0 {
58 | page = 1
59 | }
60 | if pageSize == 0 {
61 | pageSize = 10
62 | }
63 | offset := (page - 1) * pageSize
64 | return db.Offset(int(offset)).Limit(int(pageSize))
65 | }
66 | }
67 |
68 | func CommonEnable() func(db *gorm.DB) *gorm.DB {
69 | return func(db *gorm.DB) *gorm.DB {
70 | return db.Where("status = ?", model.COMMON_STATUS_ENABLE)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/model/audit.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | const (
4 | AuditActionNew = "new"
5 | AuditActionClose = "close"
6 | )
7 |
8 | type AuditConn struct {
9 | IdModel
10 | Action string `json:"action" gorm:"default:'';not null;"`
11 | ConnId int64 `json:"conn_id" gorm:"default:0;not null;index"`
12 | PeerId string `json:"peer_id" gorm:"default:'';not null;index"`
13 | FromPeer string `json:"from_peer" gorm:"default:'';not null;"`
14 | FromName string `json:"from_name" gorm:"default:'';not null;"`
15 | Ip string `json:"ip" gorm:"default:'';not null;"`
16 | SessionId string `json:"session_id" gorm:"default:'';not null;"`
17 | Type int `json:"type" gorm:"default:0;not null;"`
18 | Uuid string `json:"uuid" gorm:"default:'';not null;"`
19 | CloseTime int64 `json:"close_time" gorm:"default:0;not null;"`
20 | TimeModel
21 | }
22 |
23 | type AuditConnList struct {
24 | AuditConns []*AuditConn `json:"list"`
25 | Pagination
26 | }
27 |
28 | type AuditFile struct {
29 | IdModel
30 | FromPeer string `json:"from_peer" gorm:"default:'';not null;index"`
31 | Info string `json:"info" gorm:"default:'';not null;"`
32 | IsFile bool `json:"is_file" gorm:"default:0;not null;"`
33 | Path string `json:"path" gorm:"default:'';not null;"`
34 | PeerId string `json:"peer_id" gorm:"default:'';not null;index"`
35 | Type int `json:"type" gorm:"default:0;not null;"`
36 | Uuid string `json:"uuid" gorm:"default:'';not null;"`
37 | Ip string `json:"ip" gorm:"default:'';not null;"`
38 | Num int `json:"num" gorm:"default:0;not null;"`
39 | FromName string `json:"from_name" gorm:"default:'';not null;"`
40 | TimeModel
41 | }
42 |
43 | type AuditFileList struct {
44 | AuditFiles []*AuditFile `json:"list"`
45 | Pagination
46 | }
47 |
--------------------------------------------------------------------------------
/lib/orm/mysql.go:
--------------------------------------------------------------------------------
1 | package orm
2 |
3 | import (
4 | "fmt"
5 | "gorm.io/driver/mysql"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "time"
9 | )
10 |
11 | type MysqlConfig struct {
12 | Dsn string
13 | MaxIdleConns int
14 | MaxOpenConns int
15 | }
16 |
17 | func NewMysql(mysqlConf *MysqlConfig, logwriter logger.Writer) *gorm.DB {
18 | db, err := gorm.Open(mysql.New(mysql.Config{
19 | DSN: mysqlConf.Dsn, // DSN data source name
20 | DefaultStringSize: 256, // string 类型字段的默认长度
21 | //DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
22 | //DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
23 | //DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
24 | //SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
25 | }), &gorm.Config{
26 | DisableForeignKeyConstraintWhenMigrating: true,
27 | Logger: logger.New(
28 | logwriter, // io writer
29 | logger.Config{
30 | SlowThreshold: time.Second, // Slow SQL threshold
31 | LogLevel: logger.Warn, // Log level
32 | IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
33 | ParameterizedQueries: true, // Don't include params in the SQL log
34 | Colorful: true,
35 | },
36 | ),
37 | })
38 | if err != nil {
39 | fmt.Println(err)
40 | }
41 | sqlDB, err2 := db.DB()
42 | if err2 != nil {
43 | fmt.Println(err2)
44 | }
45 | // SetMaxIdleConns 设置空闲连接池中连接的最大数量
46 | sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConns)
47 |
48 | // SetMaxOpenConns 设置打开数据库连接的最大数量。
49 | sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConns)
50 |
51 | return db
52 | }
53 |
--------------------------------------------------------------------------------
/lib/lock/local_test.go:
--------------------------------------------------------------------------------
1 | package lock
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "testing"
7 | )
8 |
9 | func TestLocal_GetLock(t *testing.T) {
10 | l := NewLocal()
11 | wg := sync.WaitGroup{}
12 | wg.Add(3)
13 | var l1 *sync.Mutex
14 | var l2 *sync.Mutex
15 | var l3 *sync.Mutex
16 | i := 0
17 | go func() {
18 | l1 = l.GetLock("key")
19 | fmt.Println("l1", l1, i)
20 | l1.Lock()
21 | fmt.Println("l1", i)
22 | i++
23 | l1.Unlock()
24 | wg.Done()
25 | }()
26 | go func() {
27 | l2 = l.GetLock("key")
28 | fmt.Println("l2", l2, i)
29 | l2.Lock()
30 | fmt.Println("l2", i)
31 | i++
32 | l2.Unlock()
33 | wg.Done()
34 | }()
35 | go func() {
36 | l3 = l.GetLock("key")
37 | fmt.Println("l3", l3, i)
38 | l3.Lock()
39 | fmt.Println("l3", i)
40 | i++
41 | l3.Unlock()
42 | wg.Done()
43 | }()
44 | wg.Wait()
45 |
46 | fmt.Println(l1, l2, l3)
47 | fmt.Println(l1 == l2, l2 == l3)
48 | fmt.Println(&sync.Mutex{} == &sync.Mutex{})
49 | }
50 |
51 | func TestLocal_Lock(t *testing.T) {
52 | l := NewLocal()
53 | wg := sync.WaitGroup{}
54 | m := 10
55 | wg.Add(m)
56 | i := 0
57 | for j := 0; j < m; j++ {
58 | go func() {
59 | l.Lock("key")
60 | //fmt.Println(j, i)
61 | i++
62 | fmt.Println(j, i)
63 | l.UnLock("key")
64 | wg.Done()
65 | }()
66 | }
67 |
68 | wg.Wait()
69 | fmt.Println(i)
70 |
71 | }
72 | func TestSyncMap(t *testing.T) {
73 | m := sync.Map{}
74 | wg := sync.WaitGroup{}
75 | wg.Add(3)
76 | go func() {
77 | v, ok := m.LoadOrStore("key", 1)
78 | fmt.Println(1, v, ok)
79 | wg.Done()
80 | }()
81 | go func() {
82 | v, ok := m.LoadOrStore("key", 2)
83 | fmt.Println(2, v, ok)
84 | wg.Done()
85 | }()
86 | go func() {
87 | v, ok := m.LoadOrStore("key", 3)
88 | fmt.Println(3, v, ok)
89 | wg.Done()
90 | }()
91 | wg.Wait()
92 | }
93 |
--------------------------------------------------------------------------------
/http/controller/admin/my/peer.go:
--------------------------------------------------------------------------------
1 | package my
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/http/request/admin"
6 | "github.com/lejianwen/rustdesk-api/v2/http/response"
7 | "github.com/lejianwen/rustdesk-api/v2/service"
8 | "gorm.io/gorm"
9 | "time"
10 | )
11 |
12 | type Peer struct {
13 | }
14 |
15 | // List 列表
16 | // @Tags 我的设备
17 | // @Summary 设备列表
18 | // @Description 设备列表
19 | // @Accept json
20 | // @Produce json
21 | // @Param page query int false "页码"
22 | // @Param page_size query int false "页大小"
23 | // @Param time_ago query int false "时间"
24 | // @Param id query string false "ID"
25 | // @Param hostname query string false "主机名"
26 | // @Param uuids query string false "uuids 用逗号分隔"
27 | // @Success 200 {object} response.Response{data=model.PeerList}
28 | // @Failure 500 {object} response.Response
29 | // @Router /admin/my/peer/list [get]
30 | // @Security token
31 | func (ct *Peer) List(c *gin.Context) {
32 | query := &admin.PeerQuery{}
33 | if err := c.ShouldBindQuery(query); err != nil {
34 | response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
35 | return
36 | }
37 | u := service.AllService.UserService.CurUser(c)
38 | res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
39 | tx.Where("user_id = ?", u.Id)
40 | if query.TimeAgo > 0 {
41 | lt := time.Now().Unix() - int64(query.TimeAgo)
42 | tx.Where("last_online_time < ?", lt)
43 | }
44 | if query.TimeAgo < 0 {
45 | lt := time.Now().Unix() + int64(query.TimeAgo)
46 | tx.Where("last_online_time > ?", lt)
47 | }
48 | if query.Id != "" {
49 | tx.Where("id like ?", "%"+query.Id+"%")
50 | }
51 | if query.Hostname != "" {
52 | tx.Where("hostname like ?", "%"+query.Hostname+"%")
53 | }
54 | if query.Uuids != "" {
55 | tx.Where("uuid in (?)", query.Uuids)
56 | }
57 | })
58 | response.Success(c, res)
59 | }
60 |
--------------------------------------------------------------------------------
/lib/cache/cache_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "github.com/go-redis/redis/v8"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestSimpleCache(t *testing.T) {
11 |
12 | type st struct {
13 | A string
14 | B string
15 | }
16 |
17 | items := map[string]interface{}{}
18 | items["a"] = "b"
19 | items["b"] = "c"
20 |
21 | ab := &st{
22 | A: "a",
23 | B: "b",
24 | }
25 | items["ab"] = *ab
26 |
27 | a := items["a"]
28 | fmt.Println(a)
29 |
30 | b := items["b"]
31 | fmt.Println(b)
32 |
33 | ab.A = "aa"
34 | ab2 := st{}
35 | ab2 = (items["ab"]).(st)
36 | fmt.Println(ab2, reflect.TypeOf(ab2))
37 |
38 | }
39 |
40 | func TestFileCacheSet(t *testing.T) {
41 | fc := New("file")
42 | err := fc.Set("123", "ddd", 0)
43 | if err != nil {
44 | fmt.Println(err.Error())
45 | t.Fatalf("写入失败")
46 | }
47 | }
48 |
49 | func TestFileCacheGet(t *testing.T) {
50 | fc := New("file")
51 | err := fc.Set("123", "45156", 300)
52 | if err != nil {
53 | t.Fatalf("写入失败")
54 | }
55 | res := ""
56 | err = fc.Get("123", &res)
57 | if err != nil {
58 | t.Fatalf("读取失败")
59 | }
60 | fmt.Println("res", res)
61 | }
62 |
63 | func TestRedisCacheSet(t *testing.T) {
64 | rc := NewRedis(&redis.Options{
65 | Addr: "192.168.1.168:6379",
66 | Password: "", // no password set
67 | DB: 0, // use default DB
68 | })
69 | err := rc.Set("123", "ddd", 0)
70 | if err != nil {
71 | fmt.Println(err.Error())
72 | t.Fatalf("写入失败")
73 | }
74 | }
75 |
76 | func TestRedisCacheGet(t *testing.T) {
77 | rc := NewRedis(&redis.Options{
78 | Addr: "192.168.1.168:6379",
79 | Password: "", // no password set
80 | DB: 0, // use default DB
81 | })
82 | err := rc.Set("123", "451156", 300)
83 | if err != nil {
84 | t.Fatalf("写入失败")
85 | }
86 | res := ""
87 | err = rc.Get("123", &res)
88 | if err != nil {
89 | t.Fatalf("读取失败")
90 | }
91 | fmt.Println("res", res)
92 | }
93 |
--------------------------------------------------------------------------------
/http/request/api/user.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | /*
4 | *
5 |
6 | message LoginRequest {
7 | string username = 1;
8 | bytes password = 2;
9 | string my_id = 4;
10 | string my_name = 5;
11 | OptionMessage option = 6;
12 | oneof union {
13 | FileTransfer file_transfer = 7;
14 | PortForward port_forward = 8;
15 | }
16 | bool video_ack_required = 9;
17 | uint64 session_id = 10;
18 | string version = 11;
19 | OSLogin os_login = 12;
20 | string my_platform = 13;
21 | bytes hwid = 14;
22 | }
23 | */
24 |
25 | type DeviceInfoInLogin struct {
26 | Name string `json:"name" label:"name"`
27 | Os string `json:"os" label:"os"`
28 | Type string `json:"type" label:"type"`
29 | }
30 |
31 | type LoginForm struct {
32 | AutoLogin bool `json:"autoLogin" label:"自动登录"`
33 | DeviceInfo DeviceInfoInLogin `json:"deviceInfo" label:"设备信息"`
34 | Id string `json:"id" label:"id"`
35 | Type string `json:"type" label:"type"`
36 | Uuid string `json:"uuid" label:"uuid"`
37 | Username string `json:"username" validate:"required,gte=2,lte=32" label:"用户名"`
38 | Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"密码"`
39 | }
40 |
41 | type UserListQuery struct {
42 | Page uint `json:"page" form:"page" validate:"required" label:"页码"`
43 | PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"`
44 | Status int `json:"status" form:"status" label:"状态"`
45 | Accessible string `json:"accessible" form:"accessible"`
46 | }
47 |
48 | type PeerListQuery struct {
49 | Page uint `json:"page" form:"page" validate:"required" label:"页码"`
50 | PageSize uint `json:"pageSize" form:"pageSize" validate:"required" label:"每页数量"`
51 | Status int `json:"status" form:"status" label:"状态"`
52 | Accessible string `json:"accessible" form:"accessible"`
53 | }
54 |
--------------------------------------------------------------------------------
/http/request/api/audit.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/model"
7 | "strconv"
8 | )
9 |
10 | type AuditConnForm struct {
11 | Action string `json:"action"`
12 | ConnId int64 `json:"conn_id"`
13 | Id string `json:"id"`
14 | Peer []string `json:"peer"`
15 | Ip string `json:"ip"`
16 | SessionId float64 `json:"session_id"`
17 | Type int `json:"type"`
18 | Uuid string `json:"uuid"`
19 | }
20 |
21 | func (a *AuditConnForm) ToAuditConn() *model.AuditConn {
22 | fp := ""
23 | fn := ""
24 | if len(a.Peer) >= 1 {
25 | fp = a.Peer[0]
26 | if len(a.Peer) == 2 {
27 | fn = a.Peer[1]
28 | }
29 | }
30 | ssid := strconv.FormatFloat(a.SessionId, 'f', -1, 64)
31 | return &model.AuditConn{
32 | Action: a.Action,
33 | ConnId: a.ConnId,
34 | PeerId: a.Id,
35 | FromPeer: fp,
36 | FromName: fn,
37 | Ip: a.Ip,
38 | SessionId: ssid,
39 | Type: a.Type,
40 | Uuid: a.Uuid,
41 | }
42 | }
43 |
44 | type AuditFileForm struct {
45 | Id string `json:"id"`
46 | Info string `json:"info"`
47 | IsFile bool `json:"is_file"`
48 | Path string `json:"path"`
49 | PeerId string `json:"peer_id"`
50 | Type int `json:"type"`
51 | Uuid string `json:"uuid"`
52 | }
53 | type AuditFileInfo struct {
54 | Ip string `json:"ip"`
55 | Name string `json:"name"`
56 | Num int `json:"num"`
57 | }
58 |
59 | func (a *AuditFileForm) ToAuditFile() *model.AuditFile {
60 | fi := &AuditFileInfo{}
61 | err := json.Unmarshal([]byte(a.Info), fi)
62 | if err != nil {
63 | global.Logger.Warn("ToAuditFile", err)
64 | }
65 |
66 | return &model.AuditFile{
67 | PeerId: a.Id,
68 | Info: a.Info,
69 | IsFile: a.IsFile,
70 | FromPeer: a.PeerId,
71 | Path: a.Path,
72 | Type: a.Type,
73 | Uuid: a.Uuid,
74 | FromName: fi.Name,
75 | Ip: fi.Ip,
76 | Num: fi.Num,
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/cache/file_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestFileSet(t *testing.T) {
10 | fc := NewFileCache()
11 | err := fc.Set("123", "ddd", 0)
12 | if err != nil {
13 | fmt.Println(err.Error())
14 | t.Fatalf("写入失败")
15 | }
16 | }
17 |
18 | func TestFileGet(t *testing.T) {
19 | fc := NewFileCache()
20 | res := ""
21 | err := fc.Get("123", &res)
22 | if err != nil {
23 | fmt.Println(err.Error())
24 | t.Fatalf("读取失败")
25 | }
26 | fmt.Println("res", res)
27 | }
28 | func TestFileSetGet(t *testing.T) {
29 | fc := NewFileCache()
30 | err := fc.Set("key1", "ddd", 0)
31 | res := ""
32 | err = fc.Get("key1", &res)
33 | if err != nil {
34 | fmt.Println(err.Error())
35 | t.Fatalf("读取失败")
36 | }
37 | fmt.Println("res", res)
38 | }
39 | func TestFileGetJson(t *testing.T) {
40 | fc := NewFileCache()
41 | old := &r{
42 | A: "a", B: "b",
43 | }
44 | fc.Set("123", old, 0)
45 | res := &r{}
46 | err2 := fc.Get("123", res)
47 | fmt.Println("res", res)
48 | if err2 != nil {
49 | t.Fatalf("读取失败" + err2.Error())
50 | }
51 | }
52 | func TestFileSetGetJson(t *testing.T) {
53 | fc := NewFileCache()
54 |
55 | old_rr := &rr{AA: "aa", BB: "bb"}
56 | old := &r{
57 | A: "a", B: "b",
58 | R: old_rr,
59 | }
60 | err := fc.Set("123", old, 300)
61 | if err != nil {
62 | t.Fatalf("写入失败")
63 | }
64 | //old_rr.AA = "aaa"
65 | fmt.Println("old_rr", old)
66 |
67 | res := &r{}
68 | err2 := fc.Get("123", res)
69 | fmt.Println("res", res)
70 | if err2 != nil {
71 | t.Fatalf("读取失败" + err2.Error())
72 | }
73 | if !reflect.DeepEqual(res, old) {
74 | t.Fatalf("读取错误")
75 | }
76 |
77 | }
78 |
79 | func BenchmarkSet(b *testing.B) {
80 | fc := NewFileCache()
81 | b.ResetTimer()
82 | for i := 0; i < b.N; i++ {
83 | fc.Set("123", "{dsv}", 1000)
84 | }
85 | }
86 |
87 | func BenchmarkGet(b *testing.B) {
88 | fc := NewFileCache()
89 | b.ResetTimer()
90 | v := ""
91 | for i := 0; i < b.N; i++ {
92 | fc.Get("123", &v)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/resources/web/js/src/codec.js:
--------------------------------------------------------------------------------
1 | // example: https://github.com/rgov/js-theora-decoder/blob/main/index.html
2 | // https://github.com/brion/ogv.js/releases, yarn add has no simd
3 | // dev: copy decoder files from node/ogv/dist/* to project dir
4 | // dist: .... to dist
5 | /*
6 | OGVDemuxerOggW: 'ogv-demuxer-ogg-wasm.js',
7 | OGVDemuxerWebMW: 'ogv-demuxer-webm-wasm.js',
8 | OGVDecoderAudioOpusW: 'ogv-decoder-audio-opus-wasm.js',
9 | OGVDecoderAudioVorbisW: 'ogv-decoder-audio-vorbis-wasm.js',
10 | OGVDecoderVideoTheoraW: 'ogv-decoder-video-theora-wasm.js',
11 | OGVDecoderVideoVP8W: 'ogv-decoder-video-vp8-wasm.js',
12 | OGVDecoderVideoVP8MTW: 'ogv-decoder-video-vp8-mt-wasm.js',
13 | OGVDecoderVideoVP9W: 'ogv-decoder-video-vp9-wasm.js',
14 | OGVDecoderVideoVP9SIMDW: 'ogv-decoder-video-vp9-simd-wasm.js',
15 | OGVDecoderVideoVP9MTW: 'ogv-decoder-video-vp9-mt-wasm.js',
16 | OGVDecoderVideoVP9SIMDMTW: 'ogv-decoder-video-vp9-simd-mt-wasm.js',
17 | OGVDecoderVideoAV1W: 'ogv-decoder-video-av1-wasm.js',
18 | OGVDecoderVideoAV1SIMDW: 'ogv-decoder-video-av1-simd-wasm.js',
19 | OGVDecoderVideoAV1MTW: 'ogv-decoder-video-av1-mt-wasm.js',
20 | OGVDecoderVideoAV1SIMDMTW: 'ogv-decoder-video-av1-simd-mt-wasm.js',
21 | */
22 | import { simd } from "wasm-feature-detect";
23 |
24 | export async function loadVp9(callback) {
25 | // Multithreading is used only if `options.threading` is true.
26 | // This requires browser support for the new `SharedArrayBuffer` and `Atomics` APIs,
27 | // currently available in Firefox and Chrome with experimental flags enabled.
28 | // 所有主流浏览器均默认于2018年1月5日禁用SharedArrayBuffer
29 | const isSIMD = await simd();
30 | console.log('isSIMD: ' + isSIMD);
31 | window.OGVLoader.loadClass(
32 | isSIMD ? "OGVDecoderVideoVP9SIMDW" : "OGVDecoderVideoVP9W",
33 | (videoCodecClass) => {
34 | window.videoCodecClass = videoCodecClass;
35 | videoCodecClass({ videoFormat: {} }).then((decoder) => {
36 | decoder.init(() => {
37 | callback(decoder);
38 | })
39 | })
40 | },
41 | { worker: true, threading: true }
42 | );
43 | }
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/COPYING-opus.txt:
--------------------------------------------------------------------------------
1 | Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
2 | Jean-Marc Valin, Timothy B. Terriberry,
3 | CSIRO, Gregory Maxwell, Mark Borgerding,
4 | Erik de Castro Lopo
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions
8 | are met:
9 |
10 | - Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 |
13 | - Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in the
15 | documentation and/or other materials provided with the distribution.
16 |
17 | - Neither the name of Internet Society, IETF or IETF Trust, nor the
18 | names of specific contributors, may be used to endorse or promote
19 | products derived from this software without specific prior written
20 | permission.
21 |
22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
26 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 |
34 | Opus is subject to the royalty-free patent licenses which are
35 | specified at:
36 |
37 | Xiph.Org Foundation:
38 | https://datatracker.ietf.org/ipr/1524/
39 |
40 | Microsoft Corporation:
41 | https://datatracker.ietf.org/ipr/1914/
42 |
43 | Broadcom Corporation:
44 | https://datatracker.ietf.org/ipr/1526/
45 |
--------------------------------------------------------------------------------
/service/group.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | "gorm.io/gorm"
6 | )
7 |
8 | type GroupService struct {
9 | }
10 |
11 | // InfoById 根据用户id取用户信息
12 | func (us *GroupService) InfoById(id uint) *model.Group {
13 | u := &model.Group{}
14 | DB.Where("id = ?", id).First(u)
15 | return u
16 | }
17 |
18 | func (us *GroupService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.GroupList) {
19 | res = &model.GroupList{}
20 | res.Page = int64(page)
21 | res.PageSize = int64(pageSize)
22 | tx := DB.Model(&model.Group{})
23 | if where != nil {
24 | where(tx)
25 | }
26 | tx.Count(&res.Total)
27 | tx.Scopes(Paginate(page, pageSize))
28 | tx.Find(&res.Groups)
29 | return
30 | }
31 |
32 | // Create 创建
33 | func (us *GroupService) Create(u *model.Group) error {
34 | res := DB.Create(u).Error
35 | return res
36 | }
37 | func (us *GroupService) Delete(u *model.Group) error {
38 | return DB.Delete(u).Error
39 | }
40 |
41 | // Update 更新
42 | func (us *GroupService) Update(u *model.Group) error {
43 | return DB.Model(u).Updates(u).Error
44 | }
45 |
46 | // DeviceGroupInfoById 根据用户id取用户信息
47 | func (us *GroupService) DeviceGroupInfoById(id uint) *model.DeviceGroup {
48 | u := &model.DeviceGroup{}
49 | DB.Where("id = ?", id).First(u)
50 | return u
51 | }
52 |
53 | func (us *GroupService) DeviceGroupList(page, pageSize uint, where func(tx *gorm.DB)) (res *model.DeviceGroupList) {
54 | res = &model.DeviceGroupList{}
55 | res.Page = int64(page)
56 | res.PageSize = int64(pageSize)
57 | tx := DB.Model(&model.DeviceGroup{})
58 | if where != nil {
59 | where(tx)
60 | }
61 | tx.Count(&res.Total)
62 | tx.Scopes(Paginate(page, pageSize))
63 | tx.Find(&res.DeviceGroups)
64 | return
65 | }
66 |
67 | func (us *GroupService) DeviceGroupCreate(u *model.DeviceGroup) error {
68 | res := DB.Create(u).Error
69 | return res
70 | }
71 | func (us *GroupService) DeviceGroupDelete(u *model.DeviceGroup) error {
72 | return DB.Delete(u).Error
73 | }
74 |
75 | func (us *GroupService) DeviceGroupUpdate(u *model.DeviceGroup) error {
76 | return DB.Model(u).Updates(u).Error
77 | }
78 |
--------------------------------------------------------------------------------
/http/controller/api/index.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
6 | "github.com/lejianwen/rustdesk-api/v2/http/response"
7 | "github.com/lejianwen/rustdesk-api/v2/model"
8 | "github.com/lejianwen/rustdesk-api/v2/service"
9 | "net/http"
10 | "time"
11 | )
12 |
13 | type Index struct {
14 | }
15 |
16 | // Index 首页
17 | // @Tags 首页
18 | // @Summary 首页
19 | // @Description 首页
20 | // @Accept json
21 | // @Produce json
22 | // @Success 200 {object} response.Response
23 | // @Failure 500 {object} response.Response
24 | // @Router / [get]
25 | func (i *Index) Index(c *gin.Context) {
26 | response.Success(
27 | c,
28 | "Hello Gwen",
29 | )
30 | }
31 |
32 | // Heartbeat 心跳
33 | // @Tags 首页
34 | // @Summary 心跳
35 | // @Description 心跳
36 | // @Accept json
37 | // @Produce json
38 | // @Success 200 {object} nil
39 | // @Failure 500 {object} response.Response
40 | // @Router /heartbeat [post]
41 | func (i *Index) Heartbeat(c *gin.Context) {
42 | info := &requstform.PeerInfoInHeartbeat{}
43 | err := c.ShouldBindJSON(info)
44 | if err != nil {
45 | c.JSON(http.StatusOK, gin.H{})
46 | return
47 | }
48 | if info.Uuid == "" {
49 | c.JSON(http.StatusOK, gin.H{})
50 | return
51 | }
52 | peer := service.AllService.PeerService.FindById(info.Id)
53 | if peer == nil || peer.RowId == 0 {
54 | c.JSON(http.StatusOK, gin.H{})
55 | return
56 | }
57 | //如果在40s以内则不更新
58 | if time.Now().Unix()-peer.LastOnlineTime >= 30 {
59 | upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()}
60 | service.AllService.PeerService.Update(upp)
61 | }
62 | c.JSON(http.StatusOK, gin.H{})
63 | }
64 |
65 | // Version 版本
66 | // @Tags 首页
67 | // @Summary 版本
68 | // @Description 版本
69 | // @Accept json
70 | // @Produce json
71 | // @Success 200 {object} response.Response
72 | // @Failure 500 {object} response.Response
73 | // @Router /version [get]
74 | func (i *Index) Version(c *gin.Context) {
75 | //读取resources/version文件
76 | v := service.AllService.AppService.GetAppVersion()
77 | response.Success(
78 | c,
79 | v,
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-support.js:
--------------------------------------------------------------------------------
1 | (()=>{var e={575:e=>{e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.__esModule=!0,e.exports.default=e.exports},913:e=>{function t(e,t){for(var o=0;o{e.exports=function(e){return e&&e.__esModule?e:{default:e}},e.exports.__esModule=!0,e.exports.default=e.exports},8:e=>{function t(o){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(o)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},523:(e,t,o)=>{"use strict";var r=o(318);Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var n=r(o(575)),u=r(o(913)),s=new(function(){function e(){(0,n.default)(this,e)}return(0,u.default)(e,[{key:"hasTypedArrays",value:function(){return!!window.Uint32Array}},{key:"hasWebAssembly",value:function(){return!!window.WebAssembly}},{key:"hasWebAudio",value:function(){return!(!window.AudioContext&&!window.webkitAudioContext)}},{key:"hasFlash",value:function(){return!1}},{key:"hasAudio",value:function(){return this.hasWebAudio()}},{key:"isBlacklisted",value:function(e){return!1}},{key:"isSlow",value:function(){return!1}},{key:"isTooSlow",value:function(){return!1}},{key:"supported",value:function(e){return"OGVDecoder"===e?this.hasWebAssembly():"OGVPlayer"===e&&this.supported("OGVDecoder")&&this.hasAudio()}}]),e}());t.default=s}},t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var u=t[r]={exports:{}};return e[r](u,u.exports,o),u.exports}(()=>{"use strict";var e=o(318),t=e(o(8)),r=e(o(523));"object"===("undefined"==typeof window?"undefined":(0,t.default)(window))&&(window.OGVCompat=r.default,window.OGVVersion="1.8.6-20220111172545-1f60d9d")})()})();
--------------------------------------------------------------------------------
/http/controller/admin/file.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/lejianwen/rustdesk-api/v2/global"
7 | "github.com/lejianwen/rustdesk-api/v2/http/response"
8 | "github.com/lejianwen/rustdesk-api/v2/lib/upload"
9 | "os"
10 | "time"
11 | )
12 |
13 | type File struct {
14 | }
15 |
16 | // OssToken 文件
17 | // @Tags 文件
18 | // @Summary 获取ossToken
19 | // @Description 获取ossToken
20 | // @Accept json
21 | // @Produce json
22 | // @Success 200 {object} response.Response
23 | // @Failure 500 {object} response.Response
24 | // @Router /admin/file/oss_token [get]
25 | // @Security token
26 | func (f *File) OssToken(c *gin.Context) {
27 | token := global.Oss.GetPolicyToken("")
28 | response.Success(c, token)
29 | }
30 |
31 | type FileBack struct {
32 | upload.CallbackBaseForm
33 | Url string `json:"url"`
34 | }
35 |
36 | // Notify 上传成功后回调
37 | func (f *File) Notify(c *gin.Context) {
38 |
39 | res := global.Oss.Verify(c.Request)
40 | if !res {
41 | response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
42 | return
43 | }
44 | fm := &FileBack{}
45 | if err := c.ShouldBind(fm); err != nil {
46 | fmt.Println(err)
47 | }
48 | fm.Url = global.Config.Oss.Host + "/" + fm.Filename
49 | response.Success(c, fm)
50 |
51 | }
52 |
53 | // Upload 上传文件到本地
54 | // @Tags 文件
55 | // @Summary 上传文件到本地
56 | // @Description 上传文件到本地
57 | // @Accept multipart/form-data
58 | // @Produce json
59 | // @Param file formData file true "上传文件示例"
60 | // @Success 200 {object} response.Response
61 | // @Failure 500 {object} response.Response
62 | // @Router /admin/file/upload [post]
63 | // @Security token
64 | func (f *File) Upload(c *gin.Context) {
65 | file, _ := c.FormFile("file")
66 | timePath := time.Now().Format("20060102") + "/"
67 | webPath := "/upload/" + timePath
68 | path := global.Config.Gin.ResourcesPath + webPath
69 | dst := path + file.Filename
70 | err := os.MkdirAll(path, os.ModePerm)
71 | if err != nil {
72 | return
73 | }
74 | // 上传文件至指定目录
75 | err = c.SaveUploadedFile(file, dst)
76 | if err != nil {
77 | return
78 | }
79 | // 返回文件web地址
80 | response.Success(c, gin.H{
81 | "url": webPath + file.Filename,
82 | })
83 | }
84 |
--------------------------------------------------------------------------------
/lib/cache/redis_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "github.com/go-redis/redis/v8"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestRedisSet(t *testing.T) {
11 | //rc := New("redis")
12 | rc := RedisCacheInit(&redis.Options{
13 | Addr: "192.168.1.168:6379",
14 | Password: "", // no password set
15 | DB: 0, // use default DB
16 | })
17 | err := rc.Set("123", "ddd", 0)
18 | if err != nil {
19 | fmt.Println(err.Error())
20 | t.Fatalf("写入失败")
21 | }
22 | }
23 |
24 | func TestRedisGet(t *testing.T) {
25 | rc := RedisCacheInit(&redis.Options{
26 | Addr: "192.168.1.168:6379",
27 | Password: "", // no password set
28 | DB: 0, // use default DB
29 | })
30 | err := rc.Set("123", "451156", 300)
31 | if err != nil {
32 | t.Fatalf("写入失败")
33 | }
34 | res := ""
35 | err = rc.Get("123", &res)
36 | if err != nil {
37 | t.Fatalf("读取失败")
38 | }
39 | fmt.Println("res", res)
40 | }
41 |
42 | func TestRedisGetJson(t *testing.T) {
43 | rc := RedisCacheInit(&redis.Options{
44 | Addr: "192.168.1.168:6379",
45 | Password: "", // no password set
46 | DB: 0, // use default DB
47 | })
48 | type r struct {
49 | Aa string `json:"a"`
50 | B string `json:"c"`
51 | }
52 | old := &r{
53 | Aa: "ab", B: "cdc",
54 | }
55 | err := rc.Set("1233", old, 300)
56 | if err != nil {
57 | t.Fatalf("写入失败")
58 | }
59 |
60 | res := &r{}
61 | err2 := rc.Get("1233", res)
62 | if err2 != nil {
63 | t.Fatalf("读取失败")
64 | }
65 | if !reflect.DeepEqual(res, old) {
66 | t.Fatalf("读取错误")
67 | }
68 | fmt.Println(res, res.Aa)
69 | }
70 |
71 | func BenchmarkRSet(b *testing.B) {
72 | rc := RedisCacheInit(&redis.Options{
73 | Addr: "192.168.1.168:6379",
74 | Password: "", // no password set
75 | DB: 0, // use default DB
76 | })
77 | b.ResetTimer()
78 | for i := 0; i < b.N; i++ {
79 | rc.Set("123", "{dsv}", 1000)
80 | }
81 | }
82 |
83 | func BenchmarkRGet(b *testing.B) {
84 | rc := RedisCacheInit(&redis.Options{
85 | Addr: "192.168.1.168:6379",
86 | Password: "", // no password set
87 | DB: 0, // use default DB
88 | })
89 | b.ResetTimer()
90 | v := ""
91 | for i := 0; i < b.N; i++ {
92 | rc.Get("123", &v)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/http/response/api/peer.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | /*
6 | GroupPeerPayload
7 | https://github.com/rustdesk/rustdesk/blob/master/flutter/lib/common/hbbs/hbbs.dart#L64
8 |
9 | String id = '';
10 | Map info = {};
11 | int? status;
12 | String user = '';
13 | String user_name = '';
14 | String note = '';
15 |
16 | PeerPayload.fromJson(Map json)
17 | : id = json['id'] ?? '',
18 | info = (json['info'] is Map) ? json['info'] : {},
19 | status = json['status'],
20 | user = json['user'] ?? '',
21 | user_name = json['user_name'] ?? '',
22 | note = json['note'] ?? '';
23 |
24 | static Peer toPeer(GroupPeerPayload p) {
25 | return Peer.fromJson({
26 | "id": p.id,
27 | 'loginName': p.user_name,
28 | "username": p.info['username'] ?? '',
29 | "platform": _platform(p.info['os']),
30 | "hostname": p.info['device_name'],
31 | });
32 | }
33 | */
34 | type GroupPeerPayload struct {
35 | Id string `json:"id"`
36 | Info *PeerPayloadInfo `json:"info"`
37 | Status int `json:"status"`
38 | User string `json:"user"`
39 | UserName string `json:"user_name"`
40 | Note string `json:"note"`
41 | DeviceGroupName string `json:"device_group_name"`
42 | }
43 | type PeerPayloadInfo struct {
44 | DeviceName string `json:"device_name"`
45 | Os string `json:"os"`
46 | Username string `json:"username"`
47 | }
48 |
49 | func (gpp *GroupPeerPayload) FromAddressBook(a *model.AddressBook, username string) {
50 | gpp.Id = a.Id
51 | os := a.Platform
52 | if a.Platform == "Mac OS" {
53 | os = "MacOS"
54 | }
55 | gpp.Info = &PeerPayloadInfo{
56 | DeviceName: a.Hostname,
57 | Os: os,
58 | Username: a.Username,
59 | }
60 | gpp.UserName = username
61 | }
62 |
63 | func (gpp *GroupPeerPayload) FromPeer(p *model.Peer, username string, dGroupName string) {
64 | gpp.Id = p.Id
65 | gpp.Info = &PeerPayloadInfo{
66 | DeviceName: p.Hostname,
67 | Os: p.Os,
68 | Username: p.Username,
69 | }
70 | gpp.Note = ""
71 | gpp.UserName = username
72 | gpp.DeviceGroupName = dGroupName
73 | }
74 |
--------------------------------------------------------------------------------
/config/ldap.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type LdapUser struct {
4 | BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching
5 | 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
6 | 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.
7 | Filter string `mapstructure:"filter"`
8 | Username string `mapstructure:"username"`
9 | Email string `mapstructure:"email"`
10 | FirstName string `mapstructure:"first-name"`
11 | LastName string `mapstructure:"last-name"`
12 | Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
13 | AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
14 | AllowGroup string `mapstructure:"allow-group"` // Which group is allowed to login
15 | }
16 |
17 | // type LdapGroup struct {
18 | // BaseDn string `mapstructure:"base-dn"` // The base DN of the group for searching
19 | // Name string `mapstructure:"name"` // The attribute name of the group
20 | // Filter string `mapstructure:"filter"`
21 | // Admin string `mapstructure:"admin"` // Which group is the admin group
22 | // Member string `mapstructure:"member"` // How to get the member of the group: member, uniqueMember, or memberOf (default: member)
23 | // Mode string `mapstructure:"mode"`
24 | // Map map[string]string `mapstructure:"map"` // If mode is "map", map the LDAP group to the internal group
25 | // }
26 |
27 | type Ldap struct {
28 | Enable bool `mapstructure:"enable"`
29 | Url string `mapstructure:"url"`
30 | TlsCaFile string `mapstructure:"tls-ca-file"`
31 | TlsVerify bool `mapstructure:"tls-verify"`
32 | BaseDn string `mapstructure:"base-dn"`
33 | BindDn string `mapstructure:"bind-dn"`
34 | BindPassword string `mapstructure:"bind-password"`
35 | User LdapUser `mapstructure:"user"`
36 | // Group LdapGroup `mapstructure:"group"`
37 | }
38 |
--------------------------------------------------------------------------------
/resources/web/js/src/common.ts:
--------------------------------------------------------------------------------
1 | import * as zstd from "zstddec";
2 | import { KeyEvent, controlKeyFromJSON, ControlKey } from "./message";
3 | import { KEY_MAP, LANGS } from "./gen_js_from_hbb";
4 |
5 | let decompressor: zstd.ZSTDDecoder;
6 |
7 | export async function initZstd() {
8 | const tmp = new zstd.ZSTDDecoder();
9 | await tmp.init();
10 | console.log("zstd ready");
11 | decompressor = tmp;
12 | }
13 |
14 | export async function decompress(compressedArray: Uint8Array) {
15 | const MAX = 1024 * 1024 * 64;
16 | const MIN = 1024 * 1024;
17 | let n = 30 * compressedArray.length;
18 | if (n > MAX) {
19 | n = MAX;
20 | }
21 | if (n < MIN) {
22 | n = MIN;
23 | }
24 | try {
25 | if (!decompressor) {
26 | await initZstd();
27 | }
28 | return decompressor.decode(compressedArray, n);
29 | } catch (e) {
30 | console.error("decompress failed: " + e);
31 | return undefined;
32 | }
33 | }
34 |
35 | const LANG = getLang();
36 |
37 | export function translate(locale: string, text: string): string {
38 | const lang = LANG || locale.substring(locale.length - 2).toLowerCase();
39 | let en = LANGS.en as any;
40 | let dict = (LANGS as any)[lang];
41 | if (!dict) dict = en;
42 | let res = dict[text];
43 | if (!res && lang != "en") res = en[text];
44 | return res || text;
45 | }
46 |
47 | const zCode = "z".charCodeAt(0);
48 | const aCode = "a".charCodeAt(0);
49 |
50 | export function mapKey(name: string, isDesktop: Boolean) {
51 | const tmp = KEY_MAP[name] || name;
52 | if (tmp.length == 1) {
53 | const chr = tmp.charCodeAt(0);
54 | if (!isDesktop && (chr > zCode || chr < aCode))
55 | return KeyEvent.fromPartial({ unicode: chr });
56 | else return KeyEvent.fromPartial({ chr });
57 | }
58 | const control_key = controlKeyFromJSON(tmp);
59 | if (control_key == ControlKey.UNRECOGNIZED) {
60 | console.error("Unknown control key " + tmp);
61 | }
62 | return KeyEvent.fromPartial({ control_key });
63 | }
64 |
65 | export async function sleep(ms: number) {
66 | await new Promise((r) => setTimeout(r, ms));
67 | }
68 |
69 | function getLang(): string {
70 | try {
71 | const queryString = window.location.search;
72 | const urlParams = new URLSearchParams(queryString);
73 | return urlParams.get("lang") || "";
74 | } catch (e) {
75 | return "";
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/resources/web/yuv.js:
--------------------------------------------------------------------------------
1 | var wasmExports;
2 |
3 | fetch('yuv.wasm').then(function (res) { return res.arrayBuffer(); })
4 | .then(function (file) { return WebAssembly.instantiate(file); })
5 | .then(function (wasm) {
6 | wasmExports = wasm.instance.exports;
7 | console.log('yuv ready');
8 | });
9 |
10 | var yPtr, yPtrLen, uPtr, uPtrLen, vPtr, vPtrLen, outPtr, outPtrLen;
11 | let testSpeed = [0, 0];
12 | function I420ToARGB(yb) {
13 | if (!wasmExports) return;
14 | var tm0 = new Date().getTime();
15 | var { malloc, free, memory } = wasmExports;
16 | var HEAPU8 = new Uint8Array(memory.buffer);
17 | let n = yb.y.bytes.length;
18 | if (yPtrLen != n) {
19 | if (yPtr) free(yPtr);
20 | yPtrLen = n;
21 | yPtr = malloc(n);
22 | }
23 | HEAPU8.set(yb.y.bytes, yPtr);
24 | n = yb.u.bytes.length;
25 | if (uPtrLen != n) {
26 | if (uPtr) free(uPtr);
27 | uPtrLen = n;
28 | uPtr = malloc(n);
29 | }
30 | HEAPU8.set(yb.u.bytes, uPtr);
31 | n = yb.v.bytes.length;
32 | if (vPtrLen != n) {
33 | if (vPtr) free(vPtr);
34 | vPtrLen = n;
35 | vPtr = malloc(n);
36 | }
37 | HEAPU8.set(yb.v.bytes, vPtr);
38 | var w = yb.format.displayWidth;
39 | var h = yb.format.displayHeight;
40 | n = w * h * 4;
41 | if (outPtrLen != n) {
42 | if (outPtr) free(outPtr);
43 | outPtrLen = n;
44 | outPtr = malloc(n);
45 | HEAPU8.fill(255, outPtr, outPtr + n);
46 | }
47 | // var res = wasmExports.I420ToARGB(yPtr, yb.y.stride, uPtr, yb.u.stride, vPtr, yb.v.stride, outPtr, w * 4, w, h);
48 | // var res = wasmExports.AVX_YUV_to_ARGB(outPtr, yPtr, yb.y.stride, uPtr, yb.u.stride, vPtr, yb.v.stride, w, h);
49 | var res = wasmExports.yuv420_rgb24_std(w, h, yPtr, uPtr, vPtr, yb.y.stride, yb.v.stride, outPtr, w * 4, 1);
50 | var out = HEAPU8.slice(outPtr, outPtr + n);
51 | testSpeed[1] += new Date().getTime() - tm0;
52 | testSpeed[0] += 1;
53 | if (testSpeed[0] > 30) {
54 | console.log('yuv: ' + parseInt('' + testSpeed[1] / testSpeed[0]));
55 | testSpeed = [0, 0];
56 | }
57 | return out;
58 | }
59 |
60 | var currentFrame;
61 | self.addEventListener('message', (e) => {
62 | currentFrame = e.data;
63 | });
64 |
65 | function run() {
66 | if (currentFrame) {
67 | self.postMessage(I420ToARGB(currentFrame));
68 | currentFrame = undefined;
69 | }
70 | setTimeout(run, 1);
71 | }
72 |
73 | run();
--------------------------------------------------------------------------------
/lib/cache/simple_cache_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestSimpleCache_Set(t *testing.T) {
9 | s := NewSimpleCache()
10 | err := s.Set("key", "value", 0)
11 | if err != nil {
12 | t.Fatalf("写入失败")
13 | }
14 | err = s.Set("key", 111, 0)
15 | if err != nil {
16 | t.Fatalf("写入失败")
17 | }
18 | }
19 |
20 | func TestSimpleCache_Get(t *testing.T) {
21 | s := NewSimpleCache()
22 | err := s.Set("key", "value", 0)
23 | value := ""
24 | err = s.Get("key", &value)
25 | fmt.Println("value", value)
26 | if err != nil {
27 | t.Fatalf("读取失败")
28 | }
29 |
30 | err = s.Set("key1", 11, 0)
31 | value1 := 0
32 | err = s.Get("key1", &value1)
33 | fmt.Println("value1", value1)
34 | if err != nil {
35 | t.Fatalf("读取失败")
36 | }
37 |
38 | err = s.Set("key2", []byte{'a', 'b'}, 0)
39 | value2 := []byte{}
40 | err = s.Get("key2", &value2)
41 | fmt.Println("value2", string(value2))
42 | if err != nil {
43 | t.Fatalf("读取失败")
44 | }
45 |
46 | err = s.Set("key3", 33.33, 0)
47 | var value3 int
48 | err = s.Get("key3", &value3)
49 | fmt.Println("value3", value3)
50 | if err != nil {
51 | t.Fatalf("读取失败")
52 | }
53 |
54 | }
55 |
56 | type r struct {
57 | A string `json:"a"`
58 | B string `json:"b"`
59 | R *rr `json:"r"`
60 | }
61 | type r2 struct {
62 | A string `json:"a"`
63 | B string `json:"b"`
64 | }
65 | type rr struct {
66 | AA string `json:"aa"`
67 | BB string `json:"bb"`
68 | }
69 |
70 | func TestSimpleCache_GetStruct(t *testing.T) {
71 | s := NewSimpleCache()
72 |
73 | old_rr := &rr{
74 | AA: "aa", BB: "bb",
75 | }
76 |
77 | old := &r{
78 | A: "ab", B: "cdc",
79 | R: old_rr,
80 | }
81 | err := s.Set("key", old, 300)
82 | if err != nil {
83 | t.Fatalf("写入失败")
84 | }
85 |
86 | res := &r{}
87 | err2 := s.Get("key", res)
88 | fmt.Println("res", res)
89 | if err2 != nil {
90 | t.Fatalf("读取失败" + err2.Error())
91 |
92 | }
93 |
94 | //修改原始值,看后面是否会变化
95 | old.A = "aa"
96 | old_rr.AA = "aaa"
97 | fmt.Println("old", old)
98 | res2 := &r{}
99 | err3 := s.Get("key", res2)
100 | fmt.Println("res2", res2, res2.R.AA, res2.R.BB)
101 | if err3 != nil {
102 | t.Fatalf("读取失败" + err3.Error())
103 |
104 | }
105 | //if reflect.DeepEqual(res, old) {
106 | // t.Fatalf("读取错误")
107 | //}
108 | }
109 |
--------------------------------------------------------------------------------
/service/serverCmd.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "fmt"
5 | "github.com/lejianwen/rustdesk-api/v2/model"
6 | "net"
7 | "time"
8 | )
9 |
10 | type ServerCmdService struct{}
11 |
12 | // List
13 | func (is *ServerCmdService) List(page, pageSize uint) (res *model.ServerCmdList) {
14 | res = &model.ServerCmdList{}
15 | res.Page = int64(page)
16 | res.PageSize = int64(pageSize)
17 | tx := DB.Model(&model.ServerCmd{})
18 | tx.Count(&res.Total)
19 | tx.Scopes(Paginate(page, pageSize))
20 | tx.Find(&res.ServerCmds)
21 | return
22 | }
23 |
24 | // Info
25 | func (is *ServerCmdService) Info(id uint) *model.ServerCmd {
26 | u := &model.ServerCmd{}
27 | DB.Where("id = ?", id).First(u)
28 | return u
29 | }
30 |
31 | // Delete
32 | func (is *ServerCmdService) Delete(u *model.ServerCmd) error {
33 | return DB.Delete(u).Error
34 | }
35 |
36 | // Create
37 | func (is *ServerCmdService) Create(u *model.ServerCmd) error {
38 | res := DB.Create(u).Error
39 | return res
40 | }
41 |
42 | // SendCmd 发送命令
43 | func (is *ServerCmdService) SendCmd(port int, cmd string, arg string) (string, error) {
44 | //组装命令
45 | cmd = cmd + " " + arg
46 | res, err := is.SendSocketCmd("v6", port, cmd)
47 | if err == nil {
48 | return res, nil
49 | }
50 | //v6连接失败,尝试v4
51 | res, err = is.SendSocketCmd("v4", port, cmd)
52 | if err == nil {
53 | return res, nil
54 | }
55 | return "", err
56 | }
57 |
58 | // SendSocketCmd
59 | func (is *ServerCmdService) SendSocketCmd(ty string, port int, cmd string) (string, error) {
60 | addr := "[::1]"
61 | tcp := "tcp6"
62 | if ty == "v4" {
63 | tcp = "tcp"
64 | addr = "127.0.0.1"
65 | }
66 | conn, err := net.Dial(tcp, fmt.Sprintf("%s:%v", addr, port))
67 | if err != nil {
68 | Logger.Debugf("%s connect to id server failed: %v", ty, err)
69 | return "", err
70 | }
71 | defer conn.Close()
72 | //发送命令
73 | _, err = conn.Write([]byte(cmd))
74 | if err != nil {
75 | Logger.Debugf("%s send cmd failed: %v", ty, err)
76 | return "", err
77 | }
78 | time.Sleep(100 * time.Millisecond)
79 | //读取返回
80 | buf := make([]byte, 1024)
81 | n, err := conn.Read(buf)
82 | if err != nil && err.Error() != "EOF" {
83 | Logger.Debugf("%s read response failed: %v", ty, err)
84 | return "", err
85 | }
86 | return string(buf[:n]), nil
87 | }
88 |
89 | func (is *ServerCmdService) Update(f *model.ServerCmd) error {
90 | return DB.Model(f).Updates(f).Error
91 | }
92 |
--------------------------------------------------------------------------------
/http/controller/api/peer.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/gin-gonic/gin/binding"
7 | requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
8 | "github.com/lejianwen/rustdesk-api/v2/http/response"
9 | "github.com/lejianwen/rustdesk-api/v2/service"
10 | "net/http"
11 | )
12 |
13 | type Peer struct {
14 | }
15 |
16 | // SysInfo
17 | // @Tags System
18 | // @Summary 提交系统信息
19 | // @Description 提交系统信息
20 | // @Accept json
21 | // @Produce json
22 | // @Param body body requstform.PeerForm true "系统信息表单"
23 | // @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND"
24 | // @Failure 500 {object} response.ErrorResponse
25 | // @Router /sysinfo [post]
26 | func (p *Peer) SysInfo(c *gin.Context) {
27 | f := &requstform.PeerForm{}
28 | err := c.ShouldBindBodyWith(f, binding.JSON)
29 | if err != nil {
30 | response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
31 | return
32 | }
33 | fpe := f.ToPeer()
34 | pe := service.AllService.PeerService.FindById(f.Id)
35 | if pe.RowId == 0 {
36 | pe = f.ToPeer()
37 | pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
38 | err = service.AllService.PeerService.Create(pe)
39 | if err != nil {
40 | response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
41 | return
42 | }
43 | } else {
44 | if pe.UserId == 0 {
45 | pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
46 | }
47 | fpe.RowId = pe.RowId
48 | fpe.UserId = pe.UserId
49 | err = service.AllService.PeerService.Update(fpe)
50 | if err != nil {
51 | response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
52 | return
53 | }
54 | }
55 | //SYSINFO_UPDATED 上传成功
56 | //ID_NOT_FOUND 下次心跳会上传
57 | //直接响应文本
58 | c.String(http.StatusOK, "SYSINFO_UPDATED")
59 | }
60 |
61 | // SysInfoVer
62 | // @Tags System
63 | // @Summary 获取系统版本信息
64 | // @Description 获取系统版本信息
65 | // @Accept json
66 | // @Produce json
67 | // @Success 200 {string} string ""
68 | // @Failure 500 {object} response.ErrorResponse
69 | // @Router /sysinfo_ver [post]
70 | func (p *Peer) SysInfoVer(c *gin.Context) {
71 | //读取resources/version文件
72 | v := service.AllService.AppService.GetAppVersion()
73 | // 加上启动时间,方便client上传信息
74 | v = fmt.Sprintf("%s\n%s", v, service.AllService.AppService.GetStartTime())
75 | c.String(http.StatusOK, v)
76 | }
77 |
--------------------------------------------------------------------------------
/lib/cache/file.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "crypto/md5"
5 | "fmt"
6 | "os"
7 | "sync"
8 | "time"
9 | )
10 |
11 | type FileCache struct {
12 | mu sync.Mutex
13 | locks map[string]*sync.Mutex
14 | Dir string
15 | }
16 |
17 | func (fc *FileCache) getLock(key string) *sync.Mutex {
18 | fc.mu.Lock()
19 | defer fc.mu.Unlock()
20 | if fc.locks == nil {
21 | fc.locks = make(map[string]*sync.Mutex)
22 | }
23 | if _, ok := fc.locks[key]; !ok {
24 | fc.locks[key] = new(sync.Mutex)
25 | }
26 | return fc.locks[key]
27 | }
28 |
29 | func (c *FileCache) Get(key string, value interface{}) error {
30 | data, _ := c.getValue(key)
31 | err := DecodeValue(data, value)
32 | return err
33 | }
34 |
35 | // 获取值,如果文件不存在或者过期,返回空,过滤掉错误
36 | func (c *FileCache) getValue(key string) (string, error) {
37 | f := c.fileName(key)
38 | fileInfo, err := os.Stat(f)
39 | if err != nil {
40 | //文件不存在
41 | return "", nil
42 | }
43 | difT := time.Now().Sub(fileInfo.ModTime())
44 | if difT >= 0 {
45 | os.Remove(f)
46 | return "", nil
47 | }
48 | data, err := os.ReadFile(f)
49 | if err != nil {
50 | return "", nil
51 | }
52 | return string(data), nil
53 | }
54 |
55 | // 保存值
56 | func (c *FileCache) saveValue(key string, value string, exp int) error {
57 | f := c.fileName(key)
58 | lock := c.getLock(f)
59 | lock.Lock()
60 | defer lock.Unlock()
61 |
62 | err := os.WriteFile(f, ([]byte)(value), 0644)
63 | if err != nil {
64 | return err
65 | }
66 | if exp <= 0 {
67 | exp = MaxTimeOut
68 | }
69 | expFromNow := time.Now().Add(time.Duration(exp) * time.Second)
70 | err = os.Chtimes(f, expFromNow, expFromNow)
71 | return err
72 | }
73 |
74 | func (c *FileCache) Set(key string, value interface{}, exp int) error {
75 | str, err := EncodeValue(value)
76 | if err != nil {
77 | return err
78 | }
79 |
80 | err = c.saveValue(key, str, exp)
81 | return err
82 | }
83 |
84 | func (c *FileCache) SetDir(path string) {
85 | c.Dir = path
86 | }
87 |
88 | func (c *FileCache) fileName(key string) string {
89 | f := c.Dir + string(os.PathSeparator) + fmt.Sprintf("%x", md5.Sum([]byte(key)))
90 | return f
91 | }
92 |
93 | func (c *FileCache) Gc() error {
94 | //检查文件过期时间,并删除
95 | return nil
96 | }
97 |
98 | func NewFileCache() *FileCache {
99 | return &FileCache{
100 | locks: make(map[string]*sync.Mutex),
101 | Dir: os.TempDir(),
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/lib/cache/memory_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestMemorySet(t *testing.T) {
10 | mc := NewMemoryCache(0)
11 | err := mc.Set("123", "44567", 0)
12 | if err != nil {
13 | fmt.Println(err.Error())
14 | t.Fatalf("写入失败")
15 | }
16 | }
17 |
18 | func TestMemoryGet(t *testing.T) {
19 | mc := NewMemoryCache(0)
20 | mc.Set("123", "44567", 0)
21 | res := ""
22 | err := mc.Get("123", &res)
23 | fmt.Println("res", res)
24 | if err != nil {
25 | t.Fatalf("读取失败 " + err.Error())
26 | }
27 | if res != "44567" {
28 | t.Fatalf("读取错误")
29 | }
30 |
31 | }
32 |
33 | func TestMemorySetExpGet(t *testing.T) {
34 | mc := NewMemoryCache(0)
35 | //mc.stopEviction()
36 | mc.Set("1", "10", 10)
37 | mc.Set("2", "5", 5)
38 | err := mc.Set("3", "3", 3)
39 | if err != nil {
40 | t.Fatalf("写入失败")
41 | }
42 |
43 | res := ""
44 | err = mc.Get("3", &res)
45 | if err != nil {
46 | t.Fatalf("读取失败" + err.Error())
47 | }
48 | fmt.Println("res 3", res)
49 | time.Sleep(4 * time.Second)
50 | //res = ""
51 | err = mc.Get("3", &res)
52 | if err != nil {
53 | t.Fatalf("读取失败" + err.Error())
54 | }
55 | fmt.Println("res 3", res)
56 | err = mc.Get("2", &res)
57 | if err != nil {
58 | t.Fatalf("读取失败" + err.Error())
59 | }
60 | fmt.Println("res 2", res)
61 | err = mc.Get("1", &res)
62 | if err != nil {
63 | t.Fatalf("读取失败" + err.Error())
64 | }
65 | fmt.Println("res 1", res)
66 |
67 | }
68 | func TestMemoryLru(t *testing.T) {
69 | mc := NewMemoryCache(18)
70 | mc.Set("1", "1111", 10)
71 | mc.Set("2", "2222", 5)
72 | //读取一次,2就会被放到最后
73 | mc.Get("1", nil)
74 | err := mc.Set("3", "三", 3)
75 | if err != nil {
76 | //t.Fatalf("写入失败")
77 | }
78 |
79 | res := ""
80 | err = mc.Get("3", &res)
81 | if err != nil {
82 | t.Fatalf("读取失败" + err.Error())
83 | }
84 | fmt.Println("res3", res)
85 | res = ""
86 | err = mc.Get("2", &res)
87 | if err != nil {
88 | t.Fatalf("读取失败" + err.Error())
89 | }
90 | fmt.Println("res2", res)
91 | res = ""
92 | err = mc.Get("1", &res)
93 | if err != nil {
94 | t.Fatalf("读取失败" + err.Error())
95 | }
96 | fmt.Println("res1", res)
97 |
98 | }
99 | func BenchmarkMemorySet(b *testing.B) {
100 | mc := NewMemoryCache(0)
101 | b.ResetTimer()
102 | for i := 0; i < b.N; i++ {
103 | key := fmt.Sprintf("key%d", i)
104 | value := fmt.Sprintf("value%d", i)
105 | mc.Set(key, value, 1000)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/http/request/api/peer.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "github.com/lejianwen/rustdesk-api/v2/model"
4 |
5 | type AddressBookFormData struct {
6 | Tags []string `json:"tags"`
7 | Peers []*model.AddressBook `json:"peers"`
8 | TagColors string `json:"tag_colors"`
9 | }
10 |
11 | type AddressBookForm struct {
12 | Data string `json:"data" example:"{\"tags\":[\"tag1\",\"tag2\",\"tag3\"],\"peers\":[{\"id\":\"abc\",\"username\":\"abv-l\",\"hostname\":\"\",\"platform\":\"Windows\",\"alias\":\"\",\"tags\":[\"tag1\",\"tag2\"],\"hash\":\"hash\"}],\"tag_colors\":\"{\\\"tag1\\\":4288585374,\\\"tag2\\\":4278238420,\\\"tag3\\\":4291681337}\"}"`
13 | }
14 |
15 | type PeerForm struct {
16 | Cpu string `json:"cpu"`
17 | Hostname string `json:"hostname"`
18 | Id string `json:"id"`
19 | Memory string `json:"memory"`
20 | Os string `json:"os"`
21 | Username string `json:"username"`
22 | Uuid string `json:"uuid"`
23 | Version string `json:"version"`
24 | }
25 |
26 | func (pf *PeerForm) ToPeer() *model.Peer {
27 | return &model.Peer{
28 | Cpu: pf.Cpu,
29 | Hostname: pf.Hostname,
30 | Id: pf.Id,
31 | Memory: pf.Memory,
32 | Os: pf.Os,
33 | Username: pf.Username,
34 | Uuid: pf.Uuid,
35 | Version: pf.Version,
36 | }
37 | }
38 |
39 | // PersonalAddressBookForm 个人地址簿表单
40 | type PersonalAddressBookForm struct {
41 | model.AddressBook
42 | ForceAlwaysRelay string `json:"forceAlwaysRelay"`
43 | }
44 |
45 | func (pabf *PersonalAddressBookForm) ToAddressBook() *model.AddressBook {
46 | return &model.AddressBook{
47 | RowId: pabf.RowId,
48 | Id: pabf.Id,
49 | Username: pabf.Username,
50 | Password: pabf.Password,
51 | Hostname: pabf.Hostname,
52 | Alias: pabf.Alias,
53 | Platform: pabf.Platform,
54 | Tags: pabf.Tags,
55 | Hash: pabf.Hash,
56 | UserId: pabf.UserId,
57 | ForceAlwaysRelay: pabf.ForceAlwaysRelay == "true",
58 | RdpPort: pabf.RdpPort,
59 | RdpUsername: pabf.RdpUsername,
60 | Online: pabf.Online,
61 | LoginName: pabf.LoginName,
62 | SameServer: pabf.SameServer,
63 | }
64 | }
65 |
66 | type TagRenameForm struct {
67 | Old string `json:"old"`
68 | New string `json:"new"`
69 | }
70 | type TagColorForm struct {
71 | Name string `json:"name"`
72 | Color uint `json:"color"`
73 | }
74 |
75 | type PeerInfoInHeartbeat struct {
76 | Id string `json:"id"`
77 | Uuid string `json:"uuid"`
78 | Ver int `json:"ver"`
79 | }
80 |
--------------------------------------------------------------------------------
/service/tag.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | "gorm.io/gorm"
6 | )
7 |
8 | type TagService struct {
9 | }
10 |
11 | func (s *TagService) Info(id uint) *model.Tag {
12 | p := &model.Tag{}
13 | DB.Where("id = ?", id).First(p)
14 | return p
15 | }
16 | func (s *TagService) InfoByUserIdAndNameAndCollectionId(userid uint, name string, cid uint) *model.Tag {
17 | p := &model.Tag{}
18 | DB.Where("user_id = ? and name = ? and collection_id = ?", userid, name, cid).First(p)
19 | return p
20 | }
21 |
22 | func (s *TagService) ListByUserId(userId uint) (res *model.TagList) {
23 | res = s.List(1, 1000, func(tx *gorm.DB) {
24 | tx.Where("user_id = ?", userId)
25 | })
26 | return
27 | }
28 | func (s *TagService) ListByUserIdAndCollectionId(userId, cid uint) (res *model.TagList) {
29 | res = s.List(1, 1000, func(tx *gorm.DB) {
30 | tx.Where("user_id = ? and collection_id = ?", userId, cid)
31 | tx.Order("name asc")
32 | })
33 | return
34 | }
35 | func (s *TagService) UpdateTags(userId uint, tags map[string]uint) {
36 | tx := DB.Begin()
37 | //先查询所有tag
38 | var allTags []*model.Tag
39 | tx.Where("user_id = ?", userId).Find(&allTags)
40 | for _, t := range allTags {
41 | if _, ok := tags[t.Name]; !ok {
42 | //删除
43 | tx.Delete(t)
44 | } else {
45 | if tags[t.Name] != t.Color {
46 | //更新
47 | t.Color = tags[t.Name]
48 | tx.Save(t)
49 | }
50 | //移除
51 | delete(tags, t.Name)
52 | }
53 | }
54 | //新增
55 | for tag, color := range tags {
56 | t := &model.Tag{}
57 | t.Name = tag
58 | t.Color = color
59 | t.UserId = userId
60 | tx.Create(t)
61 | }
62 | tx.Commit()
63 | }
64 |
65 | // InfoById 根据用户id取用户信息
66 | func (s *TagService) InfoById(id uint) *model.Tag {
67 | u := &model.Tag{}
68 | DB.Where("id = ?", id).First(u)
69 | return u
70 | }
71 |
72 | func (s *TagService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.TagList) {
73 | res = &model.TagList{}
74 | res.Page = int64(page)
75 | res.PageSize = int64(pageSize)
76 | tx := DB.Model(&model.Tag{})
77 | if where != nil {
78 | where(tx)
79 | }
80 | tx.Count(&res.Total)
81 | tx.Scopes(Paginate(page, pageSize))
82 | tx.Find(&res.Tags)
83 | return
84 | }
85 |
86 | // Create 创建
87 | func (s *TagService) Create(u *model.Tag) error {
88 | res := DB.Create(u).Error
89 | return res
90 | }
91 | func (s *TagService) Delete(u *model.Tag) error {
92 | return DB.Delete(u).Error
93 | }
94 |
95 | // Update 更新
96 | func (s *TagService) Update(u *model.Tag) error {
97 | return DB.Model(u).Select("*").Omit("created_at").Updates(u).Error
98 | }
99 |
--------------------------------------------------------------------------------
/http/request/admin/user.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | )
6 |
7 | type UserForm struct {
8 | Id uint `json:"id"`
9 | Username string `json:"username" validate:"required,gte=2,lte=32"`
10 | Email string `json:"email"` //validate:"required,email" email不强制
11 | //Password string `json:"password" validate:"required,gte=4,lte=20"`
12 | Nickname string `json:"nickname"`
13 | Avatar string `json:"avatar"`
14 | GroupId uint `json:"group_id" validate:"required"`
15 | IsAdmin *bool `json:"is_admin" `
16 | Status model.StatusCode `json:"status" validate:"required,gte=0"`
17 | Remark string `json:"remark"`
18 | }
19 |
20 | func (uf *UserForm) FromUser(user *model.User) *UserForm {
21 | uf.Id = user.Id
22 | uf.Username = user.Username
23 | uf.Nickname = user.Nickname
24 | uf.Email = user.Email
25 | uf.Avatar = user.Avatar
26 | uf.GroupId = user.GroupId
27 | uf.IsAdmin = user.IsAdmin
28 | uf.Status = user.Status
29 | uf.Remark = user.Remark
30 | return uf
31 | }
32 | func (uf *UserForm) ToUser() *model.User {
33 | user := &model.User{}
34 | user.Id = uf.Id
35 | user.Username = uf.Username
36 | user.Nickname = uf.Nickname
37 | user.Email = uf.Email
38 | user.Avatar = uf.Avatar
39 | user.GroupId = uf.GroupId
40 | user.IsAdmin = uf.IsAdmin
41 | user.Status = uf.Status
42 | user.Remark = uf.Remark
43 | return user
44 | }
45 |
46 | type PageQuery struct {
47 | Page uint `form:"page"`
48 | PageSize uint `form:"page_size"`
49 | }
50 |
51 | type UserQuery struct {
52 | PageQuery
53 | Username string `form:"username"`
54 | }
55 | type UserPasswordForm struct {
56 | Id uint `json:"id" validate:"required"`
57 | Password string `json:"password" validate:"required,gte=4,lte=32"`
58 | }
59 |
60 | type ChangeCurPasswordForm struct {
61 | OldPassword string `json:"old_password" validate:"required,gte=4,lte=32"`
62 | NewPassword string `json:"new_password" validate:"required,gte=4,lte=32"`
63 | }
64 | type GroupUsersQuery struct {
65 | IsMy int `json:"is_my"`
66 | UserId uint `json:"user_id"`
67 | }
68 |
69 | type RegisterForm struct {
70 | Username string `json:"username" validate:"required,gte=2,lte=32"`
71 | Email string `json:"email"` // validate:"required,email"
72 | Password string `json:"password" validate:"required,gte=4,lte=32"`
73 | ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=32"`
74 | }
75 |
76 | type UserTokenBatchDeleteForm struct {
77 | Ids []uint `json:"ids" validate:"required"`
78 | }
79 |
--------------------------------------------------------------------------------
/resources/templates/oauth_success.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | OauthSuccess - RustDesk API
7 |
59 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
Close
74 |
75 |
76 |
81 |
82 |
--------------------------------------------------------------------------------
/resources/templates/oauth_fail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | OauthFailed - RustDesk API
7 |
59 |
60 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
Close
74 |
75 |
80 |
81 |
--------------------------------------------------------------------------------
/resources/web/js/gen_js_from_hbb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import re
4 | import os
5 | import glob
6 | from tabnanny import check
7 |
8 | def pad_start(s, n, c = ' '):
9 | if len(s) >= n:
10 | return s
11 | return c * (n - len(s)) + s
12 |
13 | def safe_unicode(s):
14 | res = ""
15 | for c in s:
16 | res += r"\u{}".format(pad_start(hex(ord(c))[2:], 4, '0'))
17 | return res
18 |
19 | def main():
20 | print('export const LANGS = {')
21 | for fn in glob.glob('../../../src/lang/*'):
22 | lang = os.path.basename(fn)[:-3]
23 | if lang == 'template': continue
24 | print(' %s: {'%lang)
25 | for ln in open(fn, encoding='utf-8'):
26 | ln = ln.strip()
27 | if ln.startswith('("'):
28 | toks = ln.split('", "')
29 | assert(len(toks) == 2)
30 | a = toks[0][2:]
31 | b = toks[1][:-3]
32 | print(' "%s": "%s",'%(safe_unicode(a), safe_unicode(b)))
33 | print(' },')
34 | print('}')
35 | check_if_retry = ['', False]
36 | KEY_MAP = ['', False]
37 | for ln in open('../../../src/client.rs', encoding='utf-8'):
38 | ln = ln.strip()
39 | if 'check_if_retry' in ln:
40 | check_if_retry[1] = True
41 | continue
42 | if ln.startswith('}') and check_if_retry[1]:
43 | check_if_retry[1] = False
44 | continue
45 | if check_if_retry[1]:
46 | ln = removeComment(ln)
47 | check_if_retry[0] += ln + '\n'
48 | if 'KEY_MAP' in ln:
49 | KEY_MAP[1] = True
50 | continue
51 | if '.collect' in ln and KEY_MAP[1]:
52 | KEY_MAP[1] = False
53 | continue
54 | if KEY_MAP[1] and ln.startswith('('):
55 | ln = removeComment(ln)
56 | toks = ln.split('", Key::')
57 | assert(len(toks) == 2)
58 | a = toks[0][2:]
59 | b = toks[1].replace('ControlKey(ControlKey::', '').replace("Chr('", '').replace("' as _)),", '').replace(')),', '')
60 | KEY_MAP[0] += ' "%s": "%s",\n'%(a, b)
61 | print()
62 | print('export function checkIfRetry(msgtype: string, title: string, text: string) {')
63 | print(' return %s'%check_if_retry[0].replace('to_lowercase', 'toLowerCase').replace('contains', 'indexOf').replace('!', '').replace('")', '") < 0'))
64 | print(';}')
65 | print()
66 | print('export const KEY_MAP: any = {')
67 | print(KEY_MAP[0])
68 | print('}')
69 | for ln in open('../../../Cargo.toml', encoding='utf-8'):
70 | if ln.startswith('version ='):
71 | print('export const ' + ln)
72 |
73 |
74 | def removeComment(ln):
75 | return re.sub('\s+\/\/.*$', '', ln)
76 |
77 | main()
78 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-mt-wasm.worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}OGVDecoderVideoAV1MTW(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
2 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp8-mt-wasm.worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}OGVDecoderVideoVP8MTW(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
2 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-mt-wasm.worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}OGVDecoderVideoVP9MTW(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
2 |
--------------------------------------------------------------------------------
/http/controller/api/audit.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/gin-gonic/gin/binding"
6 | request "github.com/lejianwen/rustdesk-api/v2/http/request/api"
7 | "github.com/lejianwen/rustdesk-api/v2/http/response"
8 | "github.com/lejianwen/rustdesk-api/v2/model"
9 | "github.com/lejianwen/rustdesk-api/v2/service"
10 | "time"
11 | )
12 |
13 | type Audit struct {
14 | }
15 |
16 | // AuditConn
17 | // @Tags 审计
18 | // @Summary 审计连接
19 | // @Description 审计连接
20 | // @Accept json
21 | // @Produce json
22 | // @Param body body request.AuditConnForm true "审计连接"
23 | // @Success 200 {string} string ""
24 | // @Failure 500 {object} response.Response
25 | // @Router /audit/conn [post]
26 | func (a *Audit) AuditConn(c *gin.Context) {
27 | af := &request.AuditConnForm{}
28 | err := c.ShouldBindBodyWith(af, binding.JSON)
29 | if err != nil {
30 | response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
31 | return
32 | }
33 | /*ttt := &gin.H{}
34 | c.ShouldBindBodyWith(ttt, binding.JSON)
35 | fmt.Println(ttt)*/
36 | ac := af.ToAuditConn()
37 | if af.Action == model.AuditActionNew {
38 | service.AllService.AuditService.CreateAuditConn(ac)
39 | } else if af.Action == model.AuditActionClose {
40 | ex := service.AllService.AuditService.InfoByPeerIdAndConnId(af.Id, af.ConnId)
41 | if ex.Id != 0 {
42 | ex.CloseTime = time.Now().Unix()
43 | service.AllService.AuditService.UpdateAuditConn(ex)
44 | }
45 | } else if af.Action == "" {
46 | ex := service.AllService.AuditService.InfoByPeerIdAndConnId(af.Id, af.ConnId)
47 | if ex.Id != 0 {
48 | up := &model.AuditConn{
49 | IdModel: model.IdModel{Id: ex.Id},
50 | FromPeer: ac.FromPeer,
51 | FromName: ac.FromName,
52 | SessionId: ac.SessionId,
53 | Type: ac.Type,
54 | }
55 | service.AllService.AuditService.UpdateAuditConn(up)
56 | }
57 | }
58 | response.Success(c, "")
59 | }
60 |
61 | // AuditFile
62 | // @Tags 审计
63 | // @Summary 审计文件
64 | // @Description 审计文件
65 | // @Accept json
66 | // @Produce json
67 | // @Param body body request.AuditFileForm true "审计文件"
68 | // @Success 200 {string} string ""
69 | // @Failure 500 {object} response.Response
70 | // @Router /audit/file [post]
71 | func (a *Audit) AuditFile(c *gin.Context) {
72 | aff := &request.AuditFileForm{}
73 | err := c.ShouldBindBodyWith(aff, binding.JSON)
74 | if err != nil {
75 | response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
76 | return
77 | }
78 | //ttt := &gin.H{}
79 | //c.ShouldBindBodyWith(ttt, binding.JSON)
80 | //fmt.Println(ttt)
81 | af := aff.ToAuditFile()
82 | service.AllService.AuditService.CreateAuditFile(af)
83 | response.Success(c, "")
84 | }
85 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-av1-simd-mt-wasm.worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}OGVDecoderVideoAV1SIMDMTW(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
2 |
--------------------------------------------------------------------------------
/resources/web/ogvjs-1.8.6/ogv-decoder-video-vp9-simd-mt-wasm.worker.js:
--------------------------------------------------------------------------------
1 | "use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}OGVDecoderVideoVP9SIMDMTW(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
2 |
--------------------------------------------------------------------------------
/http/controller/admin/config.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/lejianwen/rustdesk-api/v2/global"
6 | "github.com/lejianwen/rustdesk-api/v2/http/response"
7 | "github.com/lejianwen/rustdesk-api/v2/model"
8 | "github.com/lejianwen/rustdesk-api/v2/service"
9 | "os"
10 | "strings"
11 | )
12 |
13 | type Config struct {
14 | }
15 |
16 | // ServerConfig RUSTDESK服务配置
17 | // @Tags ADMIN
18 | // @Summary RUSTDESK服务配置
19 | // @Description 服务配置,给webclient提供api-server
20 | // @Accept json
21 | // @Produce json
22 | // @Success 200 {object} response.Response
23 | // @Failure 500 {object} response.Response
24 | // @Router /admin/config/server [get]
25 | // @Security token
26 | func (co *Config) ServerConfig(c *gin.Context) {
27 | cf := &response.ServerConfigResponse{
28 | IdServer: global.Config.Rustdesk.IdServer,
29 | Key: global.Config.Rustdesk.Key,
30 | RelayServer: global.Config.Rustdesk.RelayServer,
31 | ApiServer: global.Config.Rustdesk.ApiServer,
32 | }
33 | response.Success(c, cf)
34 | }
35 |
36 | // AppConfig APP服务配置
37 | // @Tags ADMIN
38 | // @Summary APP服务配置
39 | // @Description APP服务配置
40 | // @Accept json
41 | // @Produce json
42 | // @Success 200 {object} response.Response
43 | // @Failure 500 {object} response.Response
44 | // @Router /admin/config/app [get]
45 | // @Security token
46 | func (co *Config) AppConfig(c *gin.Context) {
47 | response.Success(c, &gin.H{
48 | "web_client": global.Config.App.WebClient,
49 | })
50 | }
51 |
52 | // AdminConfig ADMIN服务配置
53 | // @Tags ADMIN
54 | // @Summary ADMIN服务配置
55 | // @Description ADMIN服务配置
56 | // @Accept json
57 | // @Produce json
58 | // @Success 200 {object} response.Response
59 | // @Failure 500 {object} response.Response
60 | // @Router /admin/config/admin [get]
61 | // @Security token
62 | func (co *Config) AdminConfig(c *gin.Context) {
63 |
64 | u := &model.User{}
65 | token := c.GetHeader("api-token")
66 | if token != "" {
67 | u, _ = service.AllService.UserService.InfoByAccessToken(token)
68 | if !service.AllService.UserService.CheckUserEnable(u) {
69 | u.Id = 0
70 | }
71 | }
72 |
73 | if u.Id == 0 {
74 | response.Success(c, &gin.H{
75 | "title": global.Config.Admin.Title,
76 | })
77 | return
78 | }
79 |
80 | hello := global.Config.Admin.Hello
81 | if hello == "" {
82 | helloFile := global.Config.Admin.HelloFile
83 | if helloFile != "" {
84 | b, err := os.ReadFile(helloFile)
85 | if err == nil && len(b) > 0 {
86 | hello = string(b)
87 | }
88 | }
89 | }
90 |
91 | //replace {{username}} to username
92 | hello = strings.Replace(hello, "{{username}}", u.Username, -1)
93 | response.Success(c, &gin.H{
94 | "title": global.Config.Admin.Title,
95 | "hello": hello,
96 | })
97 | }
98 |
--------------------------------------------------------------------------------
/service/audit.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/lejianwen/rustdesk-api/v2/model"
5 | "gorm.io/gorm"
6 | )
7 |
8 | type AuditService struct {
9 | }
10 |
11 | func (as *AuditService) AuditConnList(page, pageSize uint, where func(tx *gorm.DB)) (res *model.AuditConnList) {
12 | res = &model.AuditConnList{}
13 | res.Page = int64(page)
14 | res.PageSize = int64(pageSize)
15 | tx := DB.Model(&model.AuditConn{})
16 | if where != nil {
17 | where(tx)
18 | }
19 | tx.Count(&res.Total)
20 | tx.Scopes(Paginate(page, pageSize))
21 | tx.Find(&res.AuditConns)
22 | return
23 | }
24 |
25 | // Create 创建
26 | func (as *AuditService) CreateAuditConn(u *model.AuditConn) error {
27 | res := DB.Create(u).Error
28 | return res
29 | }
30 | func (as *AuditService) DeleteAuditConn(u *model.AuditConn) error {
31 | return DB.Delete(u).Error
32 | }
33 |
34 | // Update 更新
35 | func (as *AuditService) UpdateAuditConn(u *model.AuditConn) error {
36 | return DB.Model(u).Updates(u).Error
37 | }
38 |
39 | // InfoByPeerIdAndConnId
40 | func (as *AuditService) InfoByPeerIdAndConnId(peerId string, connId int64) (res *model.AuditConn) {
41 | res = &model.AuditConn{}
42 | DB.Where("peer_id = ? and conn_id = ?", peerId, connId).First(res)
43 | return
44 | }
45 |
46 | // ConnInfoById
47 | func (as *AuditService) ConnInfoById(id uint) (res *model.AuditConn) {
48 | res = &model.AuditConn{}
49 | DB.Where("id = ?", id).First(res)
50 | return
51 | }
52 |
53 | // FileInfoById
54 | func (as *AuditService) FileInfoById(id uint) (res *model.AuditFile) {
55 | res = &model.AuditFile{}
56 | DB.Where("id = ?", id).First(res)
57 | return
58 | }
59 |
60 | func (as *AuditService) AuditFileList(page, pageSize uint, where func(tx *gorm.DB)) (res *model.AuditFileList) {
61 | res = &model.AuditFileList{}
62 | res.Page = int64(page)
63 | res.PageSize = int64(pageSize)
64 | tx := DB.Model(&model.AuditFile{})
65 | if where != nil {
66 | where(tx)
67 | }
68 | tx.Count(&res.Total)
69 | tx.Scopes(Paginate(page, pageSize))
70 | tx.Find(&res.AuditFiles)
71 | return
72 | }
73 |
74 | // CreateAuditFile
75 | func (as *AuditService) CreateAuditFile(u *model.AuditFile) error {
76 | res := DB.Create(u).Error
77 | return res
78 | }
79 | func (as *AuditService) DeleteAuditFile(u *model.AuditFile) error {
80 | return DB.Delete(u).Error
81 | }
82 |
83 | // Update 更新
84 | func (as *AuditService) UpdateAuditFile(u *model.AuditFile) error {
85 | return DB.Model(u).Updates(u).Error
86 | }
87 |
88 | func (as *AuditService) BatchDeleteAuditConn(ids []uint) error {
89 | return DB.Where("id in (?)", ids).Delete(&model.AuditConn{}).Error
90 | }
91 |
92 | func (as *AuditService) BatchDeleteAuditFile(ids []uint) error {
93 | return DB.Where("id in (?)", ids).Delete(&model.AuditFile{}).Error
94 | }
95 |
--------------------------------------------------------------------------------
/utils/tools.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/md5"
5 | crand "crypto/rand"
6 | "encoding/json"
7 | "fmt"
8 | "reflect"
9 | "runtime/debug"
10 | "strings"
11 | )
12 |
13 | func Md5(str string) string {
14 | t := md5.Sum(([]byte)(str))
15 | return fmt.Sprintf("%x", t)
16 | }
17 |
18 | func CopyStructByJson(src, dst interface{}) {
19 | str, _ := json.Marshal(src)
20 | err := json.Unmarshal(str, dst)
21 | if err != nil {
22 | return
23 | }
24 | }
25 |
26 | // CopyStructToMap 结构体转map
27 | func CopyStructToMap(src interface{}) map[string]interface{} {
28 | var res = map[string]interface{}{}
29 | str, _ := json.Marshal(src)
30 | err := json.Unmarshal(str, &res)
31 | if err != nil {
32 | return nil
33 | }
34 | return res
35 | }
36 |
37 | // SafeGo is a common function to recover panic for goroutines
38 | func SafeGo(f interface{}, params ...interface{}) {
39 | go func() {
40 | defer func() {
41 | if r := recover(); r != nil {
42 | fmt.Printf("Recovered in SafeGo: %v\n", r)
43 | debug.PrintStack()
44 | }
45 | }()
46 |
47 | // Convert f to a reflect.Value
48 | funcValue := reflect.ValueOf(f)
49 |
50 | // Check if the f is a function
51 | if funcValue.Kind() != reflect.Func {
52 | fmt.Println("SafeGo: value is not a function")
53 | return
54 | }
55 |
56 | // Convert params to reflect.Value
57 | paramsValue := make([]reflect.Value, len(params))
58 | for i, param := range params {
59 | paramsValue[i] = reflect.ValueOf(param)
60 | }
61 |
62 | // Call the function f with params
63 | funcValue.Call(paramsValue)
64 | }()
65 | }
66 |
67 | // RandomString 生成随机字符串
68 | func RandomString(n int) string {
69 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
70 | length := len(letterBytes)
71 | b := make([]byte, n)
72 | randomBytes := make([]byte, n)
73 | if _, err := crand.Read(randomBytes); err != nil {
74 | return ""
75 | }
76 | for i, rb := range randomBytes {
77 | b[i] = letterBytes[int(rb)%length]
78 | }
79 | return string(b)
80 | }
81 |
82 | // Keys 泛型函数,K 是键类型,V 是值类型
83 | func Keys[K comparable, V any](m map[K]V) []K {
84 | keys := make([]K, 0, len(m))
85 | for k := range m {
86 | keys = append(keys, k)
87 | }
88 | return keys
89 | }
90 |
91 | // Values 泛型函数,K 是键类型,V 是值类型
92 | func Values[K comparable, V any](m map[K]V) []V {
93 | values := make([]V, 0, len(m))
94 | for _, v := range m {
95 | values = append(values, v)
96 | }
97 | return values
98 | }
99 |
100 | func InArray(k string, arr []string) bool {
101 | for _, v := range arr {
102 | if k == v {
103 | return true
104 | }
105 | }
106 | return false
107 | }
108 |
109 | func StringConcat(strs ...string) string {
110 | var builder strings.Builder
111 | for _, str := range strs {
112 | builder.WriteString(str)
113 | }
114 | return builder.String()
115 | }
116 |
--------------------------------------------------------------------------------
/lib/jwt/jwt_test.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | var pk = `-----BEGIN RSA PRIVATE KEY-----
10 | MIIEowIBAAKCAQEAnJpq2Sy91iGW3+EuG4V2ke59tITpGINzht0rO8WiRwu11W4p
11 | wakS4K4BbjvmC8YjaxXhKE5LHDw0IXvTdIDN7Fuu4qs9xWXIoK+nC3qWrVBtj/1o
12 | RJrYme1NenTXEgPlN1FOU6/9XQGgvb+1MSNqxknYo7183mHACvsIIuSTMEFhUbUw
13 | XYVQrCtACUILZ9wIDOEzclIY2ZPMTnL1vkvfj629KwGtAvpEyc96Y/HMSH5/VkiG
14 | p6L+k+NSjco9HntAGYTiQkfranvdqxRDUsKS53SbV3QSz1zc0l5OEyZDuxFTL7UC
15 | 7v0G/HVqz6mLpMje756PG/WEpwa/lADc/8FJ5QIDAQABAoIBAEsqUt6qevOsa55J
16 | lrfe92pT7kIXCUqazXiN75Jg6eLv2/b1SVWKsWTmIAmo9mHwWE+t0MRnz+VdgCgS
17 | JwxkRnKMDwT87Eky8Xku1h7MWEYXtH7IQqOrLwuyut1r907OT9adT9sbPaDGh0CM
18 | I4vSVA2YpELzUFvszyB2HRGiZINkHfdLsNxUKsHJOdXbv82RItwzmCYcZismnR3J
19 | P8THn06eoBNtlqwdFziuREOzjNnj6J/3glhR5mu4c4+AJoj0hmVaBDfac3GsQsbP
20 | x79QQPrUqH9UZ4szubYHXP0uRi/ARlHQ+GNp6foYIsevC0OtLdau0/ouFlfGkEep
21 | 3aIV5oECgYEAyyWrNhw+BhNFXsyPzEQ4/mO5ucup3cE/tAAtLiSckoXjmY8K7PQr
22 | xfKRCkuM1qpcxtYkbTs35aOdK48gL0NVd50QzrWFrQkQkVnpnJ1lYeVgEL1DmalD
23 | B55bwTdShcs0gEoKefZCvmotrmYdSpMGsapqqbZFrysFFzRDyDxnHfcCgYEAxVjA
24 | /dXxCEUjYFVC3i833lI/yiycJrhjIeffc6DqpSReuTU+i8Nh3sLiytaSqPFVASDS
25 | 08K3JwVguMTzDgrYkl365lm50WxcBuNgLkSqA90vE/H6gkRZVkuzOb7T+ZdDxf0s
26 | 7RH4aqeeOSiOcZ3uC+d53UArJFidETXbgguXkAMCgYA22Ynbx05b15IwYW0mCvmU
27 | fhqkdr/7lvT7RdztC4eW7D2itYOOrPKwtKjCrdluEHuSWDlnoMib4UxLeY6IFFcc
28 | P7VNCqf4K21kwXEZD0pTX1pLyr5Y2+G0SeaeSbCnXVFknhksCvjEbui8oOehvgbd
29 | q5S3E/bGsAfk1wDCLMTuywKBgACHrH0CBhOvm9i2YeeW2N+P+PviAslX1WxR4xe8
30 | ZuTqpBZ7Ph/B9pFSlKlWyi4J9+B45hgLfdJtAUV9welXvh0mg3X657TYRab/FVMK
31 | fCpmfangDHwtEtBYg7K0AH27GkN92pEIa1JeAN7GbRuBARKnHHyrn3IJiuJw8pX2
32 | 0gFhAoGBAIquI9sAB2dKEOMW+iQJkLH8Hh8/EWyslow+QJiyIsRe1l9jtkOxC5D3
33 | Hj4yO4j5LOWDMTgDcLsZTxbGiTzkNc/HghrNIevDAQdgjJQNl84zDjyyCA4r/MA7
34 | bYJTtYj8q6J0EDbRdT9b6hMclyzjNXdx2loJxR0R8WUeL1lDEPq8
35 | -----END RSA PRIVATE KEY-----`
36 |
37 | // 测试token生成
38 | func TestGenerateToken(t *testing.T) {
39 | jwtService := NewJwt(pk, time.Second*1000)
40 | token := jwtService.GenerateToken(1)
41 | if token == "" {
42 | t.Fatal("token生成失败")
43 | }
44 | fmt.Println(pk, token)
45 | }
46 |
47 | // 测试token解析
48 | func TestParseToken(t *testing.T) {
49 | jwtService := NewJwt(pk, time.Second*1000)
50 | token := jwtService.GenerateToken(999)
51 | if token == "" {
52 | t.Fatal("token生成失败")
53 | }
54 | uid, err := jwtService.ParseToken(token)
55 | if err != nil {
56 |
57 | t.Fatal("token解析失败", err)
58 | }
59 | if uid != 999 {
60 | t.Fatal("token解析失败")
61 | }
62 | }
63 |
64 | func BenchmarkJwtService_GenerateToken(b *testing.B) {
65 | jwtService := NewJwt(pk, time.Second*1000)
66 | b.ResetTimer()
67 | for i := 0; i < b.N; i++ {
68 | jwtService.GenerateToken(999)
69 | }
70 | }
71 |
72 | func BenchmarkJwtService_ParseToken(b *testing.B) {
73 | jwtService := NewJwt(pk, time.Second*1000)
74 | token := jwtService.GenerateToken(999)
75 | b.ResetTimer()
76 | for i := 0; i < b.N; i++ {
77 | _, _ = jwtService.ParseToken(token)
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/http/response/response.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/lejianwen/rustdesk-api/v2/global"
7 | "github.com/nicksnyder/go-i18n/v2/i18n"
8 | "net/http"
9 | )
10 |
11 | type Response struct {
12 | Code int `json:"code"`
13 | Message string `json:"message"`
14 | Data interface{} `json:"data"`
15 | }
16 | type PageData struct {
17 | Page int `json:"page"`
18 | Total int `json:"total"`
19 | List interface{} `json:"list"`
20 | }
21 |
22 | type DataResponse struct {
23 | Total uint `json:"total"`
24 | Data interface{} `json:"data"`
25 | }
26 |
27 | type ErrorResponse struct {
28 | Error string `json:"error"`
29 | }
30 |
31 | func SendResponse(c *gin.Context, code int, message string, data interface{}) {
32 | c.JSON(http.StatusOK, Response{
33 | code, message, data,
34 | })
35 | }
36 |
37 | func Success(c *gin.Context, data interface{}) {
38 | SendResponse(c, 0, "success", data)
39 | }
40 |
41 | func Fail(c *gin.Context, code int, message string) {
42 | SendResponse(c, code, message, nil)
43 | }
44 |
45 | func Error(c *gin.Context, message string) {
46 | c.JSON(http.StatusBadRequest, ErrorResponse{
47 | Error: message,
48 | })
49 | }
50 |
51 | type ServerConfigResponse struct {
52 | IdServer string `json:"id_server"`
53 | Key string `json:"key"`
54 | RelayServer string `json:"relay_server"`
55 | ApiServer string `json:"api_server"`
56 | }
57 |
58 | func TranslateMsg(c *gin.Context, messageId string) string {
59 | localizer := global.Localizer(c.GetHeader("Accept-Language"))
60 | errMsg, err := localizer.LocalizeMessage(&i18n.Message{
61 | ID: messageId,
62 | })
63 | if err != nil {
64 | global.Logger.Warn("LocalizeMessage Error: " + err.Error())
65 | errMsg = messageId
66 | }
67 | return errMsg
68 | }
69 | func TranslateTempMsg(c *gin.Context, messageId string, templateData map[string]interface{}) string {
70 | localizer := global.Localizer(c.GetHeader("Accept-Language"))
71 | errMsg, err := localizer.Localize(&i18n.LocalizeConfig{
72 | DefaultMessage: &i18n.Message{
73 | ID: messageId,
74 | },
75 | TemplateData: templateData,
76 | })
77 | if err != nil {
78 | global.Logger.Warn("LocalizeMessage Error: " + err.Error())
79 | errMsg = messageId
80 | }
81 | return errMsg
82 | }
83 | func TranslateParamMsg(c *gin.Context, messageId string, params ...string) string {
84 | localizer := global.Localizer(c.GetHeader("Accept-Language"))
85 | templateData := make(map[string]interface{})
86 | for i, v := range params {
87 | k := fmt.Sprintf("P%d", i)
88 | templateData[k] = v
89 | }
90 | errMsg, err := localizer.Localize(&i18n.LocalizeConfig{
91 | DefaultMessage: &i18n.Message{
92 | ID: messageId,
93 | },
94 | TemplateData: templateData,
95 | })
96 | if err != nil {
97 | global.Logger.Warn("LocalizeMessage Error: " + err.Error())
98 | errMsg = messageId
99 | }
100 | return errMsg
101 | }
102 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "strings"
7 | "time"
8 | )
9 |
10 | const (
11 | DebugMode = "debug"
12 | ReleaseMode = "release"
13 | DefaultConfig = "conf/config.yaml"
14 | )
15 |
16 | type App struct {
17 | WebClient int `mapstructure:"web-client"`
18 | Register bool `mapstructure:"register"`
19 | RegisterStatus int `mapstructure:"register-status"`
20 | ShowSwagger int `mapstructure:"show-swagger"`
21 | TokenExpire time.Duration `mapstructure:"token-expire"`
22 | WebSso bool `mapstructure:"web-sso"`
23 | DisablePwdLogin bool `mapstructure:"disable-pwd-login"`
24 | CaptchaThreshold int `mapstructure:"captcha-threshold"`
25 | BanThreshold int `mapstructure:"ban-threshold"`
26 | }
27 | type Admin struct {
28 | Title string `mapstructure:"title"`
29 | Hello string `mapstructure:"hello"`
30 | HelloFile string `mapstructure:"hello-file"`
31 | IdServerPort int `mapstructure:"id-server-port"`
32 | RelayServerPort int `mapstructure:"relay-server-port"`
33 | }
34 | type Config struct {
35 | Lang string `mapstructure:"lang"`
36 | App App
37 | Admin Admin
38 | Gorm Gorm
39 | Mysql Mysql
40 | Postgresql Postgresql
41 | Gin Gin
42 | Logger Logger
43 | Redis Redis
44 | Cache Cache
45 | Oss Oss
46 | Jwt Jwt
47 | Rustdesk Rustdesk
48 | Proxy Proxy
49 | Ldap Ldap
50 | }
51 |
52 | func (a *Admin) Init() {
53 | if a.IdServerPort == 0 {
54 | a.IdServerPort = DefaultIdServerPort
55 | }
56 | if a.RelayServerPort == 0 {
57 | a.RelayServerPort = DefaultRelayServerPort
58 | }
59 | }
60 |
61 | // Init 初始化配置
62 | func Init(rowVal *Config, path string) *viper.Viper {
63 | if path == "" {
64 | path = DefaultConfig
65 | }
66 | v := viper.GetViper()
67 | v.AutomaticEnv()
68 | v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
69 | v.SetEnvPrefix("RUSTDESK_API")
70 | v.SetConfigFile(path)
71 | v.SetConfigType("yaml")
72 | err := v.ReadInConfig()
73 | if err != nil {
74 | panic(fmt.Errorf("Fatal error config file: %s \n", err))
75 | }
76 | /*
77 | v.WatchConfig()
78 |
79 |
80 | //监听配置修改没什么必要
81 | v.OnConfigChange(func(e fsnotify.Event) {
82 | //配置文件修改监听
83 | fmt.Println("config file changed:", e.Name)
84 | if err2 := v.Unmarshal(rowVal); err2 != nil {
85 | fmt.Println(err2)
86 | }
87 | rowVal.Rustdesk.LoadKeyFile()
88 | rowVal.Rustdesk.ParsePort()
89 | })
90 | */
91 | if err := v.Unmarshal(rowVal); err != nil {
92 | panic(fmt.Errorf("Fatal error config: %s \n", err))
93 | }
94 | rowVal.Rustdesk.LoadKeyFile()
95 | rowVal.Admin.Init()
96 | return v
97 | }
98 |
99 | // ReadEnv 读取环境变量
100 | func ReadEnv(rowVal interface{}) *viper.Viper {
101 | v := viper.New()
102 | v.AutomaticEnv()
103 | if err := v.Unmarshal(rowVal); err != nil {
104 | fmt.Println(err)
105 | }
106 | return v
107 | }
108 |
--------------------------------------------------------------------------------