Application device interface encryption and other bug fixes (#87)
* add: device login * update: global config * add: User transmission interface encryption * update: get global config * update: User transmission interface encryption * add: get device list * add: SecretIsEmpty Message * update: device middleware * add: query user subscribe node list * fix bug: query device list * fix bug: unbind device * update: device login * fix bug: The ad table is missing the description field * fix bug:page size is zero * update: Device Middleware * fix bug: Site custom data update failed
This commit is contained in:
parent
adbe9a06d8
commit
96808d531a
@ -11,10 +11,12 @@ info (
|
|||||||
type (
|
type (
|
||||||
// User login request
|
// User login request
|
||||||
UserLoginRequest {
|
UserLoginRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// Check user is exist request
|
// Check user is exist request
|
||||||
@ -27,21 +29,25 @@ type (
|
|||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
UserRegisterRequest {
|
UserRegisterRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Invite string `json:"invite,optional"`
|
Invite string `json:"invite,optional"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
ResetPasswordRequest {
|
ResetPasswordRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
LoginResponse {
|
LoginResponse {
|
||||||
@ -60,12 +66,14 @@ type (
|
|||||||
}
|
}
|
||||||
// login request
|
// login request
|
||||||
TelephoneLoginRequest {
|
TelephoneLoginRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneCode string `json:"telephone_code"`
|
TelephoneCode string `json:"telephone_code"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// Check user is exist request
|
// Check user is exist request
|
||||||
@ -79,6 +87,7 @@ type (
|
|||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
TelephoneRegisterRequest {
|
TelephoneRegisterRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
@ -86,16 +95,19 @@ type (
|
|||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type,optional"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
TelephoneResetPasswordRequest {
|
TelephoneResetPasswordRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type,optional"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
AppleLoginCallbackRequest {
|
AppleLoginCallbackRequest {
|
||||||
@ -107,11 +119,18 @@ type (
|
|||||||
Code string `form:"code"`
|
Code string `form:"code"`
|
||||||
State string `form:"state"`
|
State string `form:"state"`
|
||||||
}
|
}
|
||||||
|
DeviceLoginRequest {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
|
UserAgent string `json:"user_agent" validate:"required"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/auth
|
prefix: v1/auth
|
||||||
group: auth
|
group: auth
|
||||||
|
middleware: DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "User login"
|
@doc "User login"
|
||||||
@ -145,6 +164,10 @@ service ppanel {
|
|||||||
@doc "Reset password"
|
@doc "Reset password"
|
||||||
@handler TelephoneResetPassword
|
@handler TelephoneResetPassword
|
||||||
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
|
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
|
||||||
|
|
||||||
|
@doc "Device Login"
|
||||||
|
@handler DeviceLogin
|
||||||
|
post /login/device (DeviceLoginRequest) returns (LoginResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
|
|||||||
@ -92,6 +92,7 @@ type (
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/common
|
prefix: v1/common
|
||||||
group: common
|
group: common
|
||||||
|
middleware: DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get global config"
|
@doc "Get global config"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/announcement
|
prefix: v1/public/announcement
|
||||||
group: public/announcement
|
group: public/announcement
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Query announcement"
|
@doc "Query announcement"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/document
|
prefix: v1/public/document
|
||||||
group: public/document
|
group: public/document
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get document list"
|
@doc "Get document list"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/order
|
prefix: v1/public/order
|
||||||
group: public/order
|
group: public/order
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Pre create order"
|
@doc "Pre create order"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/payment
|
prefix: v1/public/payment
|
||||||
group: public/payment
|
group: public/payment
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get available payment methods"
|
@doc "Get available payment methods"
|
||||||
|
|||||||
@ -70,6 +70,7 @@ type (
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/portal
|
prefix: v1/public/portal
|
||||||
group: public/portal
|
group: public/portal
|
||||||
|
middleware: DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get available payment methods"
|
@doc "Get available payment methods"
|
||||||
|
|||||||
@ -14,16 +14,57 @@ type (
|
|||||||
QuerySubscribeListRequest {
|
QuerySubscribeListRequest {
|
||||||
Language string `form:"language"`
|
Language string `form:"language"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryUserSubscribeNodeListResponse {
|
||||||
|
List []UserSubscribeInfo `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSubscribeInfo {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
OrderId int64 `json:"order_id"`
|
||||||
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
|
StartTime int64 `json:"start_time"`
|
||||||
|
ExpireTime int64 `json:"expire_time"`
|
||||||
|
FinishedAt int64 `json:"finished_at"`
|
||||||
|
ResetTime int64 `json:"reset_time"`
|
||||||
|
Traffic int64 `json:"traffic"`
|
||||||
|
Download int64 `json:"download"`
|
||||||
|
Upload int64 `json:"upload"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Status uint8 `json:"status"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
IsTryOut bool `json:"is_try_out"`
|
||||||
|
Nodes []*UserSubscribeNodeInfo `json:"nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSubscribeNodeInfo{
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
City string `json:"city"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/public/subscribe
|
prefix: v1/public/subscribe
|
||||||
group: public/subscribe
|
group: public/subscribe
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get subscribe list"
|
@doc "Get subscribe list"
|
||||||
@handler QuerySubscribeList
|
@handler QuerySubscribeList
|
||||||
get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse)
|
get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse)
|
||||||
|
|
||||||
|
@doc "Get user subscribe node info"
|
||||||
|
@handler QueryUserSubscribeNodeList
|
||||||
|
get /node/list returns (QueryUserSubscribeNodeListResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ type (
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/ticket
|
prefix: v1/public/ticket
|
||||||
group: public/ticket
|
group: public/ticket
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get ticket list"
|
@doc "Get ticket list"
|
||||||
|
|||||||
@ -97,12 +97,21 @@ type (
|
|||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetDeviceListResponse {
|
||||||
|
List []UserDevice `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UnbindDeviceRequest {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/public/user
|
prefix: v1/public/user
|
||||||
group: public/user
|
group: public/user
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Query User Info"
|
@doc "Query User Info"
|
||||||
@ -192,5 +201,13 @@ service ppanel {
|
|||||||
@doc "Update Bind Email"
|
@doc "Update Bind Email"
|
||||||
@handler UpdateBindEmail
|
@handler UpdateBindEmail
|
||||||
put /bind_email (UpdateBindEmailRequest)
|
put /bind_email (UpdateBindEmailRequest)
|
||||||
|
|
||||||
|
@doc "Get Device List"
|
||||||
|
@handler GetDeviceList
|
||||||
|
get /devices returns (GetDeviceListResponse)
|
||||||
|
|
||||||
|
@doc "Unbind Device"
|
||||||
|
@handler UnbindDevice
|
||||||
|
put /unbind_device (UnbindDeviceRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,7 @@ type (
|
|||||||
AuthConfig {
|
AuthConfig {
|
||||||
Mobile MobileAuthenticateConfig `json:"mobile"`
|
Mobile MobileAuthenticateConfig `json:"mobile"`
|
||||||
Email EmailAuthticateConfig `json:"email"`
|
Email EmailAuthticateConfig `json:"email"`
|
||||||
|
Device DeviceAuthticateConfig `json:"device"`
|
||||||
Register PubilcRegisterConfig `json:"register"`
|
Register PubilcRegisterConfig `json:"register"`
|
||||||
}
|
}
|
||||||
PubilcRegisterConfig {
|
PubilcRegisterConfig {
|
||||||
@ -134,6 +135,14 @@ type (
|
|||||||
EnableDomainSuffix bool `json:"enable_domain_suffix"`
|
EnableDomainSuffix bool `json:"enable_domain_suffix"`
|
||||||
DomainSuffixList string `json:"domain_suffix_list"`
|
DomainSuffixList string `json:"domain_suffix_list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeviceAuthticateConfig {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
ShowAds bool `json:"show_ads"`
|
||||||
|
EnableSecurity bool `json:"enable_security"`
|
||||||
|
OnlyRealDevice bool `json:"only_real_device"`
|
||||||
|
}
|
||||||
|
|
||||||
RegisterConfig {
|
RegisterConfig {
|
||||||
StopRegister bool `json:"stop_register"`
|
StopRegister bool `json:"stop_register"`
|
||||||
EnableTrial bool `json:"enable_trial"`
|
EnableTrial bool `json:"enable_trial"`
|
||||||
@ -647,7 +656,7 @@ type (
|
|||||||
// public announcement
|
// public announcement
|
||||||
QueryAnnouncementRequest {
|
QueryAnnouncementRequest {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size,default=15"`
|
||||||
Pinned *bool `form:"pinned"`
|
Pinned *bool `form:"pinned"`
|
||||||
Popup *bool `form:"popup"`
|
Popup *bool `form:"popup"`
|
||||||
}
|
}
|
||||||
@ -664,6 +673,7 @@ type (
|
|||||||
List []SubscribeGroup `json:"list"`
|
List []SubscribeGroup `json:"list"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
GetUserSubscribeTrafficLogsRequest {
|
GetUserSubscribeTrafficLogsRequest {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size"`
|
||||||
|
|||||||
26
initialize/device.go
Normal file
26
initialize/device.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/config"
|
||||||
|
"github.com/perfect-panel/server/internal/model/auth"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Device(ctx *svc.ServiceContext) {
|
||||||
|
logger.Debug("device config initialization")
|
||||||
|
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "device")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var cfg config.DeviceConfig
|
||||||
|
var deviceConfig auth.DeviceConfig
|
||||||
|
deviceConfig.Unmarshal(method.Config)
|
||||||
|
tool.DeepCopy(&cfg, deviceConfig)
|
||||||
|
cfg.Enable = *method.Enabled
|
||||||
|
ctx.Config.Device = cfg
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ func StartInitSystemConfig(svc *svc.ServiceContext) {
|
|||||||
Site(svc)
|
Site(svc)
|
||||||
Node(svc)
|
Node(svc)
|
||||||
Email(svc)
|
Email(svc)
|
||||||
|
Device(svc)
|
||||||
Invite(svc)
|
Invite(svc)
|
||||||
Verify(svc)
|
Verify(svc)
|
||||||
Subscribe(svc)
|
Subscribe(svc)
|
||||||
|
|||||||
0
initialize/migrate/database/02115_ads.down.sql
Normal file
0
initialize/migrate/database/02115_ads.down.sql
Normal file
2
initialize/migrate/database/02115_ads.up.sql
Normal file
2
initialize/migrate/database/02115_ads.up.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `ads`
|
||||||
|
ADD COLUMN `description` VARCHAR(255) DEFAULT '' COMMENT 'Description';
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
INSERT INTO `ppanel`.`system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
|
||||||
|
SELECT 'site', 'CustomData', '{
|
||||||
|
"kr_website_id": ""
|
||||||
|
}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `ppanel`.`system` WHERE `category` = 'site' AND `key` = 'CustomData'
|
||||||
|
);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
INSERT INTO `ppanel`.`system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
|
||||||
|
SELECT 'site', 'CustomData', '{
|
||||||
|
"kr_website_id": ""
|
||||||
|
}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `ppanel`.`system` WHERE `category` = 'site' AND `key` = 'CustomData'
|
||||||
|
);
|
||||||
@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/orm"
|
"github.com/perfect-panel/server/pkg/orm"
|
||||||
)
|
)
|
||||||
@ -20,6 +21,7 @@ type Config struct {
|
|||||||
Node NodeConfig `yaml:"Node"`
|
Node NodeConfig `yaml:"Node"`
|
||||||
Mobile MobileConfig `yaml:"Mobile"`
|
Mobile MobileConfig `yaml:"Mobile"`
|
||||||
Email EmailConfig `yaml:"Email"`
|
Email EmailConfig `yaml:"Email"`
|
||||||
|
Device DeviceConfig `yaml:"device"`
|
||||||
Verify Verify `yaml:"Verify"`
|
Verify Verify `yaml:"Verify"`
|
||||||
VerifyCode VerifyCode `yaml:"VerifyCode"`
|
VerifyCode VerifyCode `yaml:"VerifyCode"`
|
||||||
Register RegisterConfig `yaml:"Register"`
|
Register RegisterConfig `yaml:"Register"`
|
||||||
@ -95,6 +97,14 @@ type MobileConfig struct {
|
|||||||
Whitelist []string `yaml:"whitelist"`
|
Whitelist []string `yaml:"whitelist"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceConfig struct {
|
||||||
|
Enable bool `yaml:"enable" default:"true"`
|
||||||
|
ShowAds bool `yaml:"show_ads"`
|
||||||
|
EnableSecurity bool `yaml:"enable_security"`
|
||||||
|
OnlyRealDevice bool `yaml:"only_real_device"`
|
||||||
|
SecuritySecret string `yaml:"security_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
type SiteConfig struct {
|
type SiteConfig struct {
|
||||||
Host string `yaml:"Host" default:""`
|
Host string `yaml:"Host" default:""`
|
||||||
SiteName string `yaml:"SiteName" default:""`
|
SiteName string `yaml:"SiteName" default:""`
|
||||||
|
|||||||
26
internal/handler/auth/deviceLoginHandler.go
Normal file
26
internal/handler/auth/deviceLoginHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/auth"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
func DeviceLoginHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.DeviceLoginRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewDeviceLoginLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.DeviceLogin(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package subscribe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/subscribe"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get user subscribe node info
|
||||||
|
func QueryUserSubscribeNodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := subscribe.NewQueryUserSubscribeNodeListLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.QueryUserSubscribeNodeList()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
internal/handler/public/user/getDeviceListHandler.go
Normal file
18
internal/handler/public/user/getDeviceListHandler.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get Device List
|
||||||
|
func GetDeviceListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := user.NewGetDeviceListLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.GetDeviceList()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/public/user/unbindDeviceHandler.go
Normal file
26
internal/handler/public/user/unbindDeviceHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unbind Device
|
||||||
|
func UnbindDeviceHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.UnbindDeviceRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewUnbindDeviceLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.UnbindDevice(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -578,6 +578,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
authGroupRouter := router.Group("/v1/auth")
|
authGroupRouter := router.Group("/v1/auth")
|
||||||
|
authGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check user is exist
|
// Check user is exist
|
||||||
@ -589,6 +590,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// User login
|
// User login
|
||||||
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
|
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
authGroupRouter.POST("/login/device", auth.DeviceLoginHandler(serverCtx))
|
||||||
|
|
||||||
// User Telephone login
|
// User Telephone login
|
||||||
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
|
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
|
||||||
|
|
||||||
@ -619,6 +623,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commonGroupRouter := router.Group("/v1/common")
|
commonGroupRouter := router.Group("/v1/common")
|
||||||
|
commonGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get Ads
|
// Get Ads
|
||||||
@ -650,7 +655,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicAnnouncementGroupRouter := router.Group("/v1/public/announcement")
|
publicAnnouncementGroupRouter := router.Group("/v1/public/announcement")
|
||||||
publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Query announcement
|
// Query announcement
|
||||||
@ -658,7 +663,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicDocumentGroupRouter := router.Group("/v1/public/document")
|
publicDocumentGroupRouter := router.Group("/v1/public/document")
|
||||||
publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get document detail
|
// Get document detail
|
||||||
@ -669,7 +674,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicOrderGroupRouter := router.Group("/v1/public/order")
|
publicOrderGroupRouter := router.Group("/v1/public/order")
|
||||||
publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Close order
|
// Close order
|
||||||
@ -698,7 +703,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicPaymentGroupRouter := router.Group("/v1/public/payment")
|
publicPaymentGroupRouter := router.Group("/v1/public/payment")
|
||||||
publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get available payment methods
|
// Get available payment methods
|
||||||
@ -706,6 +711,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicPortalGroupRouter := router.Group("/v1/public/portal")
|
publicPortalGroupRouter := router.Group("/v1/public/portal")
|
||||||
|
publicPortalGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Purchase Checkout
|
// Purchase Checkout
|
||||||
@ -728,15 +734,18 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
||||||
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get subscribe list
|
// Get subscribe list
|
||||||
publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx))
|
publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get user subscribe node info
|
||||||
|
publicSubscribeGroupRouter.GET("/node/list", publicSubscribe.QueryUserSubscribeNodeListHandler(serverCtx))
|
||||||
}
|
}
|
||||||
|
|
||||||
publicTicketGroupRouter := router.Group("/v1/public/ticket")
|
publicTicketGroupRouter := router.Group("/v1/public/ticket")
|
||||||
publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Update ticket status
|
// Update ticket status
|
||||||
@ -756,7 +765,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicUserGroupRouter := router.Group("/v1/public/user")
|
publicUserGroupRouter := router.Group("/v1/public/user")
|
||||||
publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Query User Affiliate Count
|
// Query User Affiliate Count
|
||||||
@ -786,6 +795,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Query User Commission Log
|
// Query User Commission Log
|
||||||
publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx))
|
publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get Device List
|
||||||
|
publicUserGroupRouter.GET("/devices", publicUser.GetDeviceListHandler(serverCtx))
|
||||||
|
|
||||||
// Query User Info
|
// Query User Info
|
||||||
publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx))
|
publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx))
|
||||||
|
|
||||||
@ -810,6 +822,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Reset User Subscribe Token
|
// Reset User Subscribe Token
|
||||||
publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx))
|
publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx))
|
||||||
|
|
||||||
|
// Unbind Device
|
||||||
|
publicUserGroupRouter.PUT("/unbind_device", publicUser.UnbindDeviceHandler(serverCtx))
|
||||||
|
|
||||||
// Unbind OAuth
|
// Unbind OAuth
|
||||||
publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx))
|
publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx))
|
||||||
|
|
||||||
|
|||||||
@ -92,6 +92,9 @@ func (l *UpdateAuthMethodConfigLogic) UpdateGlobal(method string) {
|
|||||||
if method == "mobile" {
|
if method == "mobile" {
|
||||||
initialize.Mobile(l.svcCtx)
|
initialize.Mobile(l.svcCtx)
|
||||||
}
|
}
|
||||||
|
if method == "device" {
|
||||||
|
initialize.Device(l.svcCtx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) {
|
func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) {
|
||||||
|
|||||||
234
internal/logic/auth/bindDeviceLogic.go
Normal file
234
internal/logic/auth/bindDeviceLogic.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindDeviceLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindDeviceLogic {
|
||||||
|
return &BindDeviceLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindDeviceToUser binds a device to a user
|
||||||
|
// If the device is already bound to another user, it will disable that user and bind the device to the current user
|
||||||
|
func (l *BindDeviceLogic) BindDeviceToUser(identifier, ip, userAgent string, currentUserId int64) error {
|
||||||
|
if identifier == "" {
|
||||||
|
// No device identifier provided, skip binding
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("binding device to user",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", currentUserId),
|
||||||
|
logger.Field("ip", ip),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if device exists
|
||||||
|
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// Device not found, create new device record
|
||||||
|
return l.createDeviceForUser(identifier, ip, userAgent, currentUserId)
|
||||||
|
}
|
||||||
|
l.Errorw("failed to query device",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device exists, check if it's bound to current user
|
||||||
|
if deviceInfo.UserId == currentUserId {
|
||||||
|
// Already bound to current user, just update IP and UserAgent
|
||||||
|
l.Infow("device already bound to current user, updating info",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", currentUserId),
|
||||||
|
)
|
||||||
|
deviceInfo.Ip = ip
|
||||||
|
deviceInfo.UserAgent = userAgent
|
||||||
|
if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil {
|
||||||
|
l.Errorw("failed to update device",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device is bound to another user, need to disable old user and rebind
|
||||||
|
l.Infow("device bound to another user, rebinding",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("old_user_id", deviceInfo.UserId),
|
||||||
|
logger.Field("new_user_id", currentUserId),
|
||||||
|
)
|
||||||
|
|
||||||
|
return l.rebindDeviceToNewUser(deviceInfo, ip, userAgent, currentUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, userId int64) error {
|
||||||
|
l.Infow("creating new device for user",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||||
|
// Create device auth method
|
||||||
|
authMethod := &user.AuthMethods{
|
||||||
|
UserId: userId,
|
||||||
|
AuthType: "device",
|
||||||
|
AuthIdentifier: identifier,
|
||||||
|
Verified: true,
|
||||||
|
}
|
||||||
|
if err := db.Create(authMethod).Error; err != nil {
|
||||||
|
l.Errorw("failed to create device auth method",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create device record
|
||||||
|
deviceInfo := &user.Device{
|
||||||
|
Ip: ip,
|
||||||
|
UserId: userId,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
Identifier: identifier,
|
||||||
|
Enabled: true,
|
||||||
|
Online: false,
|
||||||
|
}
|
||||||
|
if err := db.Create(deviceInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to create device",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("device creation failed",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("device created successfully",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, userAgent string, newUserId int64) error {
|
||||||
|
oldUserId := deviceInfo.UserId
|
||||||
|
|
||||||
|
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||||
|
// Check if old user has other auth methods besides device
|
||||||
|
var authMethods []user.AuthMethods
|
||||||
|
if err := db.Where("user_id = ?", oldUserId).Find(&authMethods).Error; err != nil {
|
||||||
|
l.Errorw("failed to query auth methods for old user",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query auth methods failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count non-device auth methods
|
||||||
|
nonDeviceAuthCount := 0
|
||||||
|
for _, auth := range authMethods {
|
||||||
|
if auth.AuthType != "device" {
|
||||||
|
nonDeviceAuthCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only disable old user if they have no other auth methods
|
||||||
|
if nonDeviceAuthCount == 0 {
|
||||||
|
falseVal := false
|
||||||
|
if err := db.Model(&user.User{}).Where("id = ?", oldUserId).Update("enable", &falseVal).Error; err != nil {
|
||||||
|
l.Errorw("failed to disable old user",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable old user failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("disabled old user (no other auth methods)",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
l.Infow("old user has other auth methods, not disabling",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("non_device_auth_count", nonDeviceAuthCount),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update device auth method to new user
|
||||||
|
if err := db.Model(&user.AuthMethods{}).
|
||||||
|
Where("auth_type = ? AND auth_identifier = ?", "device", deviceInfo.Identifier).
|
||||||
|
Update("user_id", newUserId).Error; err != nil {
|
||||||
|
l.Errorw("failed to update device auth method",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device auth method failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update device record
|
||||||
|
deviceInfo.UserId = newUserId
|
||||||
|
deviceInfo.Ip = ip
|
||||||
|
deviceInfo.UserAgent = userAgent
|
||||||
|
deviceInfo.Enabled = true
|
||||||
|
|
||||||
|
if err := db.Save(deviceInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to update device",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("device rebinding failed",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("new_user_id", newUserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("device rebound successfully",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("new_user_id", newUserId),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
293
internal/logic/auth/deviceLoginLogic.go
Normal file
293
internal/logic/auth/deviceLoginLogic.go
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/config"
|
||||||
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/jwt"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
"github.com/perfect-panel/server/pkg/uuidx"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeviceLoginLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
|
||||||
|
return &DeviceLoginLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *types.LoginResponse, err error) {
|
||||||
|
if !l.svcCtx.Config.Device.Enable {
|
||||||
|
return nil, xerr.NewErrMsg("Device login is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
loginStatus := false
|
||||||
|
var userInfo *user.User
|
||||||
|
// Record login status
|
||||||
|
defer func() {
|
||||||
|
if userInfo != nil && userInfo.Id != 0 {
|
||||||
|
loginLog := log.Login{
|
||||||
|
Method: "device",
|
||||||
|
LoginIP: req.IP,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
Success: loginStatus,
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
}
|
||||||
|
content, _ := loginLog.Marshal()
|
||||||
|
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||||
|
Type: log.TypeLogin.Uint8(),
|
||||||
|
Date: time.Now().Format("2006-01-02"),
|
||||||
|
ObjectID: userInfo.Id,
|
||||||
|
Content: string(content),
|
||||||
|
}); err != nil {
|
||||||
|
l.Errorw("failed to insert login log",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("ip", req.IP),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check if device exists by identifier
|
||||||
|
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// Device not found, create new user and device
|
||||||
|
userInfo, err = l.registerUserAndDevice(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l.Errorw("query device failed",
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Device found, get user info
|
||||||
|
userInfo, err = l.svcCtx.UserModel.FindOne(l.ctx, deviceInfo.UserId)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("query user failed",
|
||||||
|
logger.Field("user_id", deviceInfo.UserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate session id
|
||||||
|
sessionId := uuidx.NewUUID().String()
|
||||||
|
|
||||||
|
// Generate token
|
||||||
|
token, err := jwt.NewJwtToken(
|
||||||
|
l.svcCtx.Config.JwtAuth.AccessSecret,
|
||||||
|
time.Now().Unix(),
|
||||||
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", "device"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("token generate error",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store session id in redis
|
||||||
|
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||||
|
if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil {
|
||||||
|
l.Errorw("set session id error",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
loginStatus = true
|
||||||
|
return &types.LoginResponse{
|
||||||
|
Token: token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) (*user.User, error) {
|
||||||
|
l.Infow("device not found, creating new user and device",
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("ip", req.IP),
|
||||||
|
)
|
||||||
|
|
||||||
|
var userInfo *user.User
|
||||||
|
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||||
|
// Create new user
|
||||||
|
userInfo = &user.User{
|
||||||
|
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||||
|
}
|
||||||
|
if err := db.Create(userInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to create user",
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update refer code
|
||||||
|
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
|
||||||
|
if err := db.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
|
||||||
|
l.Errorw("failed to update refer code",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create device auth method
|
||||||
|
authMethod := &user.AuthMethods{
|
||||||
|
UserId: userInfo.Id,
|
||||||
|
AuthType: "device",
|
||||||
|
AuthIdentifier: req.Identifier,
|
||||||
|
Verified: true,
|
||||||
|
}
|
||||||
|
if err := db.Create(authMethod).Error; err != nil {
|
||||||
|
l.Errorw("failed to create device auth method",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert device record
|
||||||
|
deviceInfo := &user.Device{
|
||||||
|
Ip: req.IP,
|
||||||
|
UserId: userInfo.Id,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
Identifier: req.Identifier,
|
||||||
|
Enabled: true,
|
||||||
|
Online: false,
|
||||||
|
}
|
||||||
|
if err := db.Create(deviceInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to insert device",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert device failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate trial if enabled
|
||||||
|
if l.svcCtx.Config.Register.EnableTrial {
|
||||||
|
if err := l.activeTrial(userInfo.Id, db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("device registration failed",
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("device registration completed successfully",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("refer_code", userInfo.ReferCode),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register log
|
||||||
|
registerLog := log.Register{
|
||||||
|
AuthMethod: "device",
|
||||||
|
Identifier: req.Identifier,
|
||||||
|
RegisterIP: req.IP,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
}
|
||||||
|
content, _ := registerLog.Marshal()
|
||||||
|
|
||||||
|
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||||
|
Type: log.TypeRegister.Uint8(),
|
||||||
|
Date: time.Now().Format("2006-01-02"),
|
||||||
|
ObjectID: userInfo.Id,
|
||||||
|
Content: string(content),
|
||||||
|
}); err != nil {
|
||||||
|
l.Errorw("failed to insert register log",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("ip", req.IP),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error {
|
||||||
|
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("failed to find trial subscription template",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime)
|
||||||
|
subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", userId))
|
||||||
|
subscribeUUID := uuidx.NewUUID().String()
|
||||||
|
|
||||||
|
userSub := &user.Subscribe{
|
||||||
|
UserId: userId,
|
||||||
|
OrderId: 0,
|
||||||
|
SubscribeId: sub.Id,
|
||||||
|
StartTime: startTime,
|
||||||
|
ExpireTime: expireTime,
|
||||||
|
Traffic: sub.Traffic,
|
||||||
|
Download: 0,
|
||||||
|
Upload: 0,
|
||||||
|
Token: subscribeToken,
|
||||||
|
UUID: subscribeUUID,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Create(userSub).Error; err != nil {
|
||||||
|
l.Errorw("failed to insert trial subscription",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("trial subscription activated successfully",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("subscribe_id", sub.Id),
|
||||||
|
logger.Field("expire_time", expireTime),
|
||||||
|
logger.Field("traffic", sub.Traffic),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -108,6 +108,22 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
|
|||||||
if err = l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil {
|
if err = l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error())
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind device to user if identifier is provided
|
||||||
|
if req.Identifier != "" {
|
||||||
|
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||||
|
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail register if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.ctx.Value(constant.LoginType) != nil {
|
||||||
|
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||||
|
}
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -117,6 +133,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -124,6 +124,23 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
|||||||
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind device to user if identifier is provided
|
||||||
|
if req.Identifier != "" {
|
||||||
|
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||||
|
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail login if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.ctx.Value(constant.LoginType) != nil {
|
||||||
|
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||||
|
}
|
||||||
|
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -133,6 +150,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -84,6 +84,21 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error())
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind device to user if identifier is provided
|
||||||
|
if req.Identifier != "" {
|
||||||
|
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||||
|
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail register if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.ctx.Value(constant.LoginType) != nil {
|
||||||
|
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||||
|
}
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -93,6 +108,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -139,6 +139,22 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Bind device to user if identifier is provided
|
||||||
|
if req.Identifier != "" {
|
||||||
|
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||||
|
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail register if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.ctx.Value(constant.LoginType) != nil {
|
||||||
|
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||||
|
}
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -148,6 +164,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
"github.com/perfect-panel/server/internal/config"
|
||||||
@ -79,6 +80,22 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
|||||||
if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) {
|
if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind device to user if identifier is provided
|
||||||
|
if req.Identifier != "" {
|
||||||
|
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||||
|
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail login if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.ctx.Value(constant.LoginType) != nil {
|
||||||
|
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||||
|
}
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -88,6 +105,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -126,6 +126,21 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
// Bind device to user if identifier is provided
|
||||||
|
if req.Identifier != "" {
|
||||||
|
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||||
|
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail register if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.ctx.Value(constant.LoginType) != nil {
|
||||||
|
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||||
|
}
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -135,6 +150,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
@ -67,6 +68,10 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes
|
|||||||
for _, method := range authMethods {
|
for _, method := range authMethods {
|
||||||
if *method.Enabled {
|
if *method.Enabled {
|
||||||
methods = append(methods, method.Method)
|
methods = append(methods, method.Method)
|
||||||
|
if method.Method == "device" {
|
||||||
|
_ = json.Unmarshal([]byte(method.Config), &resp.Auth.Device)
|
||||||
|
resp.Auth.Device.Enable = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.OAuthMethods = methods
|
resp.OAuthMethods = methods
|
||||||
|
|||||||
@ -0,0 +1,195 @@
|
|||||||
|
package subscribe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/model/node"
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryUserSubscribeNodeListLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user subscribe node info
|
||||||
|
func NewQueryUserSubscribeNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserSubscribeNodeListLogic {
|
||||||
|
return &QueryUserSubscribeNodeListLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryUserSubscribeNodeListLogic) QueryUserSubscribeNodeList() (resp *types.QueryUserSubscribeNodeListResponse, err error) {
|
||||||
|
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("current user is not found in context")
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||||
|
}
|
||||||
|
|
||||||
|
userSubscribes, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id, 1, 2)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("failed to query user subscribe", logger.Field("error", err.Error()), logger.Field("user_id", u.Id))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "DB_ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = &types.QueryUserSubscribeNodeListResponse{}
|
||||||
|
for _, us := range userSubscribes {
|
||||||
|
userSubscribe, err := l.getUserSubscribe(us.Token)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[SubscribeLogic] Get user subscribe failed", logger.Field("error", err.Error()), logger.Field("token", userSubscribe.Token))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes, err := l.getServers(userSubscribe)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userSubscribeInfo := types.UserSubscribeInfo{
|
||||||
|
Id: userSubscribe.Id,
|
||||||
|
Nodes: nodes,
|
||||||
|
Traffic: userSubscribe.Traffic,
|
||||||
|
Upload: userSubscribe.Upload,
|
||||||
|
Download: userSubscribe.Download,
|
||||||
|
Token: userSubscribe.Token,
|
||||||
|
UserId: userSubscribe.UserId,
|
||||||
|
OrderId: userSubscribe.OrderId,
|
||||||
|
SubscribeId: userSubscribe.SubscribeId,
|
||||||
|
StartTime: userSubscribe.StartTime.Unix(),
|
||||||
|
ExpireTime: userSubscribe.ExpireTime.Unix(),
|
||||||
|
Status: userSubscribe.Status,
|
||||||
|
CreatedAt: userSubscribe.CreatedAt.Unix(),
|
||||||
|
UpdatedAt: userSubscribe.UpdatedAt.Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if userSubscribe.FinishedAt != nil {
|
||||||
|
userSubscribeInfo.FinishedAt = userSubscribe.FinishedAt.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.svcCtx.Config.Register.EnableTrial && l.svcCtx.Config.Register.TrialSubscribe == userSubscribe.SubscribeId {
|
||||||
|
userSubscribeInfo.IsTryOut = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.List = append(resp.List, userSubscribeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryUserSubscribeNodeListLogic) getServers(userSub *user.Subscribe) (userSubscribeNodes []*types.UserSubscribeNodeInfo, err error) {
|
||||||
|
userSubscribeNodes = make([]*types.UserSubscribeNodeInfo, 0)
|
||||||
|
if l.isSubscriptionExpired(userSub) {
|
||||||
|
return l.createExpiredServers(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
subDetails, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, userSub.SubscribeId)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[Generate Subscribe]find subscribe details error: %v", logger.Field("error", err.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error())
|
||||||
|
}
|
||||||
|
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
|
||||||
|
tags := strings.Split(subDetails.NodeTags, ",")
|
||||||
|
|
||||||
|
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
|
||||||
|
|
||||||
|
enable := true
|
||||||
|
|
||||||
|
_, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||||
|
Page: 0,
|
||||||
|
Size: 1000,
|
||||||
|
NodeId: nodeIds,
|
||||||
|
Enabled: &enable, // Only get enabled nodes
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(nodes) > 0 {
|
||||||
|
var serverMapIds = make(map[int64]*node.Server)
|
||||||
|
for _, n := range nodes {
|
||||||
|
serverMapIds[n.ServerId] = nil
|
||||||
|
}
|
||||||
|
var serverIds []int64
|
||||||
|
for k := range serverMapIds {
|
||||||
|
serverIds = append(serverIds, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := l.svcCtx.NodeModel.QueryServerList(l.ctx, serverIds)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range servers {
|
||||||
|
serverMapIds[s.Id] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range nodes {
|
||||||
|
server := serverMapIds[n.ServerId]
|
||||||
|
if server == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userSubscribeNode := &types.UserSubscribeNodeInfo{
|
||||||
|
Id: n.Id,
|
||||||
|
Name: n.Name,
|
||||||
|
Uuid: userSub.UUID,
|
||||||
|
Protocol: n.Protocol,
|
||||||
|
Port: n.Port,
|
||||||
|
Address: n.Address,
|
||||||
|
Tags: strings.Split(n.Tags, ","),
|
||||||
|
Country: server.Country,
|
||||||
|
City: server.City,
|
||||||
|
CreatedAt: n.CreatedAt.Unix(),
|
||||||
|
}
|
||||||
|
userSubscribeNodes = append(userSubscribeNodes, userSubscribeNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Debugf("[Query Subscribe]found servers: %v", len(nodes))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
|
||||||
|
}
|
||||||
|
logger.Debugf("[Generate Subscribe]found servers: %v", len(nodes))
|
||||||
|
return userSubscribeNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryUserSubscribeNodeListLogic) isSubscriptionExpired(userSub *user.Subscribe) bool {
|
||||||
|
return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryUserSubscribeNodeListLogic) createExpiredServers() []*types.UserSubscribeNodeInfo {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *QueryUserSubscribeNodeListLogic) getFirstHostLine() string {
|
||||||
|
host := l.svcCtx.Config.Host
|
||||||
|
lines := strings.Split(host, "\n")
|
||||||
|
if len(lines) > 0 {
|
||||||
|
return lines[0]
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
func (l *QueryUserSubscribeNodeListLogic) getUserSubscribe(token string) (*user.Subscribe, error) {
|
||||||
|
userSub, err := l.svcCtx.UserModel.FindOneSubscribeByToken(l.ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
l.Infow("[Generate Subscribe]find subscribe error: %v", logger.Field("error", err.Error()), logger.Field("token", token))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore expiration check
|
||||||
|
//if userSub.Status > 1 {
|
||||||
|
// l.Infow("[Generate Subscribe]subscribe is not available", logger.Field("status", int(userSub.Status)), logger.Field("token", token))
|
||||||
|
// return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNotAvailable), "subscribe is not available")
|
||||||
|
//}
|
||||||
|
|
||||||
|
return userSub, nil
|
||||||
|
}
|
||||||
39
internal/logic/public/user/getDeviceListLogic.go
Normal file
39
internal/logic/public/user/getDeviceListLogic.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetDeviceListLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Device List
|
||||||
|
func NewGetDeviceListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetDeviceListLogic {
|
||||||
|
return &GetDeviceListLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetDeviceListLogic) GetDeviceList() (resp *types.GetDeviceListResponse, err error) {
|
||||||
|
userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||||
|
list, count, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, userInfo.Id)
|
||||||
|
userRespList := make([]types.UserDevice, 0)
|
||||||
|
tool.DeepCopy(&userRespList, list)
|
||||||
|
resp = &types.GetDeviceListResponse{
|
||||||
|
Total: count,
|
||||||
|
List: userRespList,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
72
internal/logic/public/user/unbindDeviceLogic.go
Normal file
72
internal/logic/public/user/unbindDeviceLogic.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/config"
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UnbindDeviceLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unbind Device
|
||||||
|
func NewUnbindDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnbindDeviceLogic {
|
||||||
|
return &UnbindDeviceLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UnbindDeviceLogic) UnbindDevice(req *types.UnbindDeviceRequest) error {
|
||||||
|
userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||||
|
device, err := l.svcCtx.UserModel.FindOneDevice(l.ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DeviceNotExist), "find device")
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.UserId != userInfo.Id {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "device not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
var deleteDevice user.Device
|
||||||
|
err = tx.Model(&deleteDevice).Where("id = ?", req.Id).First(&deleteDevice).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.QueueEnqueueError), "find device err: %v", err)
|
||||||
|
}
|
||||||
|
err = tx.Delete(deleteDevice).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device err: %v", err)
|
||||||
|
}
|
||||||
|
var userAuth user.AuthMethods
|
||||||
|
err = tx.Model(&userAuth).Where("auth_identifier = ? and auth_type = ?", deleteDevice.Identifier, "device").First(&userAuth).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find device online record err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Delete(&userAuth).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device online record err: %v", err)
|
||||||
|
}
|
||||||
|
sessionId := l.ctx.Value(constant.CtxKeySessionID)
|
||||||
|
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||||
|
l.svcCtx.Redis.Del(l.ctx, sessionIdCacheKey)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -40,6 +40,11 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loginType := ""
|
||||||
|
if claims["LoginType"] != nil {
|
||||||
|
loginType = claims["LoginType"].(string)
|
||||||
|
}
|
||||||
// get user id from token
|
// get user id from token
|
||||||
userId := int64(claims["UserId"].(float64))
|
userId := int64(claims["UserId"].(float64))
|
||||||
// get session id from token
|
// get session id from token
|
||||||
@ -77,6 +82,7 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx = context.WithValue(ctx, constant.LoginType, loginType)
|
||||||
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
||||||
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
||||||
c.Request = c.Request.WithContext(ctx)
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
|||||||
295
internal/middleware/deviceMiddleware.go
Normal file
295
internal/middleware/deviceMiddleware.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
pkgaes "github.com/perfect-panel/server/pkg/aes"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
noWritten = -1
|
||||||
|
defaultStatus = http.StatusOK
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeviceMiddleware(srvCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
if !srvCtx.Config.Device.Enable {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if srvCtx.Config.Device.SecuritySecret == "" {
|
||||||
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.SecretIsEmpty), "Secret is empty"))
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
if ctx.Value(constant.CtxKeyUser) == nil && c.GetHeader("Login-Type") != "" {
|
||||||
|
ctx = context.WithValue(ctx, constant.LoginType, c.GetHeader("Login-Type"))
|
||||||
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginType, ok := ctx.Value(constant.LoginType).(string)
|
||||||
|
if !ok || loginType != "device" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw := NewResponseWriter(c, srvCtx)
|
||||||
|
if !rw.Decrypt() {
|
||||||
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidCiphertext), "Invalid ciphertext"))
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer = rw
|
||||||
|
c.Next()
|
||||||
|
rw.FlushAbort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponseWriter(c *gin.Context, srvCtx *svc.ServiceContext) (rw *ResponseWriter) {
|
||||||
|
rw = &ResponseWriter{
|
||||||
|
c: c,
|
||||||
|
body: new(bytes.Buffer),
|
||||||
|
ResponseWriter: c.Writer,
|
||||||
|
}
|
||||||
|
rw.encryptionKey = srvCtx.Config.Device.SecuritySecret
|
||||||
|
rw.encryptionMethod = "AES"
|
||||||
|
rw.encryption = true
|
||||||
|
return rw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Encrypt() {
|
||||||
|
if !rw.encryption {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := rw.body.Bytes()
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
err := json.Unmarshal(buf, ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := params["data"]
|
||||||
|
if data != nil {
|
||||||
|
var jsonData []byte
|
||||||
|
str, ok := data.(string)
|
||||||
|
if ok {
|
||||||
|
jsonData = []byte(str)
|
||||||
|
} else {
|
||||||
|
jsonData, _ = json.Marshal(data)
|
||||||
|
}
|
||||||
|
encrypt, iv, err := pkgaes.Encrypt(jsonData, rw.encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params["data"] = map[string]interface{}{
|
||||||
|
"data": encrypt,
|
||||||
|
"time": iv,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
marshal, _ := json.Marshal(params)
|
||||||
|
rw.body.Reset()
|
||||||
|
rw.body.Write(marshal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Decrypt() bool {
|
||||||
|
if !rw.encryption {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断url链接中是否存在data和iv数据,存在就进行解密并设置回去
|
||||||
|
query := rw.c.Request.URL.Query()
|
||||||
|
dataStr := query.Get("data")
|
||||||
|
timeStr := query.Get("time")
|
||||||
|
if dataStr != "" && timeStr != "" {
|
||||||
|
decrypt, err := pkgaes.Decrypt(dataStr, rw.encryptionKey, timeStr)
|
||||||
|
if err == nil {
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal([]byte(decrypt), ¶ms)
|
||||||
|
if err == nil {
|
||||||
|
for k, v := range params {
|
||||||
|
query.Set(k, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
query.Del("data")
|
||||||
|
query.Del("time")
|
||||||
|
rw.c.Request.RequestURI = fmt.Sprintf("%s?%s", rw.c.Request.RequestURI[:strings.Index(rw.c.Request.RequestURI, "?")], query.Encode())
|
||||||
|
rw.c.Request.URL.RawQuery = query.Encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断body是否存在数据,存在就尝试解密,并设置回去
|
||||||
|
body, err := io.ReadAll(rw.c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(body, ¶ms)
|
||||||
|
data := params["data"]
|
||||||
|
nonce := params["time"]
|
||||||
|
if err != nil || data == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := data.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iv, ok := nonce.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt, err := pkgaes.Decrypt(str, rw.encryptionKey, iv)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) FlushAbort() {
|
||||||
|
defer rw.c.Abort()
|
||||||
|
responseBody := rw.body.String()
|
||||||
|
fmt.Println("Original Response Body:", responseBody)
|
||||||
|
rw.flush = true
|
||||||
|
if rw.encryption {
|
||||||
|
rw.Encrypt()
|
||||||
|
}
|
||||||
|
_, err := rw.Write(rw.body.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
size int
|
||||||
|
status int
|
||||||
|
flush bool
|
||||||
|
body *bytes.Buffer
|
||||||
|
c *gin.Context
|
||||||
|
encryption bool
|
||||||
|
encryptionKey string
|
||||||
|
encryptionMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Unwrap() http.ResponseWriter {
|
||||||
|
return rw.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:unused
|
||||||
|
func (rw *ResponseWriter) reset(writer http.ResponseWriter) {
|
||||||
|
rw.ResponseWriter = writer
|
||||||
|
rw.size = noWritten
|
||||||
|
rw.status = defaultStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) WriteHeader(code int) {
|
||||||
|
if code > 0 && rw.status != code {
|
||||||
|
if rw.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.status = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) WriteHeaderNow() {
|
||||||
|
if !rw.Written() {
|
||||||
|
rw.size = 0
|
||||||
|
rw.ResponseWriter.WriteHeader(rw.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Write(data []byte) (n int, err error) {
|
||||||
|
if rw.flush {
|
||||||
|
rw.WriteHeaderNow()
|
||||||
|
n, err = rw.ResponseWriter.Write(data)
|
||||||
|
rw.size += n
|
||||||
|
} else {
|
||||||
|
rw.body.Write(data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) WriteString(s string) (n int, err error) {
|
||||||
|
if rw.flush {
|
||||||
|
rw.WriteHeaderNow()
|
||||||
|
n, err = rw.ResponseWriter.Write([]byte(s))
|
||||||
|
rw.size += n
|
||||||
|
} else {
|
||||||
|
rw.body.Write([]byte(s))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Status() int {
|
||||||
|
return rw.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Size() int {
|
||||||
|
return rw.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Written() bool {
|
||||||
|
return rw.size != noWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack implements the http.Hijacker interface.
|
||||||
|
func (rw *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if rw.size < 0 {
|
||||||
|
rw.size = 0
|
||||||
|
}
|
||||||
|
return rw.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify implements the http.CloseNotifier interface.
|
||||||
|
func (rw *ResponseWriter) CloseNotify() <-chan bool {
|
||||||
|
// 通过 r.Context().Done() 来监听请求的取消
|
||||||
|
done := rw.c.Request.Context().Done()
|
||||||
|
closed := make(chan bool)
|
||||||
|
|
||||||
|
// 当上下文被取消时,通过 closed channel 发送通知
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
closed <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the http.Flusher interface.
|
||||||
|
func (rw *ResponseWriter) Flush() {
|
||||||
|
rw.WriteHeaderNow()
|
||||||
|
rw.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Pusher() (pusher http.Pusher) {
|
||||||
|
if pusher, ok := rw.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return pusher
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -43,7 +43,7 @@ func (m *customAnnouncementModel) GetAnnouncementListByPage(ctx context.Context,
|
|||||||
if filter.Search != "" {
|
if filter.Search != "" {
|
||||||
conn = conn.Where("`title` LIKE ? OR `content` LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%")
|
conn = conn.Where("`title` LIKE ? OR `content` LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%")
|
||||||
}
|
}
|
||||||
return conn.Count(&total).Offset((page - 1) * size).Limit(size).Find(v).Error
|
return conn.Count(&total).Offset((page - 1) * size).Limit(size).Find(&list).Error
|
||||||
})
|
})
|
||||||
return total, list, err
|
return total, list, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type (
|
|||||||
UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error
|
UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error
|
||||||
DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error
|
DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error
|
||||||
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
|
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
|
||||||
|
QueryServerList(ctx context.Context, ids []int64) (servers []*Server, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeModel interface {
|
NodeModel interface {
|
||||||
|
|||||||
@ -65,6 +65,12 @@ func (m *customServerModel) FilterServerList(ctx context.Context, params *Filter
|
|||||||
return total, servers, err
|
return total, servers, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *customServerModel) QueryServerList(ctx context.Context, ids []int64) (servers []*Server, err error) {
|
||||||
|
query := m.WithContext(ctx).Model(&Server{})
|
||||||
|
err = query.Where("id IN (?)", ids).Find(&servers).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// FilterNodeList Filter Node List
|
// FilterNodeList Filter Node List
|
||||||
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
|
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
|
||||||
var nodes []*Node
|
var nodes []*Node
|
||||||
|
|||||||
@ -46,6 +46,16 @@ func (m *customUserModel) QueryDevicePageList(ctx context.Context, userId, subsc
|
|||||||
return list, total, err
|
return list, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryDeviceList returns a list of records that meet the conditions.
|
||||||
|
func (m *customUserModel) QueryDeviceList(ctx context.Context, userId int64) ([]*Device, int64, error) {
|
||||||
|
var list []*Device
|
||||||
|
var total int64
|
||||||
|
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&Device{}).Where("`user_id` = ?", userId).Count(&total).Find(&list).Error
|
||||||
|
})
|
||||||
|
return list, total, err
|
||||||
|
}
|
||||||
|
|
||||||
func (m *customUserModel) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
func (m *customUserModel) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
||||||
old, err := m.FindOneDevice(ctx, data.Id)
|
old, err := m.FindOneDevice(ctx, data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,3 +86,18 @@ func (m *customUserModel) DeleteDevice(ctx context.Context, id int64, tx ...*gor
|
|||||||
}, data.GetCacheKeys()...)
|
}, data.GetCacheKeys()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *customUserModel) InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
||||||
|
defer func() {
|
||||||
|
if clearErr := m.ClearDeviceCache(ctx, data); clearErr != nil {
|
||||||
|
// log cache clear error
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
if len(tx) > 0 {
|
||||||
|
conn = tx[0]
|
||||||
|
}
|
||||||
|
return conn.Create(data).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -95,10 +95,12 @@ type customUserLogicModel interface {
|
|||||||
FindUserAuthMethodByPlatform(ctx context.Context, userId int64, platform string) (*AuthMethods, error)
|
FindUserAuthMethodByPlatform(ctx context.Context, userId int64, platform string) (*AuthMethods, error)
|
||||||
FindOneByEmail(ctx context.Context, email string) (*User, error)
|
FindOneByEmail(ctx context.Context, email string) (*User, error)
|
||||||
FindOneDevice(ctx context.Context, id int64) (*Device, error)
|
FindOneDevice(ctx context.Context, id int64) (*Device, error)
|
||||||
|
QueryDeviceList(ctx context.Context, userid int64) ([]*Device, int64, error)
|
||||||
QueryDevicePageList(ctx context.Context, userid, subscribeId int64, page, size int) ([]*Device, int64, error)
|
QueryDevicePageList(ctx context.Context, userid, subscribeId int64, page, size int) ([]*Device, int64, error)
|
||||||
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||||
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
||||||
DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
|
DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
|
||||||
|
InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||||
|
|
||||||
ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
|
ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
|
||||||
ClearUserCache(ctx context.Context, data ...*User) error
|
ClearUserCache(ctx context.Context, data ...*User) error
|
||||||
|
|||||||
@ -118,6 +118,7 @@ type ApplicationVersion struct {
|
|||||||
type AuthConfig struct {
|
type AuthConfig struct {
|
||||||
Mobile MobileAuthenticateConfig `json:"mobile"`
|
Mobile MobileAuthenticateConfig `json:"mobile"`
|
||||||
Email EmailAuthticateConfig `json:"email"`
|
Email EmailAuthticateConfig `json:"email"`
|
||||||
|
Device DeviceAuthticateConfig `json:"device"`
|
||||||
Register PubilcRegisterConfig `json:"register"`
|
Register PubilcRegisterConfig `json:"register"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,6 +518,20 @@ type DeleteUserSubscribeRequest struct {
|
|||||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceAuthticateConfig struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
ShowAds bool `json:"show_ads"`
|
||||||
|
EnableSecurity bool `json:"enable_security"`
|
||||||
|
OnlyRealDevice bool `json:"only_real_device"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceLoginRequest struct {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
|
UserAgent string `json:"user_agent" validate:"required"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
type Document struct {
|
type Document struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
@ -802,6 +817,11 @@ type GetDetailRequest struct {
|
|||||||
Id int64 `form:"id" validate:"required"`
|
Id int64 `form:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetDeviceListResponse struct {
|
||||||
|
List []UserDevice `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
type GetDocumentDetailRequest struct {
|
type GetDocumentDetailRequest struct {
|
||||||
Id int64 `json:"id" validate:"required"`
|
Id int64 `json:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
@ -1520,7 +1540,7 @@ type PurchaseOrderResponse struct {
|
|||||||
|
|
||||||
type QueryAnnouncementRequest struct {
|
type QueryAnnouncementRequest struct {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
Size int `form:"size"`
|
Size int `form:"size,default=15"`
|
||||||
Pinned *bool `form:"pinned"`
|
Pinned *bool `form:"pinned"`
|
||||||
Popup *bool `form:"popup"`
|
Popup *bool `form:"popup"`
|
||||||
}
|
}
|
||||||
@ -1677,6 +1697,10 @@ type QueryUserSubscribeListResponse struct {
|
|||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QueryUserSubscribeNodeListResponse struct {
|
||||||
|
List []UserSubscribeInfo `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
type QuotaTask struct {
|
type QuotaTask struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Subscribers []int64 `json:"subscribers"`
|
Subscribers []int64 `json:"subscribers"`
|
||||||
@ -1737,11 +1761,13 @@ type RenewalOrderResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ResetPasswordRequest struct {
|
type ResetPasswordRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2090,16 +2116,19 @@ type TelephoneCheckUserResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TelephoneLoginRequest struct {
|
type TelephoneLoginRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneCode string `json:"telephone_code"`
|
TelephoneCode string `json:"telephone_code"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TelephoneRegisterRequest struct {
|
type TelephoneRegisterRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
@ -2107,16 +2136,19 @@ type TelephoneRegisterRequest struct {
|
|||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type,optional"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TelephoneResetPasswordRequest struct {
|
type TelephoneResetPasswordRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type,optional"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2211,6 +2243,10 @@ type Tuic struct {
|
|||||||
SecurityConfig SecurityConfig `json:"security_config"`
|
SecurityConfig SecurityConfig `json:"security_config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UnbindDeviceRequest struct {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type UnbindOAuthRequest struct {
|
type UnbindOAuthRequest struct {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
}
|
}
|
||||||
@ -2490,20 +2526,24 @@ type UserLoginLog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserLoginRequest struct {
|
type UserLoginRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserRegisterRequest struct {
|
type UserRegisterRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Invite string `json:"invite,optional"`
|
Invite string `json:"invite,optional"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2559,6 +2599,26 @@ type UserSubscribeDetail struct {
|
|||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserSubscribeInfo struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
OrderId int64 `json:"order_id"`
|
||||||
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
|
StartTime int64 `json:"start_time"`
|
||||||
|
ExpireTime int64 `json:"expire_time"`
|
||||||
|
FinishedAt int64 `json:"finished_at"`
|
||||||
|
ResetTime int64 `json:"reset_time"`
|
||||||
|
Traffic int64 `json:"traffic"`
|
||||||
|
Download int64 `json:"download"`
|
||||||
|
Upload int64 `json:"upload"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Status uint8 `json:"status"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
IsTryOut bool `json:"is_try_out"`
|
||||||
|
Nodes []*UserSubscribeNodeInfo `json:"nodes"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserSubscribeLog struct {
|
type UserSubscribeLog struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
UserId int64 `json:"user_id"`
|
UserId int64 `json:"user_id"`
|
||||||
@ -2569,6 +2629,19 @@ type UserSubscribeLog struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserSubscribeNodeInfo struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
City string `json:"city"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserSubscribeTrafficLog struct {
|
type UserSubscribeTrafficLog struct {
|
||||||
SubscribeId int64 `json:"subscribe_id"` // Subscribe ID
|
SubscribeId int64 `json:"subscribe_id"` // Subscribe ID
|
||||||
UserId int64 `json:"user_id"` // User ID
|
UserId int64 `json:"user_id"` // User ID
|
||||||
|
|||||||
@ -8,4 +8,5 @@ const (
|
|||||||
CtxKeyRequestHost CtxKey = "requestHost"
|
CtxKeyRequestHost CtxKey = "requestHost"
|
||||||
CtxKeyPlatform CtxKey = "platform"
|
CtxKeyPlatform CtxKey = "platform"
|
||||||
CtxKeyPayment CtxKey = "payment"
|
CtxKeyPayment CtxKey = "payment"
|
||||||
|
LoginType CtxKey = "loginType"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodePassWord(t *testing.T) {
|
func TestEncodePassWord(t *testing.T) {
|
||||||
t.Logf("EncodePassWord: %v", EncodePassWord(""))
|
t.Logf("EncodePassWord: %v", EncodePassWord("password"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiPasswordVerify(t *testing.T) {
|
func TestMultiPasswordVerify(t *testing.T) {
|
||||||
|
|||||||
@ -47,6 +47,7 @@ const (
|
|||||||
ErrorTokenExpire uint32 = 40004
|
ErrorTokenExpire uint32 = 40004
|
||||||
InvalidAccess uint32 = 40005
|
InvalidAccess uint32 = 40005
|
||||||
InvalidCiphertext uint32 = 40006
|
InvalidCiphertext uint32 = 40006
|
||||||
|
SecretIsEmpty uint32 = 40007
|
||||||
)
|
)
|
||||||
|
|
||||||
//coupon error
|
//coupon error
|
||||||
|
|||||||
@ -14,6 +14,7 @@ func init() {
|
|||||||
ErrorTokenEmpty: "User token is empty",
|
ErrorTokenEmpty: "User token is empty",
|
||||||
ErrorTokenInvalid: "User token is invalid",
|
ErrorTokenInvalid: "User token is invalid",
|
||||||
ErrorTokenExpire: "User token is expired",
|
ErrorTokenExpire: "User token is expired",
|
||||||
|
SecretIsEmpty: "Secret is empty",
|
||||||
InvalidAccess: "Invalid access",
|
InvalidAccess: "Invalid access",
|
||||||
InvalidCiphertext: "Invalid ciphertext",
|
InvalidCiphertext: "Invalid ciphertext",
|
||||||
// Database error
|
// Database error
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user