完善登录逻辑,登录接口统一到 /token

This commit is contained in:
2025-04-23 19:01:08 +08:00
parent b181864a2f
commit 1374757eab
28 changed files with 404 additions and 266 deletions

View File

@@ -22,6 +22,12 @@
- [ ] Limiter
- [ ] Compress
使用 fiber 自带 validator 进行参数验证
增加 domain 层,缓解同包字段过长的问题
移动端适配
授权过期跳转登录,成功后返回原链接
错误处理类型转换失败问题

View File

@@ -179,20 +179,21 @@ comment on column user_role.deleted_at is '删除时间';
drop table if exists client cascade;
create table client (
id serial primary key,
client_id varchar(255) not null unique,
client_secret varchar(255) not null,
redirect_uri varchar(255),
grant_code bool not null default false,
grant_client bool not null default false,
grant_refresh bool not null default false,
spec int not null,
name varchar(255) not null,
icon varchar(255),
status int not null default 1,
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
deleted_at timestamp
id serial primary key,
client_id varchar(255) not null unique,
client_secret varchar(255) not null,
redirect_uri varchar(255),
grant_code bool not null default false,
grant_client bool not null default false,
grant_refresh bool not null default false,
grant_password bool not null default false,
spec int not null,
name varchar(255) not null,
icon varchar(255),
status int not null default 1,
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
deleted_at timestamp
);
create index client_client_id_index on client (client_id);
@@ -209,6 +210,7 @@ comment on column client.redirect_uri is 'OAuth2 重定向URI';
comment on column client.grant_code is '允许授权码授予';
comment on column client.grant_client is '允许客户端凭证授予';
comment on column client.grant_refresh is '允许刷新令牌授予';
comment on column client.grant_password is '允许密码授予';
comment on column client.spec is '安全规范0-web1-native2-browser';
comment on column client.name is '名称';
comment on column client.icon is '图标URL';
@@ -822,19 +824,19 @@ comment on column bill.deleted_at is '删除时间';
-- coupon 优惠券
drop table if exists coupon cascade;
create table coupon (
id serial primary key,
user_id int references "user" (id)
id serial primary key,
user_id int references "user" (id)
on update cascade
on delete cascade,
code varchar(255) not null unique,
remark varchar(255),
amount decimal(12, 2) not null default 0,
code varchar(255) not null unique,
remark varchar(255),
amount decimal(12, 2) not null default 0,
min_amount decimal(12, 2) not null default 0,
status int not null default 0,
expire_at timestamp,
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
deleted_at timestamp
status int not null default 0,
expire_at timestamp,
created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
deleted_at timestamp
);
create index coupon_user_id_index on coupon (user_id);
create index coupon_code_index on coupon (code);

View File

@@ -76,7 +76,6 @@ func Permit(types []services.PayloadType, permissions ...string) fiber.Handler {
return c.Next()
}
}
func PermitAll(permissions ...string) fiber.Handler {
@@ -88,14 +87,6 @@ func PermitAll(permissions ...string) fiber.Handler {
}, permissions...)
}
// PermitUser 创建针对单个路由的鉴权中间件
func PermitUser(permissions ...string) fiber.Handler {
return Permit([]services.PayloadType{
services.PayloadUser,
services.PayloadAdmin,
}, permissions...)
}
func PermitDevice(permissions ...string) fiber.Handler {
return Permit([]services.PayloadType{
services.PayloadClientPublic,
@@ -104,74 +95,6 @@ func PermitDevice(permissions ...string) fiber.Handler {
}, permissions...)
}
func PermitPublic(permissions ...string) fiber.Handler {
return Permit([]services.PayloadType{
services.PayloadClientPublic,
services.PayloadAdmin,
}, permissions...)
}
func PermitConfidential(permissions ...string) fiber.Handler {
return Permit([]services.PayloadType{
services.PayloadClientConfidential,
services.PayloadAdmin,
}, permissions...)
}
func authBearer(ctx context.Context, token string) (*services.AuthContext, error) {
auth, err := services.Session.Find(ctx, token)
if err != nil {
slog.Debug(err.Error())
return nil, err
}
return auth, nil
}
func authBasic(ctx context.Context, token string) (*services.AuthContext, error) {
// 解析 Basic 认证信息
var base, err = base64.URLEncoding.DecodeString(token)
if err != nil {
slog.Debug(err.Error())
return nil, err
}
var split = strings.Split(string(base), ":")
if len(split) != 2 {
msg := "无法解析 Basic 认证信息"
slog.Debug(msg)
return nil, errors.New(msg)
}
var clientID = split[0]
// 获取客户端信息
client, err := q.Client.
Where(
q.Client.ClientID.Eq(clientID),
q.Client.Spec.Eq(0),
q.Client.GrantClient.Is(true),
q.Client.Status.Eq(1)).
Take()
if err != nil {
return nil, err
}
// todo 查询客户端关联权限
// 组织授权信息(一次性请求)
return &services.AuthContext{
Payload: services.Payload{
Id: client.ID,
Type: services.PayloadClientConfidential,
Name: client.Name,
Avatar: client.Icon,
},
Permissions: nil,
Metadata: nil,
}, nil
}
func Protect(c *fiber.Ctx, types []services.PayloadType, permissions []string) (*services.AuthContext, error) {
// 获取令牌
var header = c.Get("Authorization")
@@ -216,3 +139,57 @@ func Protect(c *fiber.Ctx, types []services.PayloadType, permissions []string) (
return auth, nil
}
func authBearer(ctx context.Context, token string) (*services.AuthContext, error) {
auth, err := services.Session.Find(ctx, token)
if err != nil {
slog.Debug(err.Error())
return nil, err
}
return auth, nil
}
func authBasic(_ context.Context, token string) (*services.AuthContext, error) {
// 解析 Basic 认证信息
var base, err = base64.URLEncoding.DecodeString(token)
if err != nil {
slog.Debug(err.Error())
return nil, err
}
var split = strings.Split(string(base), ":")
if len(split) != 2 {
msg := "无法解析 Basic 认证信息"
slog.Debug(msg)
return nil, errors.New(msg)
}
var clientID = split[0]
// 获取客户端信息
client, err := q.Client.
Where(
q.Client.ClientID.Eq(clientID),
q.Client.Spec.Eq(0),
q.Client.GrantClient.Is(true),
q.Client.Status.Eq(1)).
Take()
if err != nil {
return nil, err
}
// todo 查询客户端关联权限
// 组织授权信息(一次性请求)
return &services.AuthContext{
Payload: services.Payload{
Id: client.ID,
Type: services.PayloadClientConfidential,
Name: client.Name,
Avatar: client.Icon,
},
Permissions: nil,
Metadata: nil,
}, nil
}

View File

@@ -51,7 +51,7 @@ func ListBill(c *fiber.Ctx) error {
do = do.Where(q.Bill.BillNo.Eq(*req.BillNo))
}
bills, err := do.Debug().
bills, err := do.
Preload(q.Bill.Resource, q.Bill.Trade, q.Bill.Refund).
Preload(q.Bill.Resource.Pss).
Order(q.Bill.CreatedAt.Desc()).

View File

@@ -3,9 +3,10 @@ package handlers
import (
"encoding/base64"
"errors"
"platform/web/models"
"log/slog"
m "platform/web/models"
q "platform/web/queries"
"platform/web/services"
s "platform/web/services"
"strings"
"time"
@@ -17,22 +18,42 @@ import (
// region Token
type TokenReq struct {
ClientID string `json:"client_id" form:"client_id"`
ClientSecret string `json:"client_secret" form:"client_secret"`
GrantType TokenGrantType `json:"grant_type" form:"grant_type"`
Code string `json:"code" form:"code"`
RedirectURI string `json:"redirect_uri" form:"redirect_uri"`
CodeVerifier string `json:"code_verifier" form:"code_verifier"`
RefreshToken string `json:"refresh_token" form:"refresh_token"`
Scope string `json:"scope" form:"scope"`
GrantType s.OauthGrantType `json:"grant_type" form:"grant_type"`
ClientID string `json:"client_id" form:"client_id"`
ClientSecret string `json:"client_secret" form:"client_secret"`
Scope string `json:"scope" form:"scope"`
TokenReqCode
TokenReqClient
TokenReqRefresh
TokenReqPassword
}
type TokenReqCode struct {
Code string `json:"code" form:"code"`
RedirectURI string `json:"redirect_uri" form:"redirect_uri"`
CodeVerifier string `json:"code_verifier" form:"code_verifier"`
}
type TokenReqClient struct {
}
type TokenReqRefresh struct {
RefreshToken string `json:"refresh_token" form:"refresh_token"`
}
type TokenReqPassword struct {
LoginType s.OauthGrantLoginType `json:"login_type" form:"login_type"`
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
Remember bool `json:"remember" form:"remember"`
}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope,omitempty"`
ExpiresIn int `json:"expires_in"`
}
type TokenErrResp struct {
@@ -40,57 +61,57 @@ type TokenErrResp struct {
Description string `json:"error_description,omitempty"`
}
type TokenGrantType string
const (
AuthorizationCode = TokenGrantType("authorization_code")
ClientCredentials = TokenGrantType("client_credentials")
RefreshToken = TokenGrantType("refresh_token")
)
// Token 处理 OAuth2.0 授权请求
func Token(c *fiber.Ctx) error {
// 验证请求参数
req := new(TokenReq)
if err := c.BodyParser(req); err != nil {
return sendError(c, services.ErrOauthInvalidRequest, "无法解析请求参数")
return sendError(c, s.ErrOauthInvalidRequest, "无法解析请求参数")
}
if req.GrantType == "" {
return sendError(c, services.ErrOauthInvalidRequest, "缺少必要参数grant_type")
return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数grant_type")
}
slog.Debug("oauth token", slog.String("grant_type",
string(req.GrantType)),
slog.String("client_id", req.ClientID),
)
// 基于授权类型处理请求
switch req.GrantType {
case AuthorizationCode:
case s.OauthGrantTypeAuthorizationCode:
return authorizationCode(c, req)
case ClientCredentials:
case s.OauthGrantTypeClientCredentials:
return clientCredentials(c, req)
case RefreshToken:
case s.OauthGrantTypeRefreshToken:
return refreshToken(c, req)
case s.OauthGrantTypePassword:
return password(c, req)
default:
return sendError(c, services.ErrOauthUnsupportedGrantType)
return sendError(c, s.ErrOauthUnsupportedGrantType)
}
}
// 授权码
func authorizationCode(c *fiber.Ctx, req *TokenReq) error {
if req.Code == "" {
return sendError(c, services.ErrOauthInvalidRequest, "缺少必要参数code")
return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数code")
}
client, err := protect(c, services.GrantTypeAuthorizationCode, req.ClientID, req.ClientSecret)
client, err := protect(c, s.OauthGrantTypeAuthorizationCode, req.ClientID, req.ClientSecret)
if err != nil {
return sendError(c, err)
}
token, err := services.Auth.OauthAuthorizationCode(c.Context(), client, req.Code, req.RedirectURI, req.CodeVerifier)
token, err := s.Auth.OauthAuthorizationCode(c.Context(), client, req.Code, req.RedirectURI, req.CodeVerifier)
if err != nil {
return sendError(c, err.(services.AuthServiceOauthError))
return sendError(c, err.(s.AuthServiceOauthError))
}
return sendSuccess(c, token)
@@ -98,15 +119,15 @@ func authorizationCode(c *fiber.Ctx, req *TokenReq) error {
// 客户端凭证
func clientCredentials(c *fiber.Ctx, req *TokenReq) error {
client, err := protect(c, services.GrantTypeClientCredentials, req.ClientID, req.ClientSecret)
client, err := protect(c, s.OauthGrantTypeClientCredentials, req.ClientID, req.ClientSecret)
if err != nil {
return sendError(c, err)
}
scope := strings.Split(req.Scope, ",")
token, err := services.Auth.OauthClientCredentials(c.Context(), client, scope...)
token, err := s.Auth.OauthClientCredentials(c.Context(), client, scope...)
if err != nil {
return sendError(c, err.(services.AuthServiceOauthError))
return sendError(c, err.(s.AuthServiceOauthError))
}
return sendSuccess(c, token)
@@ -115,19 +136,19 @@ func clientCredentials(c *fiber.Ctx, req *TokenReq) error {
// 刷新令牌
func refreshToken(c *fiber.Ctx, req *TokenReq) error {
if req.RefreshToken == "" {
return sendError(c, services.ErrOauthInvalidRequest, "缺少必要参数refresh_token")
return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数refresh_token")
}
client, err := protect(c, services.GrantTypeRefreshToken, req.ClientID, req.ClientSecret)
client, err := protect(c, s.OauthGrantTypeRefreshToken, req.ClientID, req.ClientSecret)
if err != nil {
return sendError(c, err)
}
scope := strings.Split(req.Scope, ",")
token, err := services.Auth.OauthRefreshToken(c.Context(), client, req.RefreshToken, scope)
token, err := s.Auth.OauthRefreshToken(c.Context(), client, req.RefreshToken, scope)
if err != nil {
if errors.Is(err, services.ErrInvalidToken) {
return sendError(c, services.ErrOauthInvalidGrant)
if errors.Is(err, s.ErrInvalidToken) {
return sendError(c, s.ErrOauthInvalidGrant)
}
return sendError(c, err)
}
@@ -135,8 +156,108 @@ func refreshToken(c *fiber.Ctx, req *TokenReq) error {
return sendSuccess(c, token)
}
func password(c *fiber.Ctx, req *TokenReq) error {
if req.LoginType == "" {
return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数password_type")
}
if req.Username == "" {
return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数username")
}
if req.Password == "" {
return sendError(c, s.ErrOauthInvalidRequest, "缺少必要参数password")
}
// 验证客户端凭证
_, err := protect(c, s.OauthGrantTypePassword, req.ClientID, req.ClientSecret)
if err != nil {
return sendError(c, err)
}
// 验证验证码
err = s.Verifier.VerifySms(c.Context(), req.Username, req.Password)
if err != nil {
if errors.Is(err, s.ErrVerifierServiceInvalid) {
return fiber.NewError(fiber.StatusBadRequest, "验证码错误")
}
return err
}
// 查找用户
var user *m.User
err = q.Q.Transaction(func(tx *q.Query) error {
switch req.LoginType {
case s.OauthGrantPasswordTypePhoneCode:
user, err = tx.User.Where(tx.User.Phone.Eq(req.Username)).Take()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
case s.OauthGrantPasswordTypeEmailCode:
user, err = tx.User.Where(tx.User.Email.Eq(req.Username)).Take()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
case s.OauthGrantPasswordTypePassword:
user, err = tx.User.
Where(tx.User.Or(
tx.User.Phone.Eq(req.Username),
tx.User.Email.Eq(req.Username),
tx.User.Username.Eq(req.Username),
)).
Take()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
default:
return sendError(c, s.ErrOauthInvalidRequest, "无效的登录类型")
}
// 如果用户不存在,初始化用户 todo 初始化默认权限信息
if user == nil {
user = &m.User{
Phone: req.Username,
Username: req.Username,
}
}
// 更新用户的登录时间
user.LastLogin = time.Now()
user.LastLoginHost = c.IP()
user.LastLoginAgent = c.Get("User-Agent")
if err := tx.User.Omit(q.User.AdminID).Save(user); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
// 保存到会话
auth := s.AuthContext{
Payload: s.Payload{
Id: user.ID,
Type: s.PayloadUser,
Name: user.Name,
Avatar: user.Avatar,
},
}
duration := s.DefaultSessionConfig
if !req.Remember {
duration.RefreshTokenDuration = 0
}
token, err := s.Session.Create(c.Context(), auth)
if err != nil {
return err
}
return sendSuccess(c, token)
}
// 检查客户端凭证
func protect(c *fiber.Ctx, grant services.GrantType, clientId, clientSecret string) (*models.Client, error) {
func protect(c *fiber.Ctx, grant s.OauthGrantType, clientId, clientSecret string) (*m.Client, error) {
header := c.Get("Authorization")
if header != "" {
basic := strings.TrimPrefix(header, "Basic ")
@@ -155,44 +276,48 @@ func protect(c *fiber.Ctx, grant services.GrantType, clientId, clientSecret stri
// 查找客户端
if clientId == "" {
return nil, services.ErrOauthInvalidRequest
return nil, s.ErrOauthInvalidRequest
}
client, err := q.Client.Where(q.Client.ClientID.Eq(clientId)).Take()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, services.ErrOauthInvalidClient
return nil, s.ErrOauthInvalidClient
}
return nil, err
}
// 验证客户端状态
if client.Status != 1 {
return nil, services.ErrOauthUnauthorizedClient
return nil, s.ErrOauthUnauthorizedClient
}
// 验证授权类型
switch grant {
case services.GrantTypeAuthorizationCode:
case s.OauthGrantTypeAuthorizationCode:
if !client.GrantCode {
return nil, services.ErrOauthUnauthorizedClient
return nil, s.ErrOauthUnauthorizedClient
}
case services.GrantTypeClientCredentials:
case s.OauthGrantTypeClientCredentials:
if !client.GrantClient || client.Spec != 0 {
return nil, services.ErrOauthUnauthorizedClient
return nil, s.ErrOauthUnauthorizedClient
}
case services.GrantTypeRefreshToken:
case s.OauthGrantTypeRefreshToken:
if !client.GrantRefresh {
return nil, services.ErrOauthUnauthorizedClient
return nil, s.ErrOauthUnauthorizedClient
}
case s.OauthGrantTypePassword:
if !client.GrantPassword {
return nil, s.ErrOauthUnauthorizedClient
}
}
// 如果客户端是 confidential验证 client_secret失败返回错误
if client.Spec == 0 {
if clientSecret == "" {
return nil, services.ErrOauthInvalidRequest
return nil, s.ErrOauthInvalidRequest
}
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
return nil, services.ErrOauthInvalidClient
return nil, s.ErrOauthInvalidClient
}
}
@@ -200,7 +325,7 @@ func protect(c *fiber.Ctx, grant services.GrantType, clientId, clientSecret stri
}
// 发送成功响应
func sendSuccess(c *fiber.Ctx, details *services.TokenDetails) error {
func sendSuccess(c *fiber.Ctx, details *s.TokenDetails) error {
return c.JSON(TokenResp{
AccessToken: details.AccessToken,
TokenType: "Bearer",
@@ -211,23 +336,23 @@ func sendSuccess(c *fiber.Ctx, details *services.TokenDetails) error {
// 发送错误响应
func sendError(c *fiber.Ctx, err error, description ...string) error {
var sErr services.AuthServiceOauthError
var sErr s.AuthServiceOauthError
if errors.As(err, &sErr) {
status := fiber.StatusBadRequest
var desc string
switch {
case errors.Is(sErr, services.ErrOauthInvalidRequest):
case errors.Is(sErr, s.ErrOauthInvalidRequest):
desc = "无效的请求"
case errors.Is(sErr, services.ErrOauthInvalidClient):
case errors.Is(sErr, s.ErrOauthInvalidClient):
status = fiber.StatusUnauthorized
desc = "无效的客户端凭证"
case errors.Is(sErr, services.ErrOauthInvalidGrant):
case errors.Is(sErr, s.ErrOauthInvalidGrant):
desc = "无效的授权凭证"
case errors.Is(sErr, services.ErrOauthInvalidScope):
case errors.Is(sErr, s.ErrOauthInvalidScope):
desc = "无效的授权范围"
case errors.Is(sErr, services.ErrOauthUnauthorizedClient):
case errors.Is(sErr, s.ErrOauthUnauthorizedClient):
desc = "未授权的客户端"
case errors.Is(sErr, services.ErrOauthUnsupportedGrantType):
case errors.Is(sErr, s.ErrOauthUnsupportedGrantType):
desc = "不支持的授权类型"
}
if len(description) > 0 {

View File

@@ -2,6 +2,7 @@ package handlers
import (
"errors"
"platform/web/auth"
"platform/web/services"
"regexp"
"strconv"
@@ -16,6 +17,13 @@ type VerifierReq struct {
func SmsCode(c *fiber.Ctx) error {
_, err := auth.Protect(c, []services.PayloadType{
services.PayloadClientConfidential,
}, []string{})
if err != nil {
return err
}
// 解析请求参数
req := new(VerifierReq)
if err := c.BodyParser(req); err != nil {

View File

@@ -20,12 +20,12 @@ type Bill struct {
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
TradeID int32 `gorm:"column:trade_id" json:"trade_id"`
ResourceID int32 `gorm:"column:resource_id" json:"resource_id"`
Type int32 `gorm:"column:type;not null" json:"type"`
BillNo string `gorm:"column:bill_no;not null" json:"bill_no"`
RefundID int32 `gorm:"column:refund_id" json:"refund_id"`
Amount float64 `gorm:"column:amount;not null" json:"amount"`
TradeID int32 `gorm:"column:trade_id;comment:订单ID" json:"trade_id"` // 订单ID
ResourceID int32 `gorm:"column:resource_id;comment:套餐ID" json:"resource_id"` // 套餐ID
Type int32 `gorm:"column:type;not null;comment:账单类型0-充值1-消费2-退款" json:"type"` // 账单类型0-充值1-消费2-退款
BillNo string `gorm:"column:bill_no;not null;comment:易读账单号" json:"bill_no"` // 易读账单号
RefundID int32 `gorm:"column:refund_id;comment:退款ID" json:"refund_id"` // 退款ID
Amount float64 `gorm:"column:amount;not null;comment:账单金额" json:"amount"` // 账单金额
Trade *Trade `gorm:"foreignKey:TradeID" json:"trade"`
Refund *Refund `gorm:"foreignKey:RefundID" json:"refund"`
Resource *Resource `gorm:"foreignKey:ResourceID" json:"resource"`

View File

@@ -31,7 +31,7 @@ type Channel struct {
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
ProxyHost string `gorm:"column:proxy_host;not null" json:"proxy_host"`
Protocol int32 `gorm:"column:protocol" json:"protocol"`
Protocol int32 `gorm:"column:protocol;comment:协议类型1-http2-https3-socks5" json:"protocol"` // 协议类型1-http2-https3-socks5
}
// TableName Channel's table name

View File

@@ -14,20 +14,21 @@ const TableNameClient = "client"
// Client mapped from table <client>
type Client struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:客户端ID" json:"id"` // 客户端ID
ClientID string `gorm:"column:client_id;not null;comment:OAuth2客户端标识符" json:"client_id"` // OAuth2客户端标识符
ClientSecret string `gorm:"column:client_secret;not null;comment:OAuth2客户端密钥" json:"client_secret"` // OAuth2客户端密钥
RedirectURI string `gorm:"column:redirect_uri;comment:OAuth2 重定向URI" json:"redirect_uri"` // OAuth2 重定向URI
GrantCode bool `gorm:"column:grant_code;not null;comment:允许授权码授予" json:"grant_code"` // 允许授权码授予
GrantClient bool `gorm:"column:grant_client;not null;comment:允许客户端凭证授予" json:"grant_client"` // 允许客户端凭证授予
GrantRefresh bool `gorm:"column:grant_refresh;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予
Spec int32 `gorm:"column:spec;not null;comment:安全规范0-web1-native2-browser" json:"spec"` // 安全规范0-web1-native2-browser
Name string `gorm:"column:name;not null;comment:名称" json:"name"` // 名称
Icon string `gorm:"column:icon;comment:图标URL" json:"icon"` // 图标URL
Status int32 `gorm:"column:status;not null;default:1;comment:状态1-正常0-禁用" json:"status"` // 状态1-正常0-禁用
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:客户端ID" json:"id"` // 客户端ID
ClientID string `gorm:"column:client_id;not null;comment:OAuth2客户端标识符" json:"client_id"` // OAuth2客户端标识符
ClientSecret string `gorm:"column:client_secret;not null;comment:OAuth2客户端密钥" json:"client_secret"` // OAuth2客户端密钥
RedirectURI string `gorm:"column:redirect_uri;comment:OAuth2 重定向URI" json:"redirect_uri"` // OAuth2 重定向URI
GrantCode bool `gorm:"column:grant_code;not null;comment:允许授权码授予" json:"grant_code"` // 允许授权码授予
GrantClient bool `gorm:"column:grant_client;not null;comment:允许客户端凭证授予" json:"grant_client"` // 允许客户端凭证授予
GrantRefresh bool `gorm:"column:grant_refresh;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予
Spec int32 `gorm:"column:spec;not null;comment:安全规范0-web1-native2-browser" json:"spec"` // 安全规范0-web1-native2-browser
Name string `gorm:"column:name;not null;comment:名称" json:"name"` // 名称
Icon string `gorm:"column:icon;comment:图标URL" json:"icon"` // 图标URL
Status int32 `gorm:"column:status;not null;default:1;comment:状态1-正常0-禁用" json:"status"` // 状态1-正常0-禁用
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
GrantPassword bool `gorm:"column:grant_password;not null" json:"grant_password"`
}
// TableName Client's table name

View File

@@ -15,17 +15,17 @@ const TableNameCoupon = "coupon"
// Coupon mapped from table <coupon>
type Coupon struct {
ExpireAt time.Time `gorm:"column:expire_at" json:"expire_at"`
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
UserID int32 `gorm:"column:user_id" json:"user_id"`
Status int32 `gorm:"column:status;not null" json:"status"`
Code string `gorm:"column:code;not null" json:"code"`
Remark string `gorm:"column:remark" json:"remark"`
Amount float64 `gorm:"column:amount;not null" json:"amount"`
MinAmount float64 `gorm:"column:min_amount;not null" json:"min_amount"`
ExpireAt time.Time `gorm:"column:expire_at;comment:过期时间" json:"expire_at"` // 过期时间
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:优惠券ID" json:"id"` // 优惠券ID
UserID int32 `gorm:"column:user_id;comment:用户ID" json:"user_id"` // 用户ID
Status int32 `gorm:"column:status;not null;comment:优惠券状态0-未使用1-已使用2-已过期" json:"status"` // 优惠券状态0-未使用1-已使用2-已过期
Code string `gorm:"column:code;not null;comment:优惠券代码" json:"code"` // 优惠券代码
Remark string `gorm:"column:remark;comment:优惠券备注" json:"remark"` // 优惠券备注
Amount float64 `gorm:"column:amount;not null;comment:优惠券金额" json:"amount"` // 优惠券金额
MinAmount float64 `gorm:"column:min_amount;not null;comment:最低消费金额" json:"min_amount"` // 最低消费金额
}
// TableName Coupon's table name

View File

@@ -22,13 +22,13 @@ type Node struct {
City string `gorm:"column:city;not null;comment:城市" json:"city"` // 城市
ProxyID int32 `gorm:"column:proxy_id;comment:代理ID" json:"proxy_id"` // 代理ID
ProxyPort int32 `gorm:"column:proxy_port;comment:代理端口" json:"proxy_port"` // 代理端口
Status int32 `gorm:"column:status;not null;comment:节点状态:1-正常,0-离线" json:"status"` // 节点状态:1-正常,0-离线
Status int32 `gorm:"column:status;not null;comment:节点状态0-离线1-正常" json:"status"` // 节点状态0-离线1-正常
Rtt int32 `gorm:"column:rtt;comment:延迟" json:"rtt"` // 延迟
Loss int32 `gorm:"column:loss;comment:丢包率" json:"loss"` // 丢包率
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
Isp int32 `gorm:"column:isp;not null" json:"isp"`
Isp int32 `gorm:"column:isp;not null;comment:运营商0-其他1-电信2-联通3-移动" json:"isp"` // 运营商0-其他1-电信2-联通3-移动
}
// TableName Node's table name

View File

@@ -20,9 +20,9 @@ type Refund struct {
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
TradeID int32 `gorm:"column:trade_id;not null" json:"trade_id"`
Reason string `gorm:"column:reason" json:"reason"`
Status int32 `gorm:"column:status;not null" json:"status"`
TradeID int32 `gorm:"column:trade_id;not null;comment:订单ID" json:"trade_id"` // 订单ID
Reason string `gorm:"column:reason;comment:退款原因" json:"reason"` // 退款原因
Status int32 `gorm:"column:status;not null;comment:退款状态0-待处理1-已退款2-已拒绝" json:"status"` // 退款状态0-待处理1-已退款2-已拒绝
}
// TableName Refund's table name

View File

@@ -20,8 +20,8 @@ type Resource struct {
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
ResourceNo string `gorm:"column:resource_no" json:"resource_no"`
Type int32 `gorm:"column:type;not null" json:"type"`
ResourceNo string `gorm:"column:resource_no;comment:套餐编号" json:"resource_no"` // 套餐编号
Type int32 `gorm:"column:type;not null;comment:套餐类型1-动态2-隧道3-独享" json:"type"` // 套餐类型1-动态2-隧道3-独享
Pss *ResourcePss `gorm:"foreignKey:ResourceID;references:ID" json:"pss"`
}

View File

@@ -27,10 +27,10 @@ type Trade struct {
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
Type int32 `gorm:"column:type;not null" json:"type"`
CancelAt common.LocalDateTime `gorm:"column:cancel_at" json:"cancel_at"`
PaidAt common.LocalDateTime `gorm:"column:paid_at" json:"paid_at"`
PayURL string `gorm:"column:pay_url" json:"pay_url"`
Type int32 `gorm:"column:type;not null;comment:订单类型0-充值余额1-购买产品" json:"type"` // 订单类型0-充值余额1-购买产品
CancelAt common.LocalDateTime `gorm:"column:cancel_at;comment:取消时间" json:"cancel_at"` // 取消时间
PaidAt common.LocalDateTime `gorm:"column:paid_at;comment:支付时间" json:"paid_at"` // 支付时间
PayURL string `gorm:"column:pay_url;comment:支付链接" json:"pay_url"` // 支付链接
}
// TableName Trade's table name

View File

@@ -20,7 +20,7 @@ type Whitelist struct {
CreatedAt common.LocalDateTime `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt common.LocalDateTime `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:删除时间" json:"deleted_at"` // 删除时间
Remark string `gorm:"column:remark" json:"remark"`
Remark string `gorm:"column:remark;comment:备注" json:"remark"` // 备注
}
// TableName Whitelist's table name

View File

@@ -71,18 +71,18 @@ type bill struct {
billDo
ALL field.Asterisk
ID field.Int32 // 账单ID
UserID field.Int32 // 用户ID
Info field.String // 产品可读信息
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
TradeID field.Int32
ResourceID field.Int32
Type field.Int32
BillNo field.String
RefundID field.Int32
Amount field.Float64
ID field.Int32 // 账单ID
UserID field.Int32 // 用户ID
Info field.String // 产品可读信息
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
TradeID field.Int32 // 订单ID
ResourceID field.Int32 // 套餐ID
Type field.Int32 // 账单类型0-充值1-消费2-退款
BillNo field.String // 易读账单号
RefundID field.Int32 // 退款ID
Amount field.Float64 // 账单金额
Trade billBelongsToTrade
Refund billBelongsToRefund

View File

@@ -70,7 +70,7 @@ type channel struct {
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
ProxyHost field.String
Protocol field.Int32
Protocol field.Int32 // 协议类型1-http2-https3-socks5
fieldMap map[string]field.Expr
}

View File

@@ -41,6 +41,7 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) client {
_client.CreatedAt = field.NewField(tableName, "created_at")
_client.UpdatedAt = field.NewField(tableName, "updated_at")
_client.DeletedAt = field.NewField(tableName, "deleted_at")
_client.GrantPassword = field.NewBool(tableName, "grant_password")
_client.fillFieldMap()
@@ -50,21 +51,22 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) client {
type client struct {
clientDo
ALL field.Asterisk
ID field.Int32 // 客户端ID
ClientID field.String // OAuth2客户端标识符
ClientSecret field.String // OAuth2客户端密钥
RedirectURI field.String // OAuth2 重定向URI
GrantCode field.Bool // 允许授权码授予
GrantClient field.Bool // 允许客户端凭证授予
GrantRefresh field.Bool // 允许刷新令牌授予
Spec field.Int32 // 安全规范0-web1-native2-browser
Name field.String // 名称
Icon field.String // 图标URL
Status field.Int32 // 状态1-正常0-禁用
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
ALL field.Asterisk
ID field.Int32 // 客户端ID
ClientID field.String // OAuth2客户端标识符
ClientSecret field.String // OAuth2客户端密钥
RedirectURI field.String // OAuth2 重定向URI
GrantCode field.Bool // 允许授权码授予
GrantClient field.Bool // 允许客户端凭证授予
GrantRefresh field.Bool // 允许刷新令牌授予
Spec field.Int32 // 安全规范0-web1-native2-browser
Name field.String // 名称
Icon field.String // 图标URL
Status field.Int32 // 状态1-正常0-禁用
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
GrantPassword field.Bool
fieldMap map[string]field.Expr
}
@@ -95,6 +97,7 @@ func (c *client) updateTableName(table string) *client {
c.CreatedAt = field.NewField(table, "created_at")
c.UpdatedAt = field.NewField(table, "updated_at")
c.DeletedAt = field.NewField(table, "deleted_at")
c.GrantPassword = field.NewBool(table, "grant_password")
c.fillFieldMap()
@@ -111,7 +114,7 @@ func (c *client) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
}
func (c *client) fillFieldMap() {
c.fieldMap = make(map[string]field.Expr, 14)
c.fieldMap = make(map[string]field.Expr, 15)
c.fieldMap["id"] = c.ID
c.fieldMap["client_id"] = c.ClientID
c.fieldMap["client_secret"] = c.ClientSecret
@@ -126,6 +129,7 @@ func (c *client) fillFieldMap() {
c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt
c.fieldMap["deleted_at"] = c.DeletedAt
c.fieldMap["grant_password"] = c.GrantPassword
}
func (c client) clone(db *gorm.DB) client {

View File

@@ -48,17 +48,17 @@ type coupon struct {
couponDo
ALL field.Asterisk
ExpireAt field.Time
CreatedAt field.Field
UpdatedAt field.Field
DeletedAt field.Field
ID field.Int32
UserID field.Int32
Status field.Int32
Code field.String
Remark field.String
Amount field.Float64
MinAmount field.Float64
ExpireAt field.Time // 过期时间
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
ID field.Int32 // 优惠券ID
UserID field.Int32 // 用户ID
Status field.Int32 // 优惠券状态0-未使用1-已使用2-已过期
Code field.String // 优惠券代码
Remark field.String // 优惠券备注
Amount field.Float64 // 优惠券金额
MinAmount field.Float64 // 最低消费金额
fieldMap map[string]field.Expr
}

View File

@@ -60,13 +60,13 @@ type node struct {
City field.String // 城市
ProxyID field.Int32 // 代理ID
ProxyPort field.Int32 // 代理端口
Status field.Int32 // 节点状态:1-正常,0-离线
Status field.Int32 // 节点状态0-离线1-正常
Rtt field.Int32 // 延迟
Loss field.Int32 // 丢包率
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
Isp field.Int32
Isp field.Int32 // 运营商0-其他1-电信2-联通3-移动
fieldMap map[string]field.Expr
}

View File

@@ -52,9 +52,9 @@ type refund struct {
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
TradeID field.Int32
Reason field.String
Status field.Int32
TradeID field.Int32 // 订单ID
Reason field.String // 退款原因
Status field.Int32 // 退款状态0-待处理1-已退款2-已拒绝
fieldMap map[string]field.Expr
}

View File

@@ -50,14 +50,14 @@ type resource struct {
resourceDo
ALL field.Asterisk
ID field.Int32 // 套餐ID
UserID field.Int32 // 用户ID
Active field.Bool // 套餐状态
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
ResourceNo field.String
Type field.Int32
ID field.Int32 // 套餐ID
UserID field.Int32 // 用户ID
Active field.Bool // 套餐状态
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
ResourceNo field.String // 套餐编号
Type field.Int32 // 套餐类型1-动态2-隧道3-独享
Pss resourceHasOnePss
fieldMap map[string]field.Expr

View File

@@ -67,10 +67,10 @@ type trade struct {
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
Type field.Int32
CancelAt field.Field
PaidAt field.Field
PayURL field.String
Type field.Int32 // 订单类型0-充值余额1-购买产品
CancelAt field.Field // 取消时间
PaidAt field.Field // 支付时间
PayURL field.String // 支付链接
fieldMap map[string]field.Expr
}

View File

@@ -50,7 +50,7 @@ type whitelist struct {
CreatedAt field.Field // 创建时间
UpdatedAt field.Field // 更新时间
DeletedAt field.Field // 删除时间
Remark field.String
Remark field.String // 备注
fieldMap map[string]field.Expr
}

View File

@@ -12,7 +12,7 @@ func ApplyRouters(app *fiber.App) {
// 认证
auth := api.Group("/auth")
auth.Post("/verify/sms", auth2.PermitDevice(), handlers.SmsCode)
auth.Post("/verify/sms", handlers.SmsCode)
auth.Post("/login/sms", auth2.PermitDevice(), handlers.Login)
auth.Post("/logout", handlers.Logout)
auth.Post("/token", handlers.Token)

View File

@@ -85,10 +85,19 @@ func (s *authService) OauthRefreshToken(ctx context.Context, client *models.Clie
return details, nil
}
type GrantType int
type OauthGrantType string
const (
GrantTypeAuthorizationCode GrantType = iota
GrantTypeClientCredentials
GrantTypeRefreshToken
OauthGrantTypeAuthorizationCode = OauthGrantType("authorization_code")
OauthGrantTypeClientCredentials = OauthGrantType("client_credentials")
OauthGrantTypeRefreshToken = OauthGrantType("refresh_token")
OauthGrantTypePassword = OauthGrantType("password")
)
type OauthGrantLoginType string
const (
OauthGrantPasswordTypePassword = OauthGrantLoginType("password")
OauthGrantPasswordTypePhoneCode = OauthGrantLoginType("phone_code")
OauthGrantPasswordTypeEmailCode = OauthGrantLoginType("email_code")
)

View File

@@ -239,6 +239,7 @@ func mergeConfig(defaultCfg SessionConfig, customCfg SessionConfig) SessionConfi
// AuthContext 定义认证信息
type AuthContext struct {
Payload Payload `json:"payload"`
Agent Agent `json:"agent,omitempty"`
Permissions map[string]struct{} `json:"permissions,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@@ -265,6 +266,11 @@ const (
PayloadClientConfidential
)
type Agent struct {
Id int32 `json:"id,omitempty"`
Addr string `json:"addr,omitempty"`
}
// AnyPermission 检查认证是否包含指定权限
func (a *AuthContext) AnyPermission(requiredPermission ...string) bool {
if a == nil || a.Permissions == nil {