2021-11-10 01:44:51 +08:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
2021-11-10 02:33:37 +08:00
|
|
|
"github.com/pkg/errors"
|
2021-11-10 01:44:51 +08:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
|
|
mpauth "git.esin.io/lab/weixin/clientapi/mp/auth"
|
|
|
|
"git.esin.io/lab/weixin/pkg/pubsub"
|
|
|
|
pb "git.esin.io/lab/weixin/protobuf/clientapi/mp/auth"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Config struct {
|
|
|
|
WeixinAppID string
|
|
|
|
WeiXinAppSecret string
|
|
|
|
DB *gorm.DB
|
|
|
|
Publisher pubsub.Publisher
|
|
|
|
}
|
|
|
|
|
|
|
|
type Service struct {
|
|
|
|
pb.UnimplementedAuthServiceServer
|
|
|
|
client *mpauth.Client
|
|
|
|
db *gorm.DB
|
|
|
|
publisher pubsub.Publisher
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewServiceServer(cfg *Config) pb.AuthServiceServer {
|
|
|
|
return &Service{
|
|
|
|
db: cfg.DB,
|
|
|
|
client: mpauth.NewClient(&mpauth.Config{
|
|
|
|
ClientID: cfg.WeixinAppID,
|
|
|
|
ClientSecret: cfg.WeiXinAppSecret,
|
|
|
|
}),
|
|
|
|
publisher: cfg.Publisher,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func RegisterAuthServiceServer(s *grpc.Server, srv pb.AuthServiceServer) {
|
|
|
|
pb.RegisterAuthServiceServer(s, srv)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) PublishEvent(ctx context.Context, subject string, message interface{}) error {
|
|
|
|
if srv.publisher != nil {
|
|
|
|
return srv.publisher.Publish(ctx, subject, message)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) GetAuthCodeURL(ctx context.Context, req *pb.GetCodeURLRequest) (*pb.GetCodeURLResponse, error) {
|
|
|
|
resp := srv.client.GetCodeURL(req.RedirectUrl, req.State, mpauth.Scope(req.Scope.String()))
|
|
|
|
|
|
|
|
return &pb.GetCodeURLResponse{
|
|
|
|
Url: resp,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) ExchangeToken(ctx context.Context, req *pb.ExchangeTokenRequest) (*pb.ExchangeTokenResponse, error) {
|
|
|
|
resp, err := srv.client.ExchangeToken(ctx, req.Code)
|
|
|
|
if err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "exchange token from weixin failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var token Token
|
|
|
|
if err := srv.db.Last(&token, "open_id = ?", resp.OpenID).Error; err != nil {
|
|
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
token = Token{Token: *resp}
|
|
|
|
}
|
|
|
|
token.Assign(resp)
|
|
|
|
if err := srv.db.Save(&token).Error; err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "save token to database failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
go srv.PublishEvent(ctx, "auth.token.exchanged", &token)
|
|
|
|
|
|
|
|
return &pb.ExchangeTokenResponse{
|
|
|
|
Token: token.Proto(),
|
|
|
|
Timestamp: timestamppb.New(token.CreatedAt),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) RefreshToken(ctx context.Context, req *pb.RefreshTokenRequest) (*pb.RefreshTokenResponse, error) {
|
|
|
|
|
|
|
|
var token Token
|
|
|
|
if err := srv.db.Last(&token, "open_id = ?", req.OpenId).Error; err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := srv.client.RefreshToken(ctx, token.RefreshToken)
|
|
|
|
if err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "refresh refreshed token from weixin failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
token.Assign(resp)
|
|
|
|
|
|
|
|
if err := srv.db.Save(&token).Error; err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "save refreshed token to database failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
go srv.PublishEvent(ctx, "auth.token.refreshed", &token)
|
|
|
|
|
|
|
|
return &pb.RefreshTokenResponse{
|
|
|
|
Token: token.Proto(),
|
|
|
|
Timestamp: timestamppb.New(token.CreatedAt),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) GetUserinfo(ctx context.Context, req *pb.GetUserinfoRequest) (*pb.GetUserinfoResponse, error) {
|
|
|
|
var token Token
|
|
|
|
if err := srv.db.Last(&token, "open_id = ?", req.OpenId).Error; err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
var userinfo Userinfo
|
|
|
|
if err := srv.db.Last(&userinfo, "open_id = ?", req.OpenId).Error; err != nil {
|
|
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := srv.client.GetUserinfo(ctx, token.AccessToken, req.OpenId, mpauth.Lang(req.Lang.String()))
|
|
|
|
if err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "get userinfo token from weixin failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
userinfo = Userinfo{Userinfo: *resp}
|
|
|
|
if err := srv.db.Create(&userinfo).Error; err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "save userinfo to database failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
go srv.PublishEvent(ctx, "auth.userinfo.created", &userinfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.GetUserinfoResponse{
|
|
|
|
Userinfo: userinfo.Proto(),
|
|
|
|
CreatedTime: timestamppb.New(userinfo.CreatedAt),
|
|
|
|
UpdatedTime: timestamppb.New(userinfo.UpdatedAt),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) SyncUserinfo(ctx context.Context, req *pb.SyncUserinfoRequest) (*pb.SyncUserinfoResponse, error) {
|
|
|
|
var token Token
|
|
|
|
if err := srv.db.Last(&token, "open_id = ?", req.OpenId).Error; err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
var userinfo Userinfo
|
|
|
|
if err := srv.db.Last(&userinfo, "open_id = ?", req.OpenId).Error; err != nil {
|
|
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := srv.client.GetUserinfo(ctx, token.AccessToken, req.OpenId, mpauth.Lang(req.Lang.String()))
|
|
|
|
if err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "get userinfo token from weixin failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
userinfo = Userinfo{Userinfo: *resp}
|
|
|
|
if err := srv.db.Save(&userinfo).Error; err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "sync userinfo and save to database failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
go srv.PublishEvent(ctx, "auth.userinfo.synchronized", &userinfo)
|
|
|
|
|
|
|
|
return &pb.SyncUserinfoResponse{
|
|
|
|
Userinfo: userinfo.Proto(),
|
|
|
|
CreatedTime: timestamppb.New(userinfo.CreatedAt),
|
|
|
|
UpdatedTime: timestamppb.New(userinfo.UpdatedAt),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (srv Service) GetClientCredential(ctx context.Context, _ *emptypb.Empty) (*pb.GetClientCredentialResponse, error) {
|
|
|
|
var cred ClientCredential
|
|
|
|
err := srv.db.Last(&cred).Error
|
|
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// get new cred when cred not found or cred expired
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) || cred.Expired() {
|
|
|
|
resp, err := srv.client.GetClientCredential(ctx)
|
|
|
|
if err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "get client credential token from weixin failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
cred = ClientCredential{
|
|
|
|
ClientCredential: *resp,
|
|
|
|
}
|
|
|
|
if err := srv.db.Create(&cred).Error; err != nil {
|
2021-11-10 02:33:37 +08:00
|
|
|
return nil, status.Errorf(codes.Internal, errors.Wrap(err, "save client credential to database failed").Error())
|
2021-11-10 01:44:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
go srv.PublishEvent(ctx, "auth.clientcredential.created", &cred)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.GetClientCredentialResponse{
|
|
|
|
ClientCredential: cred.Proto(),
|
|
|
|
Timestamp: timestamppb.New(cred.CreatedAt),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type Token struct {
|
|
|
|
gorm.Model
|
|
|
|
mpauth.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
func (Token) TableName() string {
|
|
|
|
return "auth_token"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (token Token) Expired() bool {
|
|
|
|
expTime := token.UpdatedAt.Add(time.Second * time.Duration(token.ExpiresIn))
|
|
|
|
return expTime.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (token Token) Proto() *pb.Token {
|
|
|
|
return &pb.Token{
|
|
|
|
AccessToken: token.AccessToken,
|
|
|
|
ExpiresIn: token.ExpiresIn,
|
|
|
|
RefreshToken: token.RefreshToken,
|
|
|
|
Scope: token.Scope,
|
|
|
|
OpenId: token.OpenID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (token *Token) Assign(t *mpauth.Token) {
|
|
|
|
token.Token = *t
|
|
|
|
}
|
|
|
|
|
|
|
|
type Userinfo struct {
|
|
|
|
gorm.Model
|
|
|
|
mpauth.Userinfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func (Userinfo) TableName() string {
|
|
|
|
return "auth_userinfo"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (userinfo Userinfo) Proto() *pb.Userinfo {
|
|
|
|
return &pb.Userinfo{
|
|
|
|
OpenId: userinfo.OpenID,
|
|
|
|
NickName: userinfo.NickName,
|
|
|
|
Sex: userinfo.Sex,
|
|
|
|
Province: userinfo.Province,
|
|
|
|
City: userinfo.City,
|
|
|
|
Country: userinfo.Country,
|
|
|
|
HeadImgUrl: userinfo.HeadImgURL,
|
|
|
|
Privilege: userinfo.Privilege,
|
|
|
|
UnionId: userinfo.UnionID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ClientCredential struct {
|
|
|
|
gorm.Model
|
|
|
|
mpauth.ClientCredential
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cred ClientCredential) Expired() bool {
|
|
|
|
expTime := cred.UpdatedAt.Add(time.Second * time.Duration(cred.ExpiresIn))
|
|
|
|
return expTime.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cred ClientCredential) Proto() *pb.ClientCredential {
|
|
|
|
return &pb.ClientCredential{
|
|
|
|
AccessToken: cred.AccessToken,
|
|
|
|
ExpiresIn: cred.ExpiresIn,
|
|
|
|
}
|
|
|
|
}
|