Skip to content

Commit

Permalink
feat: support wechat browser
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Sep 20, 2023
1 parent 5a7d049 commit e9ad642
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 13 deletions.
1 change: 1 addition & 0 deletions config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ name_prefix = "YW"
domain = "yiwen.ltd"
secure = false
expires_in = 2592000 # 60*60*24*30 seconds
wechat_domain = ""

[auth_url]
default_host = "www.yiwen.ltd"
Expand Down
118 changes: 109 additions & 9 deletions src/api/authn.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"github.com/yiwen-ai/auth-api/src/util"
)

const Wechat_UA = "MicroMessenger/"

type AuthN struct {
blls *bll.Blls
providers map[string]*oauth2.Config
Expand Down Expand Up @@ -79,8 +81,26 @@ func NewAuth(blls *bll.Blls, cfg *conf.ConfigTpl) *AuthN {
return authn
}

func (a *AuthN) isInWechat(ctx *gear.Context) bool {
return a.cookie.WeChatDomain != "" && strings.Contains(ctx.GetHeader(gear.HeaderUserAgent), Wechat_UA)
}

func (a *AuthN) Login(ctx *gear.Context) error {
idp := ctx.Param("idp")

if a.isInWechat(ctx) {
if idp == "wechat" || !strings.HasSuffix(ctx.Host, a.cookie.WeChatDomain) {
reqUrl := util.Ptr(*ctx.Req.URL)
reqUrl.Scheme = "https"
reqUrl.Host = strings.Replace(ctx.Host, a.cookie.Domain, a.cookie.WeChatDomain, 1)
if idp == "wechat" {
reqUrl.Path = "/idp/wechat_h5/authorize"
}
reqUrl.RawQuery = strings.Replace(ctx.Req.URL.RawQuery, a.cookie.Domain, a.cookie.WeChatDomain, 1)
return ctx.Redirect(reqUrl.String())
}
}

xid := ctx.GetHeader(gear.HeaderXRequestID)

nextURL, ok := a.authURL.CheckNextUrl(ctx.Query("next_url"))
Expand All @@ -105,6 +125,9 @@ func (a *AuthN) Login(ctx *gear.Context) error {
}

url := a.getAuthCodeURL(idp, state)
if a.isInWechat(ctx) {
url = strings.Replace(url, a.cookie.Domain, a.cookie.WeChatDomain, 1)
}
return ctx.Redirect(url)
}

Expand Down Expand Up @@ -225,14 +248,20 @@ func (a *AuthN) Callback(ctx *gear.Context) error {
})
}

isInWechat := a.isInWechat(ctx)
domain := a.cookie.Domain
if isInWechat {
domain = a.cookie.WeChatDomain
}

didCookie := &http.Cookie{
Name: didCookieName,
Value: res.SID.String(),
HttpOnly: true,
Secure: a.cookie.Secure,
MaxAge: 3600 * 24 * 366,
Path: "/",
Domain: a.cookie.Domain,
Domain: domain,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(ctx.Res, didCookie)
Expand All @@ -244,16 +273,87 @@ func (a *AuthN) Callback(ctx *gear.Context) error {
Secure: a.cookie.Secure,
MaxAge: int(a.cookie.ExpiresIn),
Path: "/",
Domain: a.cookie.Domain,
Domain: domain,
SameSite: http.SameSiteLaxMode,
}

http.SetCookie(ctx.Res, sessCookie)
next := a.authURL.GenNextUrl(nextURL, 200, "")
if isInWechat {
next = strings.Replace(next, a.cookie.Domain, a.cookie.WeChatDomain, 2)
obj := &cose.Mac0Message[key.IntMap]{
Unprotected: cose.Headers{},
Payload: key.IntMap{
0: time.Now().Add(20 * time.Second).Unix(),
1: res.SID.String(),
2: res.Session,
3: next,
},
}

if err := obj.Compute(a.stateMACer, nil); err == nil {
if data, err := cbor.Marshal(obj); err == nil {
reqUrl := &url.URL{
Scheme: "https",
Host: strings.Replace(ctx.Host, a.cookie.WeChatDomain, a.cookie.Domain, 1),
Path: "/sync_session",
RawQuery: "sess=" + base64.RawURLEncoding.EncodeToString(data),
}
return ctx.Redirect(reqUrl.String())
}
}
}

return ctx.Redirect(next)
}

func (a *AuthN) SyncSession(ctx *gear.Context) error {
data, err := base64.RawURLEncoding.DecodeString(ctx.Query("sess"))
if err != nil {
return gear.ErrBadRequest.WithMsgf("invalid sess: %v", err)
}

obj := &cose.Mac0Message[key.IntMap]{}
if err = cbor.Unmarshal(data, obj); err != nil {
return gear.ErrBadRequest.WithMsgf("invalid sess: %v", err)
}
if err = obj.Verify(a.stateMACer, nil); err != nil {
return gear.ErrBadRequest.WithMsgf("invalid sess: %v", err)
}
if v, _ := obj.Payload.GetInt64(0); v < time.Now().Unix() {
return gear.ErrBadRequest.WithMsg("expired sess")
}
sid, _ := obj.Payload.GetString(1)
sess, _ := obj.Payload.GetString(2)
next, _ := obj.Payload.GetString(3)

didCookie := &http.Cookie{
Name: a.cookie.NamePrefix + "_DID",
Value: sid,
HttpOnly: true,
Secure: a.cookie.Secure,
MaxAge: 3600 * 24 * 366,
Path: "/",
Domain: a.cookie.Domain,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(ctx.Res, didCookie)

sessCookie := &http.Cookie{
Name: a.cookie.NamePrefix + "_SESS",
Value: sess,
HttpOnly: true,
Secure: a.cookie.Secure,
MaxAge: int(a.cookie.ExpiresIn),
Path: "/",
Domain: a.cookie.Domain,
SameSite: http.SameSiteLaxMode,
}

http.SetCookie(ctx.Res, sessCookie)
return ctx.Redirect(next)
}

func (a *AuthN) giveAward(gctx context.Context, uid util.ID, referrer string) {
conf.Config.ObtainJob()
defer conf.Config.ReleaseJob()
Expand Down Expand Up @@ -397,9 +497,9 @@ func (a *AuthN) createState(idp, client_id, next_url string) (string, error) {
obj := &cose.Mac0Message[key.IntMap]{
Unprotected: cose.Headers{},
Payload: key.IntMap{
0: idp,
1: time.Now().Add(5 * time.Minute).Unix(),
2: conf.Config.Rand.Uint32(),
0: time.Now().Add(5 * time.Minute).Unix(),
1: conf.Config.Rand.Uint32(),
2: idp,
3: next_url,
},
}
Expand Down Expand Up @@ -428,12 +528,12 @@ func (a *AuthN) verifyState(idp, client_id, state string) (*url.URL, error) {
if err = obj.Verify(a.stateMACer, []byte(client_id)); err != nil {
return nil, err
}
if v, _ := obj.Payload.GetString(0); v != idp {
return nil, fmt.Errorf("invalid state for provider %q", idp)
}
if v, _ := obj.Payload.GetInt64(1); v < time.Now().Unix() {
if v, _ := obj.Payload.GetInt64(0); v < time.Now().Unix() {
return nil, fmt.Errorf("expired state")
}
if v, _ := obj.Payload.GetString(2); v != idp {
return nil, fmt.Errorf("invalid state for provider %q", idp)
}

next_url, _ := obj.Payload.GetString(3)
return url.Parse(next_url)
Expand Down
1 change: 1 addition & 0 deletions src/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func newRouters(apis *APIs) []*gear.Router {
router.Get("/access_token", apis.Session.AccessToken)
router.Get("/userinfo", apis.Session.Verify, apis.Session.UserInfo)
router.Post("/logout", apis.Session.Verify, apis.Session.Logout)
router.Get("/sync_session", apis.AuthN.SyncSession)

router.Get("/idp/:idp/authorize", apis.AuthN.Login)
router.Get("/idp/:idp/callback", apis.AuthN.Callback)
Expand Down
9 changes: 5 additions & 4 deletions src/conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ type Server struct {
}

type Cookie struct {
NamePrefix string `json:"name_prefix" toml:"name_prefix"`
Domain string `json:"domain" toml:"domain"`
Secure bool `json:"secure" toml:"secure"`
ExpiresIn uint `json:"expires_in" toml:"expires_in"`
NamePrefix string `json:"name_prefix" toml:"name_prefix"`
Domain string `json:"domain" toml:"domain"`
Secure bool `json:"secure" toml:"secure"`
ExpiresIn uint `json:"expires_in" toml:"expires_in"`
WeChatDomain string `json:"wechat_domain" toml:"wechat_domain"`
}

type AuthURL struct {
Expand Down

0 comments on commit e9ad642

Please sign in to comment.