client_crud.go 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. package service
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/netip"
  8. "strings"
  9. "time"
  10. "unicode"
  11. "github.com/google/uuid"
  12. "github.com/mhsanaei/3x-ui/v3/internal/database"
  13. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  14. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  15. "github.com/mhsanaei/3x-ui/v3/internal/util/random"
  16. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  17. "gorm.io/gorm"
  18. )
  19. func hasForbiddenClientChar(s string) bool {
  20. for _, r := range s {
  21. if r == '/' || r == '\\' || r < 0x20 || r == 0x7f || unicode.IsSpace(r) {
  22. return true
  23. }
  24. }
  25. return false
  26. }
  27. func validateClientEmail(email string) error {
  28. if hasForbiddenClientChar(email) {
  29. return common.NewError("client email contains an invalid character:", email)
  30. }
  31. return nil
  32. }
  33. func validateClientSubID(subID string) error {
  34. if hasForbiddenClientChar(subID) {
  35. return common.NewError("client subId contains an invalid character:", subID)
  36. }
  37. return nil
  38. }
  39. // Rejected rather than coerced: an unknown cycle would leave the operator with
  40. // a field that reads as configured while no job ever selects the client.
  41. func validateClientTrafficReset(period string, day int) error {
  42. switch period {
  43. case "", "never", "hourly", "daily", "weekly", "monthly":
  44. default:
  45. return common.NewError("client trafficReset must be never, hourly, daily, weekly or monthly, got:", period)
  46. }
  47. if day < 0 || day > 31 {
  48. return common.NewError("client trafficResetDay must be between 0 and 31, got:", day)
  49. }
  50. return nil
  51. }
  52. // Rejected rather than clamped: nextCalendarRenewal would silently move an
  53. // out-of-range day, and a negative one drops out of the renewal query entirely.
  54. func validateClientResetDay(day int) error {
  55. if day < 0 || day > 31 {
  56. return common.NewError("client resetDay must be between 0 and 31, got:", day)
  57. }
  58. return nil
  59. }
  60. // Rejected rather than coerced: a negative cap reads as "unlimited" to a caller
  61. // but selects nothing, so the client would silently stop renewing.
  62. func validateClientResetMax(resetMax int) error {
  63. if resetMax < 0 {
  64. return common.NewError("client resetMax must not be negative, got:", resetMax)
  65. }
  66. return nil
  67. }
  68. // normalizeClientTrafficReset stores what the inbound path would store, so the
  69. // day never reaches the DB as a 0 that three layers downstream each clamp to 1.
  70. func normalizeClientTrafficReset(c *model.Client) {
  71. if c.TrafficReset == "" {
  72. c.TrafficReset = "never"
  73. }
  74. c.TrafficResetDay = normalizeTrafficResetDay(c.TrafficResetDay)
  75. }
  76. // ClientResetCycle is the slice of a client the reset job needs: enough to know
  77. // whether it is due, and whether its disable is the quota's doing or the operator's.
  78. type ClientResetCycle struct {
  79. Email string
  80. TrafficResetDay int
  81. Enable bool
  82. Total int64
  83. Used int64
  84. }
  85. // Depleted reports a client the quota switched off. A reset restores that one;
  86. // a client disabled below its quota was switched off by hand and stays off.
  87. func (c ClientResetCycle) Depleted() bool {
  88. return c.Total > 0 && c.Used >= c.Total
  89. }
  90. // GetClientsByTrafficReset returns the clients whose own reset cycle matches the
  91. // period, independent of the cycle configured on the inbounds they belong to.
  92. func (s *ClientService) GetClientsByTrafficReset(period string) ([]ClientResetCycle, error) {
  93. var cycles []ClientResetCycle
  94. err := database.GetDB().Table("clients c").
  95. Select("c.email, c.traffic_reset_day, c.enable, COALESCE(ct.total, 0) AS total, COALESCE(ct.up, 0) + COALESCE(ct.down, 0) AS used").
  96. Joins("LEFT JOIN client_traffics ct ON ct.email = c.email").
  97. Where("c.traffic_reset = ?", period).
  98. Scan(&cycles).Error
  99. if err != nil {
  100. return nil, err
  101. }
  102. return cycles, nil
  103. }
  104. func (s *ClientService) Create(inboundSvc *InboundService, payload *ClientCreatePayload) (bool, error) {
  105. if payload == nil {
  106. return false, common.NewError("empty payload")
  107. }
  108. client := payload.Client
  109. if strings.TrimSpace(client.Email) == "" {
  110. return false, common.NewError("client email is required")
  111. }
  112. if err := validateClientEmail(client.Email); err != nil {
  113. return false, err
  114. }
  115. if err := validateClientSubID(client.SubID); err != nil {
  116. return false, err
  117. }
  118. if err := validateClientResetDay(client.ResetDay); err != nil {
  119. return false, err
  120. }
  121. if err := validateClientResetMax(client.ResetMax); err != nil {
  122. return false, err
  123. }
  124. if err := validateClientTrafficReset(client.TrafficReset, client.TrafficResetDay); err != nil {
  125. return false, err
  126. }
  127. normalizeClientTrafficReset(&client)
  128. if len(payload.InboundIds) == 0 {
  129. return false, common.NewError("at least one inbound is required")
  130. }
  131. if client.SubID == "" {
  132. client.SubID = uuid.NewString()
  133. }
  134. if !client.Enable {
  135. client.Enable = true
  136. }
  137. now := time.Now().UnixMilli()
  138. if client.CreatedAt == 0 {
  139. client.CreatedAt = now
  140. }
  141. client.UpdatedAt = now
  142. existing := &model.ClientRecord{}
  143. err := database.GetDB().Where("email = ?", client.Email).First(existing).Error
  144. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  145. return false, err
  146. }
  147. emailTaken := !errors.Is(err, gorm.ErrRecordNotFound)
  148. if emailTaken {
  149. if existing.SubID == "" || existing.SubID != client.SubID {
  150. return false, common.NewError("email already in use:", client.Email)
  151. }
  152. // Reuse stored credentials when re-adding an existing identity, or
  153. // fillProtocolDefaults mints a fresh UUID that desyncs other inbounds.
  154. if client.ID == "" {
  155. client.ID = existing.UUID
  156. }
  157. if client.Password == "" {
  158. client.Password = existing.Password
  159. }
  160. if client.Auth == "" {
  161. client.Auth = existing.Auth
  162. }
  163. if client.Secret == "" {
  164. client.Secret = existing.Secret
  165. }
  166. }
  167. if client.SubID != "" {
  168. var subTaken int64
  169. if err := database.GetDB().Model(&model.ClientRecord{}).
  170. Where("sub_id = ? AND email <> ?", client.SubID, client.Email).
  171. Count(&subTaken).Error; err != nil {
  172. return false, err
  173. }
  174. if subTaken > 0 {
  175. return false, common.NewError("subId already in use:", client.SubID)
  176. }
  177. }
  178. needRestart := false
  179. for _, ibId := range payload.InboundIds {
  180. inbound, getErr := inboundSvc.GetInbound(ibId)
  181. if getErr != nil {
  182. return needRestart, getErr
  183. }
  184. if err := s.fillProtocolDefaults(&client, inbound); err != nil {
  185. return needRestart, err
  186. }
  187. clientForInbound := client
  188. if ips, ok := client.AllowedIPsByInbound[ibId]; ok {
  189. clientForInbound.AllowedIPs = ips
  190. } else if !addressesFitAmneziaWGInbound(clientForInbound.AllowedIPs, inbound) {
  191. // The shared AllowedIPs value (e.g. from a single-field legacy
  192. // caller) came from a different subnet than this inbound's own --
  193. // clear it so defaultAmneziaWGClients allocates a fresh, correct
  194. // address for THIS inbound instead of persisting an unroutable
  195. // peer. Same reasoning as addressesFitAmneziaWGInbound's own doc
  196. // comment on the Attach path.
  197. clientForInbound.AllowedIPs = nil
  198. }
  199. settingsPayload, mErr := json.Marshal(map[string][]model.Client{"clients": {clientWithInboundFlow(clientForInbound, inbound)}})
  200. if mErr != nil {
  201. return needRestart, mErr
  202. }
  203. nr, addErr := s.AddInboundClient(inboundSvc, &model.Inbound{
  204. Id: ibId,
  205. Settings: string(settingsPayload),
  206. })
  207. if addErr != nil {
  208. return needRestart, addErr
  209. }
  210. if nr {
  211. needRestart = true
  212. }
  213. }
  214. if err := s.setClientLimitHwidByEmail(nil, client.Email, payload.LimitHwid); err != nil {
  215. return needRestart, err
  216. }
  217. return needRestart, nil
  218. }
  219. func (s *ClientService) fillProtocolDefaults(c *model.Client, ib *model.Inbound) error {
  220. switch ib.Protocol {
  221. case model.VMESS, model.VLESS:
  222. if c.ID == "" {
  223. c.ID = uuid.NewString()
  224. }
  225. case model.Trojan:
  226. if c.Password == "" {
  227. c.Password = strings.ReplaceAll(uuid.NewString(), "-", "")
  228. }
  229. case model.Shadowsocks:
  230. method := shadowsocksMethodFromSettings(ib.Settings)
  231. if c.Password == "" || !validShadowsocksClientKey(method, c.Password) {
  232. c.Password = randomShadowsocksClientKey(method)
  233. }
  234. case model.Hysteria:
  235. if c.Auth == "" {
  236. c.Auth = strings.ReplaceAll(uuid.NewString(), "-", "")
  237. }
  238. case model.MTProto:
  239. if c.Secret == "" {
  240. c.Secret = model.GenerateFakeTLSSecret(mtprotoDomainFromSettings(ib.Settings))
  241. }
  242. }
  243. return nil
  244. }
  245. // defaultMtprotoDomain is the FakeTLS fronting domain used when an mtproto
  246. // inbound carries no fakeTlsDomain of its own; it mirrors the frontend default.
  247. const defaultMtprotoDomain = "www.cloudflare.com"
  248. // mtprotoDomainFromSettings returns the inbound-level FakeTLS domain, falling
  249. // back to the default when unset, so a generated client secret always fronts a
  250. // real hostname.
  251. func mtprotoDomainFromSettings(settings string) string {
  252. domain := ""
  253. if settings != "" {
  254. var m map[string]any
  255. if err := json.Unmarshal([]byte(settings), &m); err == nil {
  256. domain, _ = m["fakeTlsDomain"].(string)
  257. }
  258. }
  259. domain = strings.TrimSpace(domain)
  260. if domain == "" {
  261. return defaultMtprotoDomain
  262. }
  263. return domain
  264. }
  265. func clientWithInboundFlow(c model.Client, ib *model.Inbound) model.Client {
  266. if ib.DisableFlow || !inboundCanEnableTlsFlow(string(ib.Protocol), ib.StreamSettings, ib.Settings) {
  267. c.Flow = ""
  268. }
  269. return c
  270. }
  271. func shadowsocksMethodFromSettings(settings string) string {
  272. if settings == "" {
  273. return ""
  274. }
  275. var m map[string]any
  276. if err := json.Unmarshal([]byte(settings), &m); err != nil {
  277. return ""
  278. }
  279. method, _ := m["method"].(string)
  280. return method
  281. }
  282. func randomShadowsocksClientKey(method string) string {
  283. if n := shadowsocksKeyBytes(method); n > 0 {
  284. return random.Base64Bytes(n)
  285. }
  286. return strings.ReplaceAll(uuid.NewString(), "-", "")
  287. }
  288. func validShadowsocksClientKey(method, key string) bool {
  289. n := shadowsocksKeyBytes(method)
  290. if n == 0 {
  291. return key != ""
  292. }
  293. decoded, err := base64.StdEncoding.DecodeString(key)
  294. if err != nil {
  295. return false
  296. }
  297. return len(decoded) == n
  298. }
  299. func shadowsocksKeyBytes(method string) int {
  300. switch method {
  301. case "2022-blake3-aes-128-gcm":
  302. return 16
  303. case "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305":
  304. return 32
  305. }
  306. return 0
  307. }
  308. // normalizeShadowsocksClientKeys rewrites any Shadowsocks-2022 client password
  309. // whose decoded length no longer matches settings.method, which happens after the
  310. // inbound method is switched between ciphers of different key sizes (e.g.
  311. // aes-256↔aes-128). A wrong-length uPSK makes xray reject the user, so the link
  312. // fails to connect; regenerating restores a valid key (clients must re-fetch).
  313. // Non-Shadowsocks / legacy-SS settings pass through unchanged.
  314. func normalizeShadowsocksClientKeys(settings string) (string, bool) {
  315. method := shadowsocksMethodFromSettings(settings)
  316. if shadowsocksKeyBytes(method) == 0 {
  317. return settings, false
  318. }
  319. var m map[string]any
  320. if err := json.Unmarshal([]byte(settings), &m); err != nil {
  321. return settings, false
  322. }
  323. clients, ok := m["clients"].([]any)
  324. if !ok {
  325. return settings, false
  326. }
  327. changed := false
  328. for i := range clients {
  329. c, ok := clients[i].(map[string]any)
  330. if !ok {
  331. continue
  332. }
  333. if pw, _ := c["password"].(string); validShadowsocksClientKey(method, pw) {
  334. continue
  335. }
  336. c["password"] = randomShadowsocksClientKey(method)
  337. clients[i] = c
  338. changed = true
  339. }
  340. if !changed {
  341. return settings, false
  342. }
  343. m["clients"] = clients
  344. bs, err := json.MarshalIndent(m, "", " ")
  345. if err != nil {
  346. return settings, false
  347. }
  348. return string(bs), true
  349. }
  350. func applyShadowsocksClientMethod(clients []any, settings map[string]any) {
  351. method, _ := settings["method"].(string)
  352. is2022 := strings.HasPrefix(method, "2022-blake3-")
  353. for i := range clients {
  354. cm, ok := clients[i].(map[string]any)
  355. if !ok {
  356. continue
  357. }
  358. if is2022 {
  359. if _, hasKey := cm["method"]; hasKey {
  360. delete(cm, "method")
  361. clients[i] = cm
  362. }
  363. continue
  364. }
  365. if method == "" {
  366. continue
  367. }
  368. if existing, _ := cm["method"].(string); existing != "" {
  369. continue
  370. }
  371. cm["method"] = method
  372. clients[i] = cm
  373. }
  374. }
  375. func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model.Client, limitHwid int, inboundFilter ...int) (bool, error) {
  376. existing, err := s.GetByID(id)
  377. if err != nil {
  378. return false, err
  379. }
  380. inboundIds, err := s.GetInboundIdsForRecord(id)
  381. if err != nil {
  382. return false, err
  383. }
  384. if len(inboundFilter) > 0 {
  385. allow := make(map[int]struct{}, len(inboundFilter))
  386. for _, fid := range inboundFilter {
  387. allow[fid] = struct{}{}
  388. }
  389. filtered := inboundIds[:0:0]
  390. for _, ibId := range inboundIds {
  391. if _, ok := allow[ibId]; ok {
  392. filtered = append(filtered, ibId)
  393. }
  394. }
  395. inboundIds = filtered
  396. }
  397. if strings.TrimSpace(updated.Email) == "" {
  398. return false, common.NewError("client email is required")
  399. }
  400. if err := validateClientEmail(updated.Email); err != nil {
  401. return false, err
  402. }
  403. if err := validateClientSubID(updated.SubID); err != nil {
  404. return false, err
  405. }
  406. if err := validateClientResetDay(updated.ResetDay); err != nil {
  407. return false, err
  408. }
  409. if err := validateClientResetMax(updated.ResetMax); err != nil {
  410. return false, err
  411. }
  412. if err := validateClientTrafficReset(updated.TrafficReset, updated.TrafficResetDay); err != nil {
  413. return false, err
  414. }
  415. normalizeClientTrafficReset(&updated)
  416. if updated.SubID == "" {
  417. updated.SubID = existing.SubID
  418. }
  419. if updated.SubID == "" {
  420. updated.SubID = uuid.NewString()
  421. }
  422. updated.UpdatedAt = time.Now().UnixMilli()
  423. if updated.CreatedAt == 0 {
  424. updated.CreatedAt = existing.CreatedAt
  425. }
  426. // Preserve existing credentials when the caller omits them, so a partial
  427. // update (e.g. only changing traffic/expiry) doesn't silently rotate the
  428. // client's UUID/password/auth via fillProtocolDefaults. Supplying a new
  429. // value still rotates it intentionally.
  430. if updated.ID == "" {
  431. updated.ID = existing.UUID
  432. }
  433. if updated.Password == "" {
  434. updated.Password = existing.Password
  435. }
  436. if updated.Auth == "" {
  437. updated.Auth = existing.Auth
  438. }
  439. if updated.Secret == "" {
  440. updated.Secret = existing.Secret
  441. }
  442. if updated.Email != existing.Email {
  443. var collisionCount int64
  444. if err := database.GetDB().Model(&model.ClientRecord{}).
  445. Where("email = ? AND id <> ?", updated.Email, id).
  446. Count(&collisionCount).Error; err != nil {
  447. return false, err
  448. }
  449. if collisionCount > 0 {
  450. return false, common.NewError("Duplicate email:", updated.Email)
  451. }
  452. }
  453. if updated.SubID != existing.SubID {
  454. var subCollision int64
  455. if err := database.GetDB().Model(&model.ClientRecord{}).
  456. Where("sub_id = ? AND id <> ?", updated.SubID, id).
  457. Count(&subCollision).Error; err != nil {
  458. return false, err
  459. }
  460. if subCollision > 0 {
  461. return false, common.NewError("Duplicate subId:", updated.SubID)
  462. }
  463. }
  464. needRestart := false
  465. for _, ibId := range inboundIds {
  466. inbound, getErr := inboundSvc.GetInbound(ibId)
  467. if getErr != nil {
  468. if errors.Is(getErr, gorm.ErrRecordNotFound) {
  469. if err := database.GetDB().
  470. Where("client_id = ? AND inbound_id = ?", id, ibId).
  471. Delete(&model.ClientInbound{}).Error; err != nil {
  472. return needRestart, err
  473. }
  474. continue
  475. }
  476. return needRestart, getErr
  477. }
  478. if existing.Email == "" {
  479. continue
  480. }
  481. if err := s.fillProtocolDefaults(&updated, inbound); err != nil {
  482. return needRestart, err
  483. }
  484. clientForInbound := updated
  485. if ips, ok := updated.AllowedIPsByInbound[ibId]; ok {
  486. clientForInbound.AllowedIPs = ips
  487. } else if !addressesFitAmneziaWGInbound(clientForInbound.AllowedIPs, inbound) {
  488. // A single shared AllowedIPs field (the common case for a caller
  489. // that never sends AllowedIPsByInbound) must never overwrite an
  490. // inbound it doesn't belong to -- e.g. a client attached to both
  491. // wg and awg saving its wg-labeled address would otherwise get
  492. // that same address silently written into the awg peer config
  493. // too. Clearing it here makes UpdateInboundClient's own
  494. // empty-AllowedIPs carry-forward (see its WireGuard/AmneziaWG
  495. // branch) preserve THIS inbound's existing, correct value
  496. // instead.
  497. clientForInbound.AllowedIPs = nil
  498. }
  499. settingsPayload, mErr := json.Marshal(map[string][]model.Client{"clients": {clientWithInboundFlow(clientForInbound, inbound)}})
  500. if mErr != nil {
  501. return needRestart, mErr
  502. }
  503. nr, upErr := s.UpdateInboundClient(inboundSvc, &model.Inbound{
  504. Id: ibId,
  505. Settings: string(settingsPayload),
  506. }, existing.Email)
  507. if upErr != nil {
  508. return needRestart, upErr
  509. }
  510. if nr {
  511. needRestart = true
  512. }
  513. }
  514. // UpdateInboundClient renames the record atomically with each inbound's
  515. // settings JSON; this direct write only covers records with no inbound left.
  516. if updated.Email != existing.Email {
  517. if err := database.GetDB().Model(&model.ClientRecord{}).
  518. Where("id = ? AND email = ?", id, existing.Email).
  519. Update("email", updated.Email).Error; err != nil {
  520. return needRestart, err
  521. }
  522. }
  523. if len(inboundIds) == 0 {
  524. merged := *existing
  525. applyClientRecordMerge(&merged, updated.ToRecord())
  526. if err := database.GetDB().Model(&model.ClientRecord{}).
  527. Where("id = ?", id).
  528. Updates(map[string]any{
  529. "sub_id": merged.SubID,
  530. "uuid": merged.UUID,
  531. "password": merged.Password,
  532. "auth": merged.Auth,
  533. "secret": merged.Secret,
  534. "flow": merged.Flow,
  535. "security": merged.Security,
  536. "wg_private_key": merged.PrivateKey,
  537. "wg_public_key": merged.PublicKey,
  538. "wg_allowed_ips": merged.AllowedIPs,
  539. "wg_pre_shared_key": merged.PreSharedKey,
  540. "wg_keep_alive": merged.KeepAlive,
  541. "limit_ip": merged.LimitIP,
  542. "total_gb": merged.TotalGB,
  543. "expiry_time": merged.ExpiryTime,
  544. "tg_id": merged.TgID,
  545. "comment": merged.Comment,
  546. "reset": merged.Reset,
  547. "reset_day": merged.ResetDay,
  548. "reset_max": merged.ResetMax,
  549. "traffic_reset": merged.TrafficReset,
  550. "traffic_reset_day": merged.TrafficResetDay,
  551. }).Error; err != nil {
  552. return needRestart, err
  553. }
  554. }
  555. reverseStr := ""
  556. if updated.Reverse != nil && strings.TrimSpace(updated.Reverse.Tag) != "" {
  557. if b, mErr := json.Marshal(updated.Reverse); mErr == nil {
  558. reverseStr = string(b)
  559. }
  560. }
  561. if err := database.GetDB().Model(&model.ClientRecord{}).
  562. Where("id = ?", id).
  563. Update("reverse", reverseStr).Error; err != nil {
  564. return needRestart, err
  565. }
  566. // Persist the group explicitly. SyncInbound deliberately preserves the
  567. // stored group when the inbound settings carry none — so a node snapshot or a
  568. // group-less settings rebuild can't wipe it (see SyncInbound + its tests).
  569. // That guard also meant clearing the group in the client editor never took
  570. // effect. The editor always round-trips the field, so apply it here,
  571. // including the empty string that removes the client from its group.
  572. if err := database.GetDB().Model(&model.ClientRecord{}).
  573. Where("id = ?", id).
  574. UpdateColumn("group_name", updated.Group).Error; err != nil {
  575. return needRestart, err
  576. }
  577. // Same shape as the group write above: SyncInbound keeps a stored ad-tag
  578. // when the incoming settings carry none, so clearing the override must be
  579. // applied here, where the editor always round-trips the field.
  580. if err := database.GetDB().Model(&model.ClientRecord{}).
  581. Where("id = ?", id).
  582. UpdateColumn("ad_tag", updated.AdTag).Error; err != nil {
  583. return needRestart, err
  584. }
  585. if err := database.GetDB().Model(&model.ClientRecord{}).
  586. Where("id = ?", id).
  587. UpdateColumn("enable", updated.Enable).Error; err != nil {
  588. return needRestart, err
  589. }
  590. if err := s.setClientLimitHwidByEmail(nil, updated.Email, limitHwid); err != nil {
  591. return needRestart, err
  592. }
  593. if err := database.GetDB().Model(&model.ClientRecord{}).
  594. Where("id = ?", id).
  595. UpdateColumn("updated_at", time.Now().UnixMilli()).Error; err != nil {
  596. return needRestart, err
  597. }
  598. return needRestart, nil
  599. }
  600. func (s *ClientService) Delete(inboundSvc *InboundService, id int, keepTraffic bool) (bool, error) {
  601. existing, err := s.GetByID(id)
  602. if err != nil {
  603. return false, err
  604. }
  605. tombstoneClientEmail(existing.Email)
  606. inboundIds, err := s.GetInboundIdsForRecord(id)
  607. if err != nil {
  608. withdrawClientTombstones(existing.Email)
  609. return false, err
  610. }
  611. needRestart := false
  612. var delErrs []error
  613. for _, ibId := range inboundIds {
  614. if _, getErr := inboundSvc.GetInbound(ibId); getErr != nil {
  615. if errors.Is(getErr, gorm.ErrRecordNotFound) {
  616. continue
  617. }
  618. delErrs = append(delErrs, fmt.Errorf("inbound %d: %w", ibId, getErr))
  619. continue
  620. }
  621. // Always delete by email — the client's stable identity. This removes
  622. // every matching entry from the inbound's settings even when the stored
  623. // credential (UUID/password/auth) drifted from the inbound JSON, or a
  624. // duplicate entry with the same email exists.
  625. if existing.Email == "" {
  626. continue
  627. }
  628. nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, keepTraffic, true)
  629. if delErr != nil {
  630. // The client is already absent from this inbound (data drift or a
  631. // retried delete). Skip it — deletion stays idempotent.
  632. if errors.Is(delErr, ErrClientNotInInbound) {
  633. continue
  634. }
  635. delErrs = append(delErrs, fmt.Errorf("inbound %d: %w", ibId, delErr))
  636. continue
  637. }
  638. if nr {
  639. needRestart = true
  640. }
  641. }
  642. // A failed inbound still holds the client in its settings JSON: keep the
  643. // record so the next delete retries exactly the leftovers, and report it.
  644. // The tombstone lifts with it, or the next node merge finishes the deletion.
  645. if len(delErrs) > 0 {
  646. withdrawClientTombstones(existing.Email)
  647. return needRestart, errors.Join(delErrs...)
  648. }
  649. db := database.GetDB()
  650. if err := db.Transaction(func(tx *gorm.DB) error {
  651. if existing.Email != "" {
  652. if err := adjustGroupBaselinesForRemovedTraffic(tx, []string{existing.Email}); err != nil {
  653. return err
  654. }
  655. }
  656. if err := tx.Where("client_id = ?", id).Delete(&model.ClientInbound{}).Error; err != nil {
  657. return err
  658. }
  659. if err := tx.Where("client_id = ?", id).Delete(&model.ClientExternalLink{}).Error; err != nil {
  660. return err
  661. }
  662. if err := clearClientHwidsBySubIDTx(tx, existing.SubID); err != nil {
  663. return err
  664. }
  665. if !keepTraffic && existing.Email != "" {
  666. if err := tx.Where("email = ?", existing.Email).Delete(&xray.ClientTraffic{}).Error; err != nil {
  667. return err
  668. }
  669. if err := clearGlobalTraffic(tx, existing.Email); err != nil {
  670. return err
  671. }
  672. if err := tx.Where("client_email = ?", existing.Email).Delete(&model.InboundClientIps{}).Error; err != nil {
  673. return err
  674. }
  675. if err := tx.Where("email = ?", existing.Email).Delete(&model.NodeClientTraffic{}).Error; err != nil {
  676. return err
  677. }
  678. }
  679. return tx.Delete(&model.ClientRecord{}, id).Error
  680. }); err != nil {
  681. withdrawClientTombstones(existing.Email)
  682. return needRestart, err
  683. }
  684. return needRestart, nil
  685. }
  686. // hasTunnelAttachment reports whether any of inboundIds is a currently
  687. // existing WireGuard or AmneziaWG inbound. Inbounds that fail to load are
  688. // skipped rather than treated as an error -- Attach's own loop already
  689. // surfaces a real error for any inbound it can't load when it gets there.
  690. func (s *ClientService) hasTunnelAttachment(inboundSvc *InboundService, inboundIds []int) bool {
  691. for _, ibId := range inboundIds {
  692. inbound, err := inboundSvc.GetInbound(ibId)
  693. if err != nil {
  694. continue
  695. }
  696. if inbound.Protocol == model.WireGuard || inbound.Protocol == model.AmneziaWG {
  697. return true
  698. }
  699. }
  700. return false
  701. }
  702. // addressesFitAmneziaWGInbound reports whether every entry in addrs falls
  703. // inside ib's own configured subnet(s). AmneziaWG only: its kernel interface
  704. // Address is exactly that subnet, so an address inherited from elsewhere (an
  705. // identity attached to a WireGuard inbound first, say) produces a peer that
  706. // can never connect -- Attach allocates fresh instead.
  707. func addressesFitAmneziaWGInbound(addrs []string, ib *model.Inbound) bool {
  708. if ib.Protocol != model.AmneziaWG || len(addrs) == 0 {
  709. return true
  710. }
  711. v4Base, v6Base, err := defaultAmneziaWGSubnetBases(ib.Settings)
  712. if err != nil {
  713. return false
  714. }
  715. bases := make([]netip.Prefix, 0, 2)
  716. for _, base := range []string{v4Base, v6Base} {
  717. if base == "" {
  718. continue
  719. }
  720. prefix, pErr := netip.ParsePrefix(base)
  721. if pErr != nil {
  722. return false
  723. }
  724. bases = append(bases, prefix)
  725. }
  726. for _, a := range addrs {
  727. host := wireguardHostAddr(a)
  728. if !host.IsValid() {
  729. return false
  730. }
  731. fits := false
  732. for _, prefix := range bases {
  733. if prefix.Contains(host) {
  734. fits = true
  735. break
  736. }
  737. }
  738. if !fits {
  739. return false
  740. }
  741. }
  742. return true
  743. }
  744. func (s *ClientService) Attach(inboundSvc *InboundService, id int, inboundIds []int) (bool, error) {
  745. existing, err := s.GetByID(id)
  746. if err != nil {
  747. return false, err
  748. }
  749. currentIds, err := s.GetInboundIdsForRecord(id)
  750. if err != nil {
  751. return false, err
  752. }
  753. have := make(map[int]struct{}, len(currentIds))
  754. for _, x := range currentIds {
  755. have[x] = struct{}{}
  756. }
  757. clientWire := existing.ToClient()
  758. flow, ffErr := s.EffectiveFlow(nil, id)
  759. if ffErr != nil {
  760. return false, ffErr
  761. }
  762. clientWire.Flow = flow
  763. clientWire.UpdatedAt = time.Now().UnixMilli()
  764. // If this identity has no CURRENT WireGuard/AmneziaWG attachment,
  765. // clientWire.AllowedIPs (from the ClientRecord) is a leftover from
  766. // whenever it last had one -- nothing reserves it anymore. Clear it so
  767. // attaching to a tunnel inbound now allocates a fresh address instead
  768. // of resurrecting the old one, which may no longer even be the lowest
  769. // free slot. Left untouched when the identity already has an active
  770. // tunnel elsewhere, so extending it to a second protocol still keeps
  771. // the same address on both.
  772. if !s.hasTunnelAttachment(inboundSvc, currentIds) {
  773. clientWire.AllowedIPs = nil
  774. }
  775. needRestart := false
  776. for _, ibId := range inboundIds {
  777. if _, attached := have[ibId]; attached {
  778. continue
  779. }
  780. inbound, getErr := inboundSvc.GetInbound(ibId)
  781. if getErr != nil {
  782. return needRestart, getErr
  783. }
  784. copyClient := *clientWire
  785. if !addressesFitAmneziaWGInbound(copyClient.AllowedIPs, inbound) {
  786. copyClient.AllowedIPs = nil
  787. }
  788. if err := s.fillProtocolDefaults(&copyClient, inbound); err != nil {
  789. return needRestart, err
  790. }
  791. settingsPayload, mErr := json.Marshal(map[string][]model.Client{"clients": {clientWithInboundFlow(copyClient, inbound)}})
  792. if mErr != nil {
  793. return needRestart, mErr
  794. }
  795. nr, addErr := s.AddInboundClient(inboundSvc, &model.Inbound{
  796. Id: ibId,
  797. Settings: string(settingsPayload),
  798. })
  799. if addErr != nil {
  800. return needRestart, addErr
  801. }
  802. if nr {
  803. needRestart = true
  804. }
  805. }
  806. return needRestart, nil
  807. }
  808. func (s *ClientService) CreateOne(inboundSvc *InboundService, inboundId int, client model.Client) (bool, error) {
  809. return s.Create(inboundSvc, &ClientCreatePayload{
  810. Client: client,
  811. InboundIds: []int{inboundId},
  812. })
  813. }
  814. func (s *ClientService) DetachByEmail(inboundSvc *InboundService, inboundId int, email string) (bool, error) {
  815. if email == "" {
  816. return false, common.NewError("client email is required")
  817. }
  818. rec, err := s.GetRecordByEmail(nil, email)
  819. if err != nil {
  820. return false, err
  821. }
  822. return s.Detach(inboundSvc, rec.Id, []int{inboundId})
  823. }
  824. func (s *ClientService) AttachByEmail(inboundSvc *InboundService, email string, inboundIds []int) (bool, error) {
  825. if email == "" {
  826. return false, common.NewError("client email is required")
  827. }
  828. rec, err := s.GetRecordByEmail(nil, email)
  829. if err != nil {
  830. return false, err
  831. }
  832. return s.Attach(inboundSvc, rec.Id, inboundIds)
  833. }
  834. func (s *ClientService) DetachByEmailMany(inboundSvc *InboundService, email string, inboundIds []int) (bool, error) {
  835. if email == "" {
  836. return false, common.NewError("client email is required")
  837. }
  838. rec, err := s.GetRecordByEmail(nil, email)
  839. if err != nil {
  840. return false, err
  841. }
  842. return s.Detach(inboundSvc, rec.Id, inboundIds)
  843. }
  844. func (s *ClientService) DeleteByEmail(inboundSvc *InboundService, email string, keepTraffic bool) (bool, error) {
  845. if email == "" {
  846. return false, common.NewError("client email is required")
  847. }
  848. rec, err := s.GetRecordByEmail(nil, email)
  849. if err == nil {
  850. return s.Delete(inboundSvc, rec.Id, keepTraffic)
  851. }
  852. if !errors.Is(err, gorm.ErrRecordNotFound) {
  853. return false, err
  854. }
  855. inboundIds, idsErr := s.findInboundIdsByClientEmail(email)
  856. if idsErr != nil {
  857. return false, idsErr
  858. }
  859. if len(inboundIds) == 0 {
  860. return false, common.NewError(fmt.Sprintf("client %q not found in any inbound or client record", email))
  861. }
  862. needRestart := false
  863. var delErrs []error
  864. for _, ibId := range inboundIds {
  865. nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, email, keepTraffic, true)
  866. if delErr != nil {
  867. if errors.Is(delErr, ErrClientNotInInbound) {
  868. continue
  869. }
  870. delErrs = append(delErrs, fmt.Errorf("inbound %d: %w", ibId, delErr))
  871. continue
  872. }
  873. if nr {
  874. needRestart = true
  875. }
  876. }
  877. if len(delErrs) > 0 {
  878. return needRestart, errors.Join(delErrs...)
  879. }
  880. if !keepTraffic {
  881. db := database.GetDB()
  882. if err := db.Where("email = ?", email).Delete(&xray.ClientTraffic{}).Error; err != nil {
  883. return needRestart, err
  884. }
  885. if err := clearGlobalTraffic(db, email); err != nil {
  886. return needRestart, err
  887. }
  888. if err := db.Where("client_email = ?", email).Delete(&model.InboundClientIps{}).Error; err != nil {
  889. return needRestart, err
  890. }
  891. if err := db.Where("email = ?", email).Delete(&model.NodeClientTraffic{}).Error; err != nil {
  892. return needRestart, err
  893. }
  894. }
  895. return needRestart, nil
  896. }
  897. func (s *ClientService) UpdateByEmail(inboundSvc *InboundService, email string, updated model.Client, limitHwid int, inboundFilter ...int) (bool, error) {
  898. if email == "" {
  899. return false, common.NewError("client email is required")
  900. }
  901. rec, err := s.GetRecordByEmail(nil, email)
  902. if err != nil {
  903. return false, err
  904. }
  905. return s.Update(inboundSvc, rec.Id, updated, limitHwid, inboundFilter...)
  906. }
  907. func (s *ClientService) Detach(inboundSvc *InboundService, id int, inboundIds []int) (bool, error) {
  908. existing, err := s.GetByID(id)
  909. if err != nil {
  910. return false, err
  911. }
  912. currentIds, err := s.GetInboundIdsForRecord(id)
  913. if err != nil {
  914. return false, err
  915. }
  916. have := make(map[int]struct{}, len(currentIds))
  917. for _, x := range currentIds {
  918. have[x] = struct{}{}
  919. }
  920. needRestart := false
  921. for _, ibId := range inboundIds {
  922. if _, attached := have[ibId]; !attached {
  923. continue
  924. }
  925. if _, getErr := inboundSvc.GetInbound(ibId); getErr != nil {
  926. return needRestart, getErr
  927. }
  928. // Detach by email — the client's stable identity (see Delete).
  929. if existing.Email == "" {
  930. continue
  931. }
  932. nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, true, false)
  933. if delErr != nil {
  934. if errors.Is(delErr, ErrClientNotInInbound) {
  935. continue
  936. }
  937. return needRestart, delErr
  938. }
  939. if nr {
  940. needRestart = true
  941. }
  942. }
  943. return needRestart, nil
  944. }