Skip to content

Commit

Permalink
Merge pull request #159 from nbtca/client-with-logto
Browse files Browse the repository at this point in the history
feat: support create client using logto
  • Loading branch information
wen-templari authored Feb 16, 2025
2 parents ecd8b69 + d00cd92 commit e3d6458
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 20 deletions.
82 changes: 67 additions & 15 deletions middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package middleware
import (
"net/http"
"slices"
"strings"

"github.com/nbtca/saturday/service"
"github.com/nbtca/saturday/util"
Expand All @@ -17,7 +18,12 @@ const (
Admin Role = "admin"
)

func Auth(role ...Role) func(c *gin.Context) {
type AuthContextUser struct {
UserInfo service.FetchUserInfoResponse
Role []string
}

func Auth(acceptableRoles ...Role) func(c *gin.Context) {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
Expand All @@ -26,34 +32,80 @@ func Auth(role ...Role) func(c *gin.Context) {
Build())
return
}
// handel legacy jwt token
// this is currently used by wechat mini app
if len(strings.Split(token, ".")) > 1 {
tokenParsed, claims, err := util.ParseToken(token)
if err != nil || !tokenParsed.Valid {
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("not authorized").
Build())
return
}
for _, roleObj := range acceptableRoles {
if string(roleObj) == claims.Role {
c.Set("id", claims.Who)
c.Set("member", claims.Member)
c.Set("role", claims.Role)
return
}
}
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("not authorized").
Build())
return
}

// strip bearer
token = token[7:]
token, err := util.GetTokenString(token)
if err != nil {
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("invalid token type").
Build())
return
}
userinfo, err := service.LogtoServiceApp.FetchUserInfo(token)
if err != nil {
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("not authorized").
Build())
return
}
// TODO this is used for backward compatibility, will only use userRoles in the future
var role string
userRoles := []string{"client"}
if slices.Contains(userinfo.Roles, "Repair Admin") {
userRoles = append(userRoles, "admin")
role = "admin"
} else if slices.Contains(userinfo.Roles, "Repair Member") {
role = "member"
}
if role != "" {
member, err := service.MemberServiceApp.GetMemberByLogtoId(userinfo.Sub)
if err != nil {
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("not authorized").
Build())
if slices.Contains(userinfo.Roles, "Repair Member") {
userRoles = append(userRoles, "member")
if role == "" {
role = "member"
}
}
for _, r := range acceptableRoles {
if slices.Contains(userRoles, string(r)) {
member, err := service.MemberServiceApp.GetMemberByLogtoId(userinfo.Sub)
if err != nil {
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("not authorized").
Build())
}
user := AuthContextUser{
Role: userRoles,
UserInfo: userinfo,
}
c.Set("id", member.MemberId)
c.Set("member", member)
c.Set("role", role)
c.Set("user", user)
return
}
c.Set("id", member.MemberId)
c.Set("member", member)
c.Set("role", role)
c.Set("user", userinfo)
return
}
c.AbortWithStatusJSON(util.MakeServiceError(http.StatusUnauthorized).
SetMessage("not authorized").
Build())
}
}

Expand Down
2 changes: 2 additions & 0 deletions migrations/000002_add_logto_id_to_client.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE public.client
DROP COLUMN logto_id;
2 changes: 2 additions & 0 deletions migrations/000002_add_logto_id_to_client.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE public.client
ADD COLUMN logto_id character varying(50) DEFAULT ''::character varying;
1 change: 1 addition & 0 deletions model/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package model
type Client struct {
ClientId int64 `json:"clientId" db:"client_id"`
OpenId string `json:"openid"`
LogtoId string `json:"logtoId" db:"logto_id"`
GmtCreate string `json:"gmtCreate" db:"gmt_create"`
GmtModified string `json:"gmtModified" db:"gmt_modified"`
}
19 changes: 16 additions & 3 deletions repo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,26 @@ func GetClientByOpenId(openId string) (model.Client, error) {
return client, nil
}

func GetClientByLogtoId(logtoId string) (model.Client, error) {
statement, args, _ := sq.Select("*").From("client").Where(squirrel.Eq{"logto_id": logtoId}).ToSql()
client := model.Client{}
if err := db.Get(&client, statement, args...); err != nil {
if err == sql.ErrNoRows {
return model.Client{}, nil
}
return model.Client{}, nil
}
return client, nil
}


func CreateClient(client *model.Client) error {
client.GmtCreate = util.GetDate()
client.GmtModified = util.GetDate()
sql, args, _ := sq.Insert("client").Columns("openid", "gmt_create", "gmt_modified").
Values(client.OpenId, time.Now(), time.Now()).ToSql()
sql, args, _ := sq.Insert("client").Columns("openid", "logto_id", "gmt_create", "gmt_modified").
Values(client.OpenId, client.LogtoId, time.Now(), time.Now()).ToSql()
var id int64
err := db.QueryRow(sql+"RETURNING id", args...).Scan(&id)
err := db.QueryRow(sql+"RETURNING client_id", args...).Scan(&id)
if err != nil {
return err
}
Expand Down
40 changes: 39 additions & 1 deletion router/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"

"github.com/danielgtaylor/huma/v2"
"github.com/gin-gonic/gin"
"github.com/nbtca/saturday/middleware"
"github.com/nbtca/saturday/model"
"github.com/nbtca/saturday/model/dto"
"github.com/nbtca/saturday/service"
Expand Down Expand Up @@ -31,7 +33,7 @@ func (ClientRouter) CreateTokenViaWeChat(c context.Context, input *struct {
return nil, huma.Error422UnprocessableEntity(err.Error())
}
}
token, err := service.ClientServiceApp.CreateTokenViaWechat(client)
token, err := service.ClientServiceApp.CreateClientToken(client)
if err != nil {
return nil, huma.Error422UnprocessableEntity(err.Error())
}
Expand All @@ -41,4 +43,40 @@ func (ClientRouter) CreateTokenViaWeChat(c context.Context, input *struct {
}), nil
}

func (ClientRouter) CreateTokenViaLogto(c *gin.Context) {
user := c.Value("user").(middleware.AuthContextUser)
response := dto.ClientTokenResponse{}
logtoId := user.UserInfo.Sub
if logtoId == "" {
c.Error(huma.Error422UnprocessableEntity("user not found"))
// return nil, huma.Error422UnprocessableEntity("user not found")
}
client, err := service.ClientServiceApp.GetClientByLogtoId(logtoId)
if err != nil {
c.Error(huma.Error422UnprocessableEntity(err.Error()))
// return nil, huma.Error422UnprocessableEntity(err.Error())
}
if client == (model.Client{}) {
client, err = service.ClientServiceApp.CreateClientByLogtoId(logtoId)
if err != nil {
// return nil, huma.Error422UnprocessableEntity(err.Error())
c.Error(huma.Error422UnprocessableEntity(err.Error()))
}
}
token, err := service.ClientServiceApp.CreateClientToken(client)
if err != nil {
// return nil, huma.Error422UnprocessableEntity(err.Error())
c.Error(huma.Error422UnprocessableEntity(err.Error()))
}
response = dto.ClientTokenResponse{
Token: token,
Client: client,
}
c.JSON(200, response)
// return util.MakeCommonResponse(dto.ClientTokenResponse{
// Token: token,
// Client: client,
// }), nil
}

var ClientRouterApp = ClientRouter{}
2 changes: 2 additions & 0 deletions router/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func SetupRouter() *gin.Engine {
Tags: []string{"Client", "Public"},
}, ClientRouterApp.CreateTokenViaWeChat)

Router.POST("/clients/token/logto", middleware.Auth("client"), ClientRouterApp.CreateTokenViaLogto)

huma.Register(api, huma.Operation{
OperationID: "get-public-event-by-id",
Method: http.MethodGet,
Expand Down
19 changes: 18 additions & 1 deletion service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
type ClientService struct {
}

func (service ClientService) CreateTokenViaWechat(client model.Client) (string, error) {
func (service ClientService) CreateClientToken(client model.Client) (string, error) {
res, err := util.CreateToken(util.Payload{Who: fmt.Sprint(client.ClientId), Role: "client"})
return res, err
}
Expand All @@ -24,6 +24,14 @@ func (service ClientService) GetClientByOpenId(openId string) (model.Client, err
return client, nil
}

func (service ClientService) GetClientByLogtoId(logtoId string) (model.Client, error) {
client, err := repo.GetClientByLogtoId(logtoId)
if err != nil {
return model.Client{}, err
}
return client, nil
}

func (service ClientService) CreateClientByOpenId(openId string) (model.Client, error) {
client := model.Client{OpenId: openId}
err := repo.CreateClient(&client)
Expand All @@ -33,4 +41,13 @@ func (service ClientService) CreateClientByOpenId(openId string) (model.Client,
return client, nil
}

func (service ClientService) CreateClientByLogtoId(logtoId string) (model.Client, error) {
client := model.Client{LogtoId: logtoId}
err := repo.CreateClient(&client)
if err != nil {
return model.Client{}, err
}
return client, nil
}

var ClientServiceApp = ClientService{}

0 comments on commit e3d6458

Please # to comment.