diff --git a/.env b/.env index 9a8241785f6c65304c0c1153eb06bc08ad589324..d87d58d73e83b76b04ea267dea45faccc5bc0326 100644 --- a/.env +++ b/.env @@ -1,12 +1,41 @@ +# IP address to listen to SERVER=0.0.0.0 +# port to bind PORT=8080 +# Gin framework release mode GIN_MODE=release - +# where to write all generated config files WG_CONF_DIR=./wireguard +# WireGuard main config file name, generally <interface name>.conf WG_INTERFACE_NAME=wg0.conf +# SMTP settings to send email to clients SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USERNAME=account@gmail.com -SMTP_PASSWORD="*************" -SMTP_FROM="Wg Gen Web <account@gmail.com>" +SMTP_PASSWORD=************* +SMTP_FROM=Wg Gen Web <account@gmail.com> + +# example with gitlab, which is RFC implementation and no need any custom stuff +OAUTH2_PROVIDER_NAME=oauth2oidc +OAUTH2_PROVIDER=https://gitlab.com +OAUTH2_CLIENT_ID= +OAUTH2_CLIENT_SECRET= +OAUTH2_REDIRECT_URL=https://wg-gen-web-demo.127-0-0-1.fr + +# example with google +OAUTH2_PROVIDER_NAME=google +OAUTH2_PROVIDER= +OAUTH2_CLIENT_ID= +OAUTH2_CLIENT_SECRET= +OAUTH2_REDIRECT_URL= + +# example with github +OAUTH2_PROVIDER_NAME=github +OAUTH2_PROVIDER= +OAUTH2_CLIENT_ID= +OAUTH2_CLIENT_SECRET= +OAUTH2_REDIRECT_URL= + +# set provider name to fake to disable auth, also the default +OAUTH2_PROVIDER_NAME=fake diff --git a/api/api.go b/api/api.go index c1bba54bfa3ec169a15dcee1995af968ea758768..8063cecafb7cdf39235a16dbc5bc1d251be7e221 100644 --- a/api/api.go +++ b/api/api.go @@ -2,244 +2,12 @@ package api import ( "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" - "github.com/skip2/go-qrcode" - "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core" - "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" - "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template" - "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version" - "net/http" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1" ) -// ApplyRoutes applies router to gin Router -func ApplyRoutes(r *gin.Engine) { - client := r.Group("/api/v1.0/client") +func ApplyRoutes(r *gin.Engine, private bool) { + api := r.Group("/api") { - - client.POST("", createClient) - client.GET("/:id", readClient) - client.PATCH("/:id", updateClient) - client.DELETE("/:id", deleteClient) - client.GET("", readClients) - client.GET("/:id/config", configClient) - client.GET("/:id/email", emailClient) - } - - server := r.Group("/api/v1.0/server") - { - server.GET("", readServer) - server.PATCH("", updateServer) - server.GET("/config", configServer) - server.GET("/version", versionStr) - } -} - -func createClient(c *gin.Context) { - var data model.Client - - if err := c.ShouldBindJSON(&data); err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - c.AbortWithStatus(http.StatusUnprocessableEntity) - return - } - - client, err := core.CreateClient(&data) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to create client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, client) -} - -func readClient(c *gin.Context) { - id := c.Param("id") - - client, err := core.ReadClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, client) -} - -func updateClient(c *gin.Context) { - var data model.Client - id := c.Param("id") - - if err := c.ShouldBindJSON(&data); err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - c.AbortWithStatus(http.StatusUnprocessableEntity) - return - } - - client, err := core.UpdateClient(id, &data) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to update client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, client) -} - -func deleteClient(c *gin.Context) { - id := c.Param("id") - - err := core.DeleteClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to remove client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, gin.H{}) -} - -func readClients(c *gin.Context) { - clients, err := core.ReadClients() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to list clients") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, clients) -} - -func configClient(c *gin.Context) { - configData, err := core.ReadClientConfig(c.Param("id")) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read client config") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - formatQr := c.DefaultQuery("qrcode", "false") - if formatQr == "false" { - // return config as txt file - c.Header("Content-Disposition", "attachment; filename=wg0.conf") - c.Data(http.StatusOK, "application/config", configData) - return - } - // return config as png qrcode - png, err := qrcode.Encode(string(configData), qrcode.Medium, 250) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to create qrcode") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - c.Data(http.StatusOK, "image/png", png) - return -} - -func emailClient(c *gin.Context) { - id := c.Param("id") - - err := core.EmailClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to send email to client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, gin.H{}) -} - -func readServer(c *gin.Context) { - client, err := core.ReadServer() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, client) -} - -func updateServer(c *gin.Context) { - var data model.Server - - if err := c.ShouldBindJSON(&data); err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - c.AbortWithStatus(http.StatusUnprocessableEntity) - return - } - - client, err := core.UpdateServer(&data) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to update client") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, client) -} - -func configServer(c *gin.Context) { - clients, err := core.ReadClients() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read clients") - c.AbortWithStatus(http.StatusUnprocessableEntity) - return - } - - server, err := core.ReadServer() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read server") - c.AbortWithStatus(http.StatusUnprocessableEntity) - return - } - - configData, err := template.DumpServerWg(clients, server) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to dump wg config") - c.AbortWithStatus(http.StatusUnprocessableEntity) - return + apiv1.ApplyRoutes(api, private) } - - // return config as txt file - c.Header("Content-Disposition", "attachment; filename=wg0.conf") - c.Data(http.StatusOK, "application/config", configData) -} - -func versionStr(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "version": version.Version, - }) } diff --git a/api/v1/auth/auth.go b/api/v1/auth/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..66fcd0a954de39aacb9c0a1c08c1ecc3c840c47f --- /dev/null +++ b/api/v1/auth/auth.go @@ -0,0 +1,134 @@ +package auth + +import ( + "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" + log "github.com/sirupsen/logrus" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util" + "golang.org/x/oauth2" + "net/http" + "time" +) + +// ApplyRoutes applies router to gin Router +func ApplyRoutes(r *gin.RouterGroup) { + g := r.Group("/auth") + { + g.GET("/oauth2_url", oauth2_url) + g.POST("/oauth2_exchange", oauth2_exchange) + g.GET("/user", user) + g.GET("/logout", logout) + } +} + +/* + * generate redirect url to get OAuth2 code or let client know that OAuth2 is disabled + */ +func oauth2_url(c *gin.Context) { + cacheDb := c.MustGet("cache").(*cache.Cache) + + state, err := util.GenerateRandomString(32) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to generate state random string") + c.AbortWithStatus(http.StatusInternalServerError) + } + + clientId, err := util.GenerateRandomString(32) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to generate state random string") + c.AbortWithStatus(http.StatusInternalServerError) + } + // save clientId and state so we can retrieve for verification + cacheDb.Set(clientId, state, 5*time.Minute) + + oauth2Client := c.MustGet("oauth2Client").(auth.Auth) + + data := &model.Auth{ + Oauth2: true, + ClientId: clientId, + State: state, + CodeUrl: oauth2Client.CodeUrl(state), + } + + c.JSON(http.StatusOK, data) +} + +/* + * exchange code and get user infos, if OAuth2 is disable just send fake data + */ +func oauth2_exchange(c *gin.Context) { + var loginVals model.Auth + if err := c.ShouldBind(&loginVals); err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("code and state fields are missing") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + cacheDb := c.MustGet("cache").(*cache.Cache) + savedState, exists := cacheDb.Get(loginVals.ClientId) + + if !exists || savedState != loginVals.State { + log.WithFields(log.Fields{ + "state": loginVals.State, + "savedState": savedState, + }).Error("saved state and client provided state mismatch") + c.AbortWithStatus(http.StatusBadRequest) + return + } + oauth2Client := c.MustGet("oauth2Client").(auth.Auth) + + oauth2Token, err := oauth2Client.Exchange(loginVals.Code) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to exchange code for token") + c.AbortWithStatus(http.StatusBadRequest) + return + } + + cacheDb.Delete(loginVals.ClientId) + cacheDb.Set(oauth2Token.AccessToken, oauth2Token, cache.DefaultExpiration) + + c.JSON(http.StatusOK, oauth2Token.AccessToken) +} + +func logout(c *gin.Context) { + cacheDb := c.MustGet("cache").(*cache.Cache) + cacheDb.Delete(c.Request.Header.Get(util.AuthTokenHeaderName)) + c.JSON(http.StatusOK, gin.H{}) +} + +func user(c *gin.Context) { + cacheDb := c.MustGet("cache").(*cache.Cache) + oauth2Token, exists := cacheDb.Get(c.Request.Header.Get(util.AuthTokenHeaderName)) + + if exists && oauth2Token.(*oauth2.Token).AccessToken == c.Request.Header.Get(util.AuthTokenHeaderName) { + oauth2Client := c.MustGet("oauth2Client").(auth.Auth) + user, err := oauth2Client.UserInfo(oauth2Token.(*oauth2.Token)) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to get user from oauth2 AccessToken") + c.AbortWithStatus(http.StatusBadRequest) + return + } + + c.JSON(http.StatusOK, user) + return + } + + log.WithFields(log.Fields{ + "exists": exists, + util.AuthTokenHeaderName: c.Request.Header.Get(util.AuthTokenHeaderName), + }).Error("oauth2 AccessToken is not recognized") + + c.AbortWithStatus(http.StatusUnauthorized) +} diff --git a/api/v1/client/client.go b/api/v1/client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..13be7bad4e3634b658ce14ef0d2ba620b828dac1 --- /dev/null +++ b/api/v1/client/client.go @@ -0,0 +1,190 @@ +package client + +import ( + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "github.com/skip2/go-qrcode" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" + "golang.org/x/oauth2" + "net/http" +) + +// ApplyRoutes applies router to gin Router +func ApplyRoutes(r *gin.RouterGroup) { + g := r.Group("/client") + { + + g.POST("", createClient) + g.GET("/:id", readClient) + g.PATCH("/:id", updateClient) + g.DELETE("/:id", deleteClient) + g.GET("", readClients) + g.GET("/:id/config", configClient) + g.GET("/:id/email", emailClient) + } +} + +func createClient(c *gin.Context) { + var data model.Client + + if err := c.ShouldBindJSON(&data); err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to bind") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + // get creation user from token and add to client infos + oauth2Token := c.MustGet("oauth2Token").(*oauth2.Token) + oauth2Client := c.MustGet("oauth2Client").(auth.Auth) + user, err := oauth2Client.UserInfo(oauth2Token) + if err != nil { + log.WithFields(log.Fields{ + "oauth2Token": oauth2Token, + "err": err, + }).Error("failed to get user with oauth token") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + data.CreatedBy = user.Name + + client, err := core.CreateClient(&data) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to create client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, client) +} + +func readClient(c *gin.Context) { + id := c.Param("id") + + client, err := core.ReadClient(id) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to read client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, client) +} + +func updateClient(c *gin.Context) { + var data model.Client + id := c.Param("id") + + if err := c.ShouldBindJSON(&data); err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to bind") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + // get update user from token and add to client infos + oauth2Token := c.MustGet("oauth2Token").(*oauth2.Token) + oauth2Client := c.MustGet("oauth2Client").(auth.Auth) + user, err := oauth2Client.UserInfo(oauth2Token) + if err != nil { + log.WithFields(log.Fields{ + "oauth2Token": oauth2Token, + "err": err, + }).Error("failed to get user with oauth token") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + data.UpdatedBy = user.Name + + client, err := core.UpdateClient(id, &data) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to update client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, client) +} + +func deleteClient(c *gin.Context) { + id := c.Param("id") + + err := core.DeleteClient(id) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to remove client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, gin.H{}) +} + +func readClients(c *gin.Context) { + clients, err := core.ReadClients() + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to list clients") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, clients) +} + +func configClient(c *gin.Context) { + configData, err := core.ReadClientConfig(c.Param("id")) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to read client config") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + formatQr := c.DefaultQuery("qrcode", "false") + if formatQr == "false" { + // return config as txt file + c.Header("Content-Disposition", "attachment; filename=wg0.conf") + c.Data(http.StatusOK, "application/config", configData) + return + } + // return config as png qrcode + png, err := qrcode.Encode(string(configData), qrcode.Medium, 250) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to create qrcode") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + c.Data(http.StatusOK, "image/png", png) + return +} + +func emailClient(c *gin.Context) { + id := c.Param("id") + + err := core.EmailClient(id) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to send email to client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, gin.H{}) +} diff --git a/api/v1/server/server.go b/api/v1/server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..158deb91e11d625fe90c24f69b96790eb9c289a4 --- /dev/null +++ b/api/v1/server/server.go @@ -0,0 +1,113 @@ +package server + +import ( + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version" + "golang.org/x/oauth2" + "net/http" +) + +// ApplyRoutes applies router to gin Router +func ApplyRoutes(r *gin.RouterGroup) { + g := r.Group("/server") + { + g.GET("", readServer) + g.PATCH("", updateServer) + g.GET("/config", configServer) + g.GET("/version", versionStr) + } +} + +func readServer(c *gin.Context) { + client, err := core.ReadServer() + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to read client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, client) +} + +func updateServer(c *gin.Context) { + var data model.Server + + if err := c.ShouldBindJSON(&data); err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to bind") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + // get update user from token and add to server infos + oauth2Token := c.MustGet("oauth2Token").(*oauth2.Token) + oauth2Client := c.MustGet("oauth2Client").(auth.Auth) + user, err := oauth2Client.UserInfo(oauth2Token) + if err != nil { + log.WithFields(log.Fields{ + "oauth2Token": oauth2Token, + "err": err, + }).Error("failed to get user with oauth token") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + data.UpdatedBy = user.Name + + server, err := core.UpdateServer(&data) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to update client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, server) +} + +func configServer(c *gin.Context) { + clients, err := core.ReadClients() + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to read clients") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + server, err := core.ReadServer() + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to read server") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + configData, err := template.DumpServerWg(clients, server) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to dump wg config") + c.AbortWithStatus(http.StatusUnprocessableEntity) + return + } + + // return config as txt file + c.Header("Content-Disposition", "attachment; filename=wg0.conf") + c.Data(http.StatusOK, "application/config", configData) +} + +func versionStr(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "version": version.Version, + }) +} diff --git a/api/v1/v1.go b/api/v1/v1.go new file mode 100644 index 0000000000000000000000000000000000000000..ce1339f6e9a01ac2d1698a1767fc897038b61144 --- /dev/null +++ b/api/v1/v1.go @@ -0,0 +1,21 @@ +package apiv1 + +import ( + "github.com/gin-gonic/gin" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1/auth" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1/client" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1/server" +) + +func ApplyRoutes(r *gin.RouterGroup, private bool) { + v1 := r.Group("/v1.0") + { + if private { + client.ApplyRoutes(v1) + server.ApplyRoutes(v1) + } else { + auth.ApplyRoutes(v1) + + } + } +} diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..d4dadaed4a3e590dda33f0afcd4b50d10d7e0c06 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,13 @@ +package auth + +import ( + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" + "golang.org/x/oauth2" +) + +type Auth interface { + Setup() error + CodeUrl(state string) string + Exchange(code string) (*oauth2.Token, error) + UserInfo(oauth2Token *oauth2.Token) (*model.User, error) +} diff --git a/auth/fake/fake.go b/auth/fake/fake.go new file mode 100644 index 0000000000000000000000000000000000000000..0ebe8d8218ee53ecc39dd46926518294c5431570 --- /dev/null +++ b/auth/fake/fake.go @@ -0,0 +1,47 @@ +package fake + +import ( + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util" + "golang.org/x/oauth2" + "time" +) + +type Fake struct{} + +// Setup validate provider +func (o *Fake) Setup() error { + return nil +} + +// CodeUrl get url to redirect client for auth +func (o *Fake) CodeUrl(state string) string { + return "_magic_string_fake_auth_no_redirect_" +} + +// Exchange exchange code for Oauth2 token +func (o *Fake) Exchange(code string) (*oauth2.Token, error) { + rand, err := util.GenerateRandomString(32) + if err != nil { + return nil, err + } + + return &oauth2.Token{ + AccessToken: rand, + TokenType: "", + RefreshToken: "", + Expiry: time.Time{}, + }, nil +} + +// UserInfo get token user +func (o *Fake) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) { + return &model.User{ + Sub: "unknown", + Name: "Unknown", + Email: "unknown", + Profile: "unknown", + Issuer: "unknown", + IssuedAt: time.Time{}, + }, nil +} diff --git a/auth/github/github.go b/auth/github/github.go new file mode 100644 index 0000000000000000000000000000000000000000..d2e73c266e17fc179d15f61f271d04f46cf9bb11 --- /dev/null +++ b/auth/github/github.go @@ -0,0 +1 @@ +package github diff --git a/auth/google/goolge.go b/auth/google/goolge.go new file mode 100644 index 0000000000000000000000000000000000000000..71664db3c875f7f0158d04ecdd883734371235ac --- /dev/null +++ b/auth/google/goolge.go @@ -0,0 +1 @@ +package google diff --git a/auth/oauth2oidc/oauth2oidc.go b/auth/oauth2oidc/oauth2oidc.go new file mode 100644 index 0000000000000000000000000000000000000000..b50444aac60d93010c0ca717681d965c0184d9fb --- /dev/null +++ b/auth/oauth2oidc/oauth2oidc.go @@ -0,0 +1,108 @@ +package oauth2oidc + +import ( + "context" + "fmt" + "github.com/coreos/go-oidc" + log "github.com/sirupsen/logrus" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" + "golang.org/x/oauth2" + "os" +) + +type Oauth2idc struct{} + +var ( + oauth2Config *oauth2.Config + oidcProvider *oidc.Provider + oidcIDTokenVerifier *oidc.IDTokenVerifier +) + +// Setup validate provider +func (o *Oauth2idc) Setup() error { + var err error + + oidcProvider, err = oidc.NewProvider(context.TODO(), os.Getenv("OAUTH2_PROVIDER")) + if err != nil { + return err + } + + oidcIDTokenVerifier = oidcProvider.Verifier(&oidc.Config{ + ClientID: os.Getenv("OAUTH2_CLIENT_ID"), + }) + + oauth2Config = &oauth2.Config{ + ClientID: os.Getenv("OAUTH2_CLIENT_ID"), + ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"), + RedirectURL: os.Getenv("OAUTH2_REDIRECT_URL"), + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + Endpoint: oidcProvider.Endpoint(), + } + + return nil +} + +// CodeUrl get url to redirect client for auth +func (o *Oauth2idc) CodeUrl(state string) string { + return oauth2Config.AuthCodeURL(state) +} + +// Exchange exchange code for Oauth2 token +func (o *Oauth2idc) Exchange(code string) (*oauth2.Token, error) { + oauth2Token, err := oauth2Config.Exchange(context.TODO(), code) + if err != nil { + return nil, err + } + + return oauth2Token, nil +} + +// UserInfo get token user +func (o *Oauth2idc) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) { + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + return nil, fmt.Errorf("no id_token field in oauth2 token") + } + + iDToken, err := oidcIDTokenVerifier.Verify(context.TODO(), rawIDToken) + if err != nil { + return nil, err + } + + userInfo, err := oidcProvider.UserInfo(context.TODO(), oauth2.StaticTokenSource(oauth2Token)) + if err != nil { + return nil, err + } + + type UserInfo struct { + Subject string `json:"sub"` + Profile string `json:"profile"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + + claims []byte + } + + // ID Token payload is just JSON + var claims map[string]interface{} + if err := userInfo.Claims(&claims); err != nil { + return nil, fmt.Errorf("failed to get id token claims: %s", err) + } + + // get some infos about user + user := &model.User{} + user.Sub = userInfo.Subject + user.Email = userInfo.Email + user.Profile = userInfo.Profile + + if v, found := claims["name"]; found && v != nil { + user.Name = v.(string) + } else { + log.Error("name not found in user info claims") + } + + user.Issuer = iDToken.Issuer + user.IssuedAt = iDToken.IssuedAt + + return user, nil +} diff --git a/cmd/wg-gen-web/main.go b/cmd/wg-gen-web/main.go index cdb044ea13650959c01eb92a69841768bfbe8250..14222080a8e541a6342e7422459f9a237a05eb7a 100644 --- a/cmd/wg-gen-web/main.go +++ b/cmd/wg-gen-web/main.go @@ -7,13 +7,24 @@ import ( "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/joho/godotenv" + "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth/fake" + "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth/oauth2oidc" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version" + "golang.org/x/oauth2" + "net/http" "os" "path/filepath" + "time" +) + +var ( + cacheDb = cache.New(60*time.Minute, 10*time.Minute) ) func init() { @@ -80,21 +91,88 @@ func main() { // cors middleware config := cors.DefaultConfig() config.AllowAllOrigins = true + config.AddAllowHeaders("Authorization") app.Use(cors.New(config)) // protection middleware app.Use(helmet.Default()) - // no route redirect to frontend app - app.NoRoute(func(c *gin.Context) { - c.Redirect(301, "/index.html") + // add cache storage to gin app + app.Use(func(ctx *gin.Context) { + ctx.Set("cache", cacheDb) + ctx.Next() }) // serve static files app.Use(static.Serve("/", static.LocalFile("./ui/dist", false))) - // apply api router - api.ApplyRoutes(app) + // setup Oauth2 client if enabled + var oauth2Client auth.Auth + + switch os.Getenv("OAUTH2_PROVIDER_NAME") { + case "fake": + log.Warn("Oauth is set to fake, no actual authentication will be performed") + oauth2Client = &fake.Fake{} + err = oauth2Client.Setup() + if err != nil { + log.WithFields(log.Fields{ + "OAUTH2_PROVIDER_NAME": "oauth2oidc", + "err": err, + }).Fatal("failed to setup Oauth2") + } + case "oauth2oidc": + log.Warn("Oauth is set to oauth2oidc, must be RFC implementation on server side") + oauth2Client = &oauth2oidc.Oauth2idc{} + err = oauth2Client.Setup() + if err != nil { + log.WithFields(log.Fields{ + "OAUTH2_PROVIDER_NAME": "oauth2oidc", + "err": err, + }).Fatal("failed to setup Oauth2") + } + default: + log.WithFields(log.Fields{ + "OAUTH2_PROVIDER_NAME": os.Getenv("OAUTH2_PROVIDER_NAME"), + }).Fatal("auth provider name unknown") + } + + if os.Getenv("OAUTH2_ENABLE") == "true" { + oauth2Client = &oauth2oidc.Oauth2idc{} + err = oauth2Client.Setup() + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Fatal("failed to setup Oauth2") + } + } + app.Use(func(ctx *gin.Context) { + ctx.Set("oauth2Client", oauth2Client) + ctx.Next() + }) + + // apply api routes public + api.ApplyRoutes(app, false) + + // simple middleware to check auth + app.Use(func(c *gin.Context) { + cacheDb := c.MustGet("cache").(*cache.Cache) + + token := c.Request.Header.Get(util.AuthTokenHeaderName) + + oauth2Token, exists := cacheDb.Get(token) + if exists && oauth2Token.(*oauth2.Token).AccessToken == token { + // will be accessible in auth endpoints + c.Set("oauth2Token", oauth2Token) + c.Next() + return + } + + c.AbortWithStatus(http.StatusUnauthorized) + return + }) + + // apply api router private + api.ApplyRoutes(app, true) err = app.Run(fmt.Sprintf("%s:%s", os.Getenv("SERVER"), os.Getenv("PORT"))) if err != nil { diff --git a/go.mod b/go.mod index cf77582e136c0b55126d0eda03851dc4d56efa07..1d31fa6ca24c081062a2959165f40e3bf575f2cc 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,26 @@ module gitlab.127-0-0-1.fr/vx3r/wg-gen-web go 1.14 require ( + github.com/appleboy/gin-jwt/v2 v2.6.3 // indirect + github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect + github.com/coreos/go-oidc v2.2.1+incompatible github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e github.com/gin-contrib/cors v1.3.1 + github.com/gin-contrib/sessions v0.0.3 github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 + github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607 // indirect github.com/gin-gonic/gin v1.6.2 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/gorilla/sessions v1.2.0 // indirect github.com/joho/godotenv v1.3.0 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.5.0 github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df + gopkg.in/square/go-jose.v2 v2.5.0 // indirect ) diff --git a/model/auth.go b/model/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..941dfeebc62ceaa354f14a4b19390ac2f0d02752 --- /dev/null +++ b/model/auth.go @@ -0,0 +1,9 @@ +package model + +type Auth struct { + Oauth2 bool `json:"oauth2"` + ClientId string `json:"clientId"` + Code string `json:"code"` + State string `json:"state"` + CodeUrl string `json:"codeUrl"` +} diff --git a/model/client.go b/model/client.go index 78cda45deea72ef593001f858db73eadbfe6b887..4a40b279e8a20f9bd3ac35ad44add8bd30eb5893 100644 --- a/model/client.go +++ b/model/client.go @@ -18,6 +18,8 @@ type Client struct { Address []string `json:"address"` PrivateKey string `json:"privateKey"` PublicKey string `json:"publicKey"` + CreatedBy string `json:"createdBy"` + UpdatedBy string `json:"updatedBy"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` } diff --git a/model/server.go b/model/server.go index 592aa366e928c256d475daa9268cd0c87c0e0f03..eb45782162e7663a9a3149a722832192844925a2 100644 --- a/model/server.go +++ b/model/server.go @@ -21,6 +21,7 @@ type Server struct { PostUp string `json:"postUp"` PreDown string `json:"preDown"` PostDown string `json:"postDown"` + UpdatedBy string `json:"updatedBy"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` } diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000000000000000000000000000000000000..852123f69ae0b09627d2a3ff17abf509491ae83c --- /dev/null +++ b/model/user.go @@ -0,0 +1,12 @@ +package model + +import "time" + +type User struct { + Sub string `json:"sub"` + Name string `json:"name"` + Email string `json:"email"` + Profile string `json:"profile"` + Issuer string `json:"issuer"` + IssuedAt time.Time `json:"issuedAt"` +} diff --git a/ui/package-lock.json b/ui/package-lock.json index c3a8613d3b23241b72523e5b7bd7d55b352c766f..8aa703ec60d076bd6ac9ecfd313ea07720ef6e88 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -109,19 +109,19 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -134,7 +134,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz?cache=0&sync_timestamp=1573280577145&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-ansi%2Fdownload%2Fstrip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -143,7 +143,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz?cache=0&sync_timestamp=1569557363805&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } @@ -339,7 +339,7 @@ }, "yallist": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true } @@ -608,7 +608,7 @@ }, "alphanum-sort": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/alphanum-sort/download/alphanum-sort-1.0.2.tgz", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, @@ -620,7 +620,7 @@ }, "ansi-html": { "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-html/download/ansi-html-0.0.7.tgz", "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", "dev": true }, @@ -641,7 +641,7 @@ }, "any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/any-promise/download/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", "dev": true }, @@ -678,7 +678,7 @@ }, "arr-diff": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/arr-diff/download/arr-diff-4.0.0.tgz", "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true }, @@ -690,19 +690,19 @@ }, "arr-union": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/arr-union/download/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, "array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz?cache=0&sync_timestamp=1574313492546&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-flatten%2Fdownload%2Farray-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, "array-union": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/array-union/download/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { @@ -711,13 +711,13 @@ }, "array-uniq": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/array-uniq/download/array-uniq-1.0.3.tgz", "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, "array-unique": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "resolved": "https://registry.npm.taobao.org/array-unique/download/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, @@ -753,13 +753,13 @@ "dependencies": { "inherits": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.1.tgz", "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", "dev": true }, "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npm.taobao.org/util/download/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -770,13 +770,13 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assign-symbols": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/assign-symbols/download/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, @@ -803,7 +803,7 @@ }, "asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "resolved": "https://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, @@ -830,7 +830,7 @@ }, "aws-sign2": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "resolved": "https://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, @@ -894,7 +894,7 @@ "dependencies": { "define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { @@ -940,13 +940,13 @@ }, "batch": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "resolved": "https://registry.npm.taobao.org/batch/download/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/bcrypt-pbkdf/download/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "requires": { @@ -1018,7 +1018,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, @@ -1032,7 +1032,7 @@ }, "bonjour": { "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "resolved": "https://registry.npm.taobao.org/bonjour/download/bonjour-3.5.0.tgz", "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { @@ -1054,7 +1054,7 @@ }, "boolbase": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/boolbase/download/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, @@ -1088,7 +1088,7 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { @@ -1099,7 +1099,7 @@ }, "brorand": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/brorand/download/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, @@ -1142,7 +1142,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/browserify-rsa/download/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -1152,7 +1152,7 @@ }, "browserify-sign": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/browserify-sign/download/browserify-sign-4.0.4.tgz", "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { @@ -1217,13 +1217,13 @@ }, "buffer-xor": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/buffer-xor/download/buffer-xor-1.0.3.tgz", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, "builtin-status-codes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/builtin-status-codes/download/builtin-status-codes-3.0.0.tgz", "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, @@ -1300,13 +1300,13 @@ }, "call-me-maybe": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/call-me-maybe/download/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, "caller-callsite": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/caller-callsite/download/caller-callsite-2.0.0.tgz?cache=0&sync_timestamp=1562696843228&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaller-callsite%2Fdownload%2Fcaller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, "requires": { @@ -1315,7 +1315,7 @@ }, "caller-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/caller-path/download/caller-path-2.0.0.tgz?cache=0&sync_timestamp=1574395542397&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaller-path%2Fdownload%2Fcaller-path-2.0.0.tgz", "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, "requires": { @@ -1324,13 +1324,13 @@ }, "callsites": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/callsites/download/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, "camel-case": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz", "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", "dev": true, "requires": { @@ -1370,7 +1370,7 @@ }, "caseless": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "resolved": "https://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, @@ -1498,7 +1498,7 @@ "dependencies": { "define-property": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { @@ -1524,7 +1524,7 @@ }, "cli-cursor": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/cli-cursor/download/cli-cursor-2.1.0.tgz", "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { @@ -1654,7 +1654,7 @@ }, "clone": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/clone/download/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, @@ -1682,13 +1682,13 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "collection-visit": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { @@ -1717,7 +1717,7 @@ }, "color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, @@ -1748,7 +1748,7 @@ }, "commondir": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/commondir/download/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, @@ -1784,7 +1784,7 @@ "dependencies": { "bytes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, @@ -1799,7 +1799,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, @@ -1852,7 +1852,7 @@ }, "constants-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/constants-browserify/download/constants-browserify-1.0.0.tgz", "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, @@ -1887,7 +1887,7 @@ }, "cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "resolved": "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, @@ -1907,7 +1907,7 @@ }, "copy-descriptor": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/copy-descriptor/download/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, @@ -1953,7 +1953,7 @@ }, "globby": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/globby/download/globby-7.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fglobby%2Fdownload%2Fglobby-7.1.1.tgz", "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { @@ -1967,7 +1967,7 @@ "dependencies": { "pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } @@ -2047,7 +2047,7 @@ }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, @@ -2154,7 +2154,7 @@ }, "css-color-names": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/css-color-names/download/css-color-names-0.0.4.tgz", "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", "dev": true }, @@ -2306,13 +2306,13 @@ }, "cssnano-util-get-arguments": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/cssnano-util-get-arguments/download/cssnano-util-get-arguments-4.0.0.tgz", "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", "dev": true }, "cssnano-util-get-match": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/cssnano-util-get-match/download/cssnano-util-get-match-4.0.0.tgz", "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", "dev": true }, @@ -2360,13 +2360,13 @@ }, "cyclist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/cyclist/download/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, "dashdash": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "resolved": "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { @@ -2390,13 +2390,13 @@ }, "decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decode-uri-component": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/decode-uri-component/download/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, @@ -2537,7 +2537,7 @@ }, "defaults": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/defaults/download/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { @@ -2611,7 +2611,7 @@ "dependencies": { "globby": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/globby/download/globby-6.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fglobby%2Fdownload%2Fglobby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -2624,7 +2624,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -2640,13 +2640,13 @@ }, "delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "depd": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, @@ -2662,7 +2662,7 @@ }, "destroy": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, @@ -2694,7 +2694,7 @@ }, "dns-equal": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/dns-equal/download/dns-equal-1.0.0.tgz", "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", "dev": true }, @@ -2710,7 +2710,7 @@ }, "dns-txt": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/dns-txt/download/dns-txt-2.0.2.tgz", "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { @@ -2798,7 +2798,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/duplexer/download/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -2816,13 +2816,13 @@ }, "easy-stack": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/easy-stack/download/easy-stack-1.0.0.tgz", "integrity": "sha1-EskbMIWjfwuqM26UhurEv5Tj54g=", "dev": true }, "ecc-jsbn": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "requires": { @@ -2832,7 +2832,7 @@ }, "ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, @@ -2877,7 +2877,7 @@ }, "encodeurl": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "dev": true }, @@ -2978,13 +2978,13 @@ }, "escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", "dev": true }, "escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "resolved": "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, @@ -3021,7 +3021,7 @@ }, "etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "resolved": "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, @@ -3079,7 +3079,7 @@ }, "expand-brackets": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { @@ -3103,7 +3103,7 @@ }, "define-property": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { @@ -3112,7 +3112,7 @@ }, "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { @@ -3121,7 +3121,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -3176,7 +3176,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, @@ -3202,7 +3202,7 @@ }, "extend-shallow": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { @@ -3239,7 +3239,7 @@ "dependencies": { "define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { @@ -3248,7 +3248,7 @@ }, "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { @@ -3288,7 +3288,7 @@ }, "extsprintf": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, @@ -3320,7 +3320,7 @@ }, "faye-websocket": { "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "resolved": "https://registry.npm.taobao.org/faye-websocket/download/faye-websocket-0.10.0.tgz", "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "dev": true, "requires": { @@ -3351,7 +3351,7 @@ }, "fill-range": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { @@ -3363,7 +3363,7 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { @@ -3398,7 +3398,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -3456,13 +3456,13 @@ }, "for-in": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/for-in/download/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "forever-agent": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "resolved": "https://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, @@ -3479,13 +3479,13 @@ }, "forwarded": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz", "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", "dev": true }, "fragment-cache": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "resolved": "https://registry.npm.taobao.org/fragment-cache/download/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { @@ -3494,13 +3494,13 @@ }, "fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "resolved": "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, "from2": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/from2/download/from2-2.3.0.tgz", "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { @@ -3530,7 +3530,7 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "resolved": "https://registry.npm.taobao.org/fs-write-stream-atomic/download/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { @@ -3576,13 +3576,13 @@ }, "get-value": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "resolved": "https://registry.npm.taobao.org/get-value/download/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, "getpass": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "resolved": "https://registry.npm.taobao.org/getpass/download/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { @@ -3605,7 +3605,7 @@ }, "glob-parent": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/glob-parent/download/glob-parent-3.1.0.tgz", "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { @@ -3615,7 +3615,7 @@ "dependencies": { "is-glob": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-glob/download/is-glob-3.1.0.tgz", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { @@ -3626,7 +3626,7 @@ }, "glob-to-regexp": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/glob-to-regexp/download/glob-to-regexp-0.3.0.tgz", "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, @@ -3684,7 +3684,7 @@ }, "har-schema": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, @@ -3709,7 +3709,7 @@ }, "has-ansi": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/has-ansi/download/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { @@ -3718,7 +3718,7 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true } @@ -3726,7 +3726,7 @@ }, "has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, @@ -3738,7 +3738,7 @@ }, "has-value": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/has-value/download/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { @@ -3749,7 +3749,7 @@ }, "has-values": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/has-values/download/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { @@ -3759,7 +3759,7 @@ "dependencies": { "kind-of": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { @@ -3770,7 +3770,7 @@ }, "hash-base": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/hash-base/download/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { @@ -3814,7 +3814,7 @@ }, "hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/hmac-drbg/download/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { @@ -3837,7 +3837,7 @@ }, "hpack.js": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "resolved": "https://registry.npm.taobao.org/hpack.js/download/hpack.js-2.1.6.tgz", "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { @@ -3849,13 +3849,13 @@ }, "hsl-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/hsl-regex/download/hsl-regex-1.0.0.tgz", "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", "dev": true }, "hsla-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/hsla-regex/download/hsla-regex-1.0.0.tgz", "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", "dev": true }, @@ -3888,7 +3888,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-3.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-webpack-plugin%2Fdownload%2Fhtml-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -3909,13 +3909,13 @@ }, "json5": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "https://registry.npm.taobao.org/json5/download/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, "loader-utils": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-0.2.17.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Floader-utils%2Fdownload%2Floader-utils-0.2.17.tgz", "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { @@ -3972,7 +3972,7 @@ }, "http-deceiver": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "resolved": "https://registry.npm.taobao.org/http-deceiver/download/http-deceiver-1.2.7.tgz", "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", "dev": true }, @@ -3991,7 +3991,7 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true } @@ -3999,7 +3999,7 @@ }, "http-parser-js": { "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "resolved": "https://registry.npm.taobao.org/http-parser-js/download/http-parser-js-0.4.10.tgz", "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", "dev": true }, @@ -4028,7 +4028,7 @@ }, "http-signature": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz?cache=0&sync_timestamp=1572997318670&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-signature%2Fdownload%2Fhttp-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { @@ -4039,7 +4039,7 @@ }, "https-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, @@ -4075,7 +4075,7 @@ }, "iferr": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "resolved": "https://registry.npm.taobao.org/iferr/download/iferr-0.1.5.tgz", "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, @@ -4087,7 +4087,7 @@ }, "import-cwd": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/import-cwd/download/import-cwd-2.1.0.tgz", "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", "dev": true, "requires": { @@ -4096,7 +4096,7 @@ }, "import-fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/import-fresh/download/import-fresh-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fimport-fresh%2Fdownload%2Fimport-fresh-2.0.0.tgz", "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { @@ -4106,7 +4106,7 @@ }, "import-from": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/import-from/download/import-from-2.1.0.tgz", "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", "dev": true, "requires": { @@ -4179,7 +4179,7 @@ }, "imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, @@ -4191,7 +4191,7 @@ }, "indexes-of": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/indexes-of/download/indexes-of-1.0.1.tgz", "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, @@ -4253,7 +4253,7 @@ }, "ip": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "resolved": "https://registry.npm.taobao.org/ip/download/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, @@ -4270,13 +4270,13 @@ }, "is-absolute-url": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-absolute-url/download/is-absolute-url-2.1.0.tgz?cache=0&sync_timestamp=1569735663940&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-absolute-url%2Fdownload%2Fis-absolute-url-2.1.0.tgz", "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", "dev": true }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -4285,7 +4285,7 @@ "dependencies": { "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { @@ -4302,7 +4302,7 @@ }, "is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "resolved": "https://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, @@ -4337,7 +4337,7 @@ }, "is-color-stop": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-color-stop/download/is-color-stop-1.1.0.tgz", "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", "dev": true, "requires": { @@ -4351,7 +4351,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -4360,7 +4360,7 @@ "dependencies": { "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { @@ -4396,13 +4396,13 @@ }, "is-directory": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "resolved": "https://registry.npm.taobao.org/is-directory/download/is-directory-0.3.1.tgz", "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, "is-extendable": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/is-extendable/download/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, @@ -4414,7 +4414,7 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, @@ -4429,7 +4429,7 @@ }, "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-number/download/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { @@ -4438,7 +4438,7 @@ "dependencies": { "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { @@ -4479,7 +4479,7 @@ }, "is-plain-obj": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-plain-obj/download/is-plain-obj-1.1.0.tgz?cache=0&sync_timestamp=1579602956062&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-plain-obj%2Fdownload%2Fis-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, @@ -4509,7 +4509,7 @@ }, "is-stream": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, @@ -4533,7 +4533,7 @@ }, "is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-typedarray/download/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, @@ -4545,19 +4545,19 @@ }, "is-wsl": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-wsl/download/is-wsl-1.1.0.tgz?cache=0&sync_timestamp=1569219566107&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-wsl%2Fdownload%2Fis-wsl-1.1.0.tgz", "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, @@ -4569,7 +4569,7 @@ }, "isstream": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, @@ -4608,13 +4608,13 @@ }, "js-message": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.5.tgz", + "resolved": "https://registry.npm.taobao.org/js-message/download/js-message-1.0.5.tgz?cache=0&sync_timestamp=1575284239628&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjs-message%2Fdownload%2Fjs-message-1.0.5.tgz", "integrity": "sha1-IwDSSxrwjondCVvBpMnJz8uJLRU=", "dev": true }, "js-queue": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/js-queue/download/js-queue-2.0.0.tgz", "integrity": "sha1-NiITz4YPRo8BJfxslqvBdCUx+Ug=", "dev": true, "requires": { @@ -4639,7 +4639,7 @@ }, "jsbn": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, @@ -4651,7 +4651,7 @@ }, "json-schema": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "resolved": "https://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, @@ -4663,7 +4663,7 @@ }, "json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, @@ -4684,7 +4684,7 @@ }, "jsonfile": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/jsonfile/download/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { @@ -4693,7 +4693,7 @@ }, "jsprim": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "resolved": "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { @@ -4745,7 +4745,7 @@ }, "lines-and-columns": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "resolved": "https://registry.npm.taobao.org/lines-and-columns/download/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, @@ -4790,25 +4790,25 @@ }, "lodash.mapvalues": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", + "resolved": "https://registry.npm.taobao.org/lodash.mapvalues/download/lodash.mapvalues-4.6.0.tgz", "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", "dev": true }, "lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/lodash.memoize/download/lodash.memoize-4.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash.memoize%2Fdownload%2Flodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, "lodash.transform": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", + "resolved": "https://registry.npm.taobao.org/lodash.transform/download/lodash.transform-4.6.0.tgz", "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=", "dev": true }, "lodash.uniq": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "resolved": "https://registry.npm.taobao.org/lodash.uniq/download/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, @@ -4829,7 +4829,7 @@ }, "lower-case": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/lower-case/download/lower-case-1.1.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flower-case%2Fdownload%2Flower-case-1.1.4.tgz", "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", "dev": true }, @@ -4862,13 +4862,13 @@ }, "map-cache": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/map-cache/download/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, "map-visit": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/map-visit/download/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { @@ -4894,7 +4894,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -4919,7 +4919,7 @@ }, "memory-fs": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "resolved": "https://registry.npm.taobao.org/memory-fs/download/memory-fs-0.4.1.tgz?cache=0&sync_timestamp=1570537511500&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmemory-fs%2Fdownload%2Fmemory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { @@ -4929,7 +4929,7 @@ }, "merge-descriptors": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, @@ -4956,7 +4956,7 @@ }, "methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, @@ -5032,7 +5032,7 @@ "dependencies": { "normalize-url": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "resolved": "https://registry.npm.taobao.org/normalize-url/download/normalize-url-1.9.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnormalize-url%2Fdownload%2Fnormalize-url-1.9.1.tgz", "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", "dev": true, "requires": { @@ -5063,7 +5063,7 @@ }, "minimalistic-crypto-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/minimalistic-crypto-utils/download/minimalistic-crypto-utils-1.0.1.tgz", "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", "dev": true }, @@ -5181,7 +5181,7 @@ }, "move-concurrently": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz", "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { @@ -5211,7 +5211,7 @@ }, "multicast-dns-service-types": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/multicast-dns-service-types/download/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, @@ -5322,7 +5322,7 @@ "dependencies": { "punycode": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "resolved": "https://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true } @@ -5362,7 +5362,7 @@ }, "normalize-range": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/normalize-range/download/normalize-range-0.1.2.tgz", "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, @@ -5374,7 +5374,7 @@ }, "npm-run-path": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { @@ -5392,13 +5392,13 @@ }, "num2fraction": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/num2fraction/download/num2fraction-1.2.2.tgz", "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, @@ -5410,13 +5410,13 @@ }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object-copy": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/object-copy/download/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { @@ -5427,7 +5427,7 @@ "dependencies": { "define-property": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { @@ -5436,7 +5436,7 @@ }, "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { @@ -5469,7 +5469,7 @@ }, "object-visit": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/object-visit/download/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { @@ -5500,7 +5500,7 @@ }, "object.pick": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/object.pick/download/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { @@ -5527,7 +5527,7 @@ }, "on-finished": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "dev": true, "requires": { @@ -5551,7 +5551,7 @@ }, "onetime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/onetime/download/onetime-2.0.1.tgz", "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { @@ -5624,7 +5624,7 @@ }, "os-browserify": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/os-browserify/download/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, @@ -5641,13 +5641,13 @@ }, "p-defer": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/p-defer/download/p-defer-1.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-defer%2Fdownload%2Fp-defer-1.0.0.tgz", "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-finally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, @@ -5718,7 +5718,7 @@ }, "param-case": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/param-case/download/param-case-2.1.1.tgz?cache=0&sync_timestamp=1576721509342&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fparam-case%2Fdownload%2Fparam-case-2.1.1.tgz", "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", "dev": true, "requires": { @@ -5774,7 +5774,7 @@ }, "pascalcase": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/pascalcase/download/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, @@ -5786,7 +5786,7 @@ }, "path-dirname": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/path-dirname/download/path-dirname-1.0.2.tgz", "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, @@ -5804,13 +5804,13 @@ }, "path-is-inside": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/path-is-inside/download/path-is-inside-1.0.2.tgz", "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, "path-key": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/path-key/download/path-key-2.0.1.tgz?cache=0&sync_timestamp=1574441404712&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpath-key%2Fdownload%2Fpath-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, @@ -5822,7 +5822,7 @@ }, "path-to-regexp": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "resolved": "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, @@ -5837,7 +5837,7 @@ "dependencies": { "pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } @@ -5858,7 +5858,7 @@ }, "performance-now": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, @@ -5876,13 +5876,13 @@ }, "pinkie": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, "pinkie-promise": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { @@ -5991,7 +5991,7 @@ }, "posix-character-classes": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/posix-character-classes/download/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, @@ -6575,7 +6575,7 @@ }, "prepend-http": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "resolved": "https://registry.npm.taobao.org/prepend-http/download/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, @@ -6588,7 +6588,7 @@ }, "pretty-error": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/pretty-error/download/pretty-error-2.1.1.tgz", "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", "dev": true, "requires": { @@ -6598,7 +6598,7 @@ }, "process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "resolved": "https://registry.npm.taobao.org/process/download/process-0.11.10.tgz", "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", "dev": true }, @@ -6610,7 +6610,7 @@ }, "promise-inflight": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/promise-inflight/download/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, @@ -6626,13 +6626,13 @@ }, "prr": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/prr/download/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, @@ -6697,7 +6697,7 @@ }, "q": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "resolved": "https://registry.npm.taobao.org/q/download/q-1.5.1.tgz", "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, @@ -6709,7 +6709,7 @@ }, "query-string": { "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "resolved": "https://registry.npm.taobao.org/query-string/download/query-string-4.3.4.tgz", "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "dev": true, "requires": { @@ -6719,13 +6719,13 @@ }, "querystring": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/querystring/download/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, "querystring-es3": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "resolved": "https://registry.npm.taobao.org/querystring-es3/download/querystring-es3-0.2.1.tgz", "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, @@ -6847,13 +6847,13 @@ }, "relateurl": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "resolved": "https://registry.npm.taobao.org/relateurl/download/relateurl-0.2.7.tgz", "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, "remove-trailing-separator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/remove-trailing-separator/download/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, @@ -6872,13 +6872,13 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "css-select": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/css-select/download/css-select-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcss-select%2Fdownload%2Fcss-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { @@ -6896,7 +6896,7 @@ }, "domutils": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "resolved": "https://registry.npm.taobao.org/domutils/download/domutils-1.5.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomutils%2Fdownload%2Fdomutils-1.5.1.tgz", "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { @@ -6906,7 +6906,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz?cache=0&sync_timestamp=1573280577145&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-ansi%2Fdownload%2Fstrip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -6923,7 +6923,7 @@ }, "repeat-string": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "resolved": "https://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, @@ -6977,7 +6977,7 @@ }, "require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/require-directory/download/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, @@ -6989,7 +6989,7 @@ }, "requires-port": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/requires-port/download/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, @@ -7004,7 +7004,7 @@ }, "resolve-cwd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/resolve-cwd/download/resolve-cwd-2.0.0.tgz", "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { @@ -7013,19 +7013,19 @@ }, "resolve-from": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, "resolve-url": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "resolved": "https://registry.npm.taobao.org/resolve-url/download/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, "restore-cursor": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/restore-cursor/download/restore-cursor-2.0.0.tgz", "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { @@ -7041,19 +7041,19 @@ }, "retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "resolved": "https://registry.npm.taobao.org/retry/download/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, "rgb-regex": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/rgb-regex/download/rgb-regex-1.0.1.tgz", "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", "dev": true }, "rgba-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/rgba-regex/download/rgba-regex-1.0.0.tgz", "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, @@ -7078,7 +7078,7 @@ }, "run-queue": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/run-queue/download/run-queue-1.0.3.tgz", "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { @@ -7093,7 +7093,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz?cache=0&sync_timestamp=1571687571136&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-regex%2Fdownload%2Fsafe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -7146,7 +7146,7 @@ }, "select-hose": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/select-hose/download/select-hose-2.0.0.tgz", "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, @@ -7197,7 +7197,7 @@ "dependencies": { "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -7225,7 +7225,7 @@ }, "serve-index": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "resolved": "https://registry.npm.taobao.org/serve-index/download/serve-index-1.9.1.tgz", "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { @@ -7249,7 +7249,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -7261,13 +7261,13 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, @@ -7293,7 +7293,7 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, @@ -7311,7 +7311,7 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { @@ -7322,7 +7322,7 @@ }, "setimmediate": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "resolved": "https://registry.npm.taobao.org/setimmediate/download/setimmediate-1.0.5.tgz", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", "dev": true }, @@ -7353,7 +7353,7 @@ }, "shebang-command": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "resolved": "https://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fshebang-command%2Fdownload%2Fshebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { @@ -7362,7 +7362,7 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/shebang-regex/download/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, @@ -7391,7 +7391,7 @@ }, "simple-swizzle": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/simple-swizzle/download/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "dev": true, "requires": { @@ -7408,7 +7408,7 @@ }, "slash": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/slash/download/slash-1.0.0.tgz", "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, @@ -7439,7 +7439,7 @@ }, "define-property": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { @@ -7448,7 +7448,7 @@ }, "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { @@ -7457,13 +7457,13 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } @@ -7482,7 +7482,7 @@ "dependencies": { "define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { @@ -7531,7 +7531,7 @@ "dependencies": { "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { @@ -7586,7 +7586,7 @@ }, "sort-keys": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/sort-keys/download/sort-keys-1.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsort-keys%2Fdownload%2Fsort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, "requires": { @@ -7630,7 +7630,7 @@ }, "source-map-url": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "resolved": "https://registry.npm.taobao.org/source-map-url/download/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, @@ -7717,7 +7717,7 @@ }, "sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, @@ -7762,7 +7762,7 @@ }, "static-extend": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/static-extend/download/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { @@ -7772,7 +7772,7 @@ "dependencies": { "define-property": { "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { @@ -7783,13 +7783,13 @@ }, "statuses": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, "stealthy-require": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/stealthy-require/download/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, @@ -7834,7 +7834,7 @@ }, "strict-uri-encode": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/strict-uri-encode/download/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, @@ -7850,13 +7850,13 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz?cache=0&sync_timestamp=1573280577145&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-ansi%2Fdownload%2Fstrip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { @@ -7943,7 +7943,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/strip-eof/download/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -8094,7 +8094,7 @@ }, "thenify": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/thenify/download/thenify-3.3.0.tgz", "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", "dev": true, "requires": { @@ -8103,7 +8103,7 @@ }, "thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "resolved": "https://registry.npm.taobao.org/thenify-all/download/thenify-all-1.6.0.tgz", "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", "dev": true, "requires": { @@ -8148,19 +8148,19 @@ }, "timsort": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/timsort/download/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, "to-arraybuffer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz", "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, "to-object-path": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "resolved": "https://registry.npm.taobao.org/to-object-path/download/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { @@ -8169,7 +8169,7 @@ "dependencies": { "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&sync_timestamp=1579193926808&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { @@ -8192,7 +8192,7 @@ }, "to-regex-range": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { @@ -8208,7 +8208,7 @@ }, "toposort": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "resolved": "https://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz", "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "dev": true }, @@ -8242,13 +8242,13 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/tty-browserify/download/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, "tunnel-agent": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "resolved": "https://registry.npm.taobao.org/tunnel-agent/download/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { @@ -8257,7 +8257,7 @@ }, "tweetnacl": { "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "resolved": "https://registry.npm.taobao.org/tweetnacl/download/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, @@ -8279,7 +8279,7 @@ }, "typedarray": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "resolved": "https://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, @@ -8315,13 +8315,13 @@ }, "uniq": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/uniq/download/uniq-1.0.1.tgz", "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "dev": true }, "uniqs": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/uniqs/download/uniqs-2.0.0.tgz", "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", "dev": true }, @@ -8351,19 +8351,19 @@ }, "unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, "unquote": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/unquote/download/unquote-1.1.1.tgz", "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", "dev": true }, "unset-value": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/unset-value/download/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { @@ -8373,7 +8373,7 @@ "dependencies": { "has-value": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "resolved": "https://registry.npm.taobao.org/has-value/download/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { @@ -8384,7 +8384,7 @@ "dependencies": { "isobject": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/isobject/download/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { @@ -8395,7 +8395,7 @@ }, "has-values": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "resolved": "https://registry.npm.taobao.org/has-values/download/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true } @@ -8409,7 +8409,7 @@ }, "upper-case": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "resolved": "https://registry.npm.taobao.org/upper-case/download/upper-case-1.1.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fupper-case%2Fdownload%2Fupper-case-1.1.3.tgz", "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", "dev": true }, @@ -8424,13 +8424,13 @@ }, "urix": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/urix/download/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, "url": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "resolved": "https://registry.npm.taobao.org/url/download/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "dev": true, "requires": { @@ -8440,7 +8440,7 @@ "dependencies": { "punycode": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "resolved": "https://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true } @@ -8484,7 +8484,7 @@ "dependencies": { "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true } @@ -8492,7 +8492,7 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, @@ -8510,13 +8510,13 @@ }, "utila": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "resolved": "https://registry.npm.taobao.org/utila/download/utila-0.4.0.tgz", "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, "utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "dev": true }, @@ -8538,7 +8538,7 @@ }, "vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "resolved": "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, @@ -8550,7 +8550,7 @@ }, "verror": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "resolved": "https://registry.npm.taobao.org/verror/download/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { @@ -8570,6 +8570,11 @@ "resolved": "https://registry.npm.taobao.org/vue/download/vue-2.6.11.tgz", "integrity": "sha1-dllNh31LEiNEBuhONSdcbVFBJcU=" }, + "vue-axios": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-2.1.5.tgz", + "integrity": "sha512-th5xVbInVoyIoe+qY+9GCflEVezxAvztD4xpFF39SRQYqpoKD2qkmX8yv08jJG9a2SgNOCjirjJGSwg/wTrbmA==" + }, "vue-cli-plugin-vuetify": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.0.5.tgz", @@ -8676,6 +8681,11 @@ "loader-utils": "^1.2.0" } }, + "vuex": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.2.0.tgz", + "integrity": "sha512-qBZGJJ1gUNWZbfZlH7ylIPwENg3R0Ckpq+qPexF065kOMOt1Ixt8WDJmtssVv7OhepWD0+Qie7pOS8f0oQy1JA==" + }, "watchpack": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", @@ -9316,7 +9326,7 @@ }, "wcwidth": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/wcwidth/download/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, "requires": { @@ -9564,7 +9574,7 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, @@ -9628,13 +9638,13 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz?cache=0&sync_timestamp=1573280577145&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-ansi%2Fdownload%2Fstrip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { @@ -10224,7 +10234,7 @@ }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { @@ -10278,7 +10288,7 @@ }, "require-main-filename": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/require-main-filename/download/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, @@ -10295,7 +10305,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz?cache=0&sync_timestamp=1573280577145&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstrip-ansi%2Fdownload%2Fstrip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -10313,7 +10323,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -10323,7 +10333,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "https://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -10423,7 +10433,7 @@ }, "which-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "resolved": "https://registry.npm.taobao.org/which-module/download/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, diff --git a/ui/package.json b/ui/package.json index 4f0b8dd199d14c7b8b96f3ea692dd15bd1abad23..19a032c638c9fe47c4c250ce964e656ac9969928 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,9 +11,11 @@ "is-cidr": "^3.1.0", "moment": "^2.24.0", "vue": "^2.6.10", + "vue-axios": "^2.1.5", "vue-moment": "^4.1.0", "vue-router": "^3.1.6", - "vuetify": "^2.2.22" + "vuetify": "^2.2.22", + "vuex": "^3.2.0" }, "devDependencies": { "@vue/cli-plugin-router": "^4.3.1", diff --git a/ui/src/App.vue b/ui/src/App.vue index 519cf15852646d77584f31f4069eab1e7e20c216..32c317fe5f0b7eb5d039a65014d10761f90a125b 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -1,69 +1,36 @@ <template> <v-app id="inspire"> + <Notification v-bind:notification="notification"/> + <div v-if="this.isAuthenticated"> + <Header/> - <v-app-bar app> - <img class="mr-3" :src="require('./assets/logo.png')" height="50" alt="Wg Gen Web"/> - <v-toolbar-title to="/">Wg Gen Web</v-toolbar-title> - - <v-spacer /> - - <v-toolbar-items> - <v-btn to="/clients"> - Clients - <v-icon right dark>mdi-account-network-outline</v-icon> - </v-btn> - <v-btn to="/server"> - Server - <v-icon right dark>mdi-vpn</v-icon> - </v-btn> - </v-toolbar-items> - - </v-app-bar> - - <v-content> - <v-container> - <router-view /> - </v-container> - <Notification v-bind:notification="notification"/> - </v-content> - - <v-footer app> - <v-row justify="start" no-gutters> - <v-col cols="12" lg="6" md="12" sm="12"> - <div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'"> - <small>Copyright © {{ new Date().getFullYear() }}, Wg Gen Web.</small> - <small>This work is licensed under a <a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">WTFPL License.</a></small> - </div> - </v-col> - </v-row> - <v-row justify="end" no-gutters> - <v-col cols="12" lg="6" md="12" sm="12"> - <div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'"> - <small>Created with</small> - <v-icon class="pr-1 pl-1">mdi-heart</v-icon><span>by</span><a class="pr-2 pl-1" href="mailto:vx3r@127-0-0-1.fr">vx3r</a> - <a :href="'https://github.com/vx3r/wg-gen-web/commit/' + version"><kbd>Version: {{ version.substring(0,7) }}</kbd></a> - </div> - </v-col> - </v-row> - </v-footer> + <v-content> + <v-container> + <router-view /> + </v-container> + </v-content> + <Footer/> + </div> </v-app> </template> <script> - import {ApiService} from "./services/ApiService"; import Notification from './components/Notification' + import Header from "./components/Header"; + import Footer from "./components/Footer"; + import {mapActions, mapGetters} from "vuex"; export default { name: 'App', components: { + Footer, + Header, Notification }, data: () => ({ - api: null, - version:'N/A', notification: { show: false, color: '', @@ -71,23 +38,69 @@ }, }), - mounted() { - this.api = new ApiService(); - this.getVersion() + computed:{ + ...mapGetters({ + isAuthenticated: 'auth/isAuthenticated', + authStatus: 'auth/authStatus', + authRedirectUrl: 'auth/authRedirectUrl', + authError: 'auth/error', + clientError: 'client/error', + serverError: 'server/error', + }) }, created () { this.$vuetify.theme.dark = true }, - methods: { - getVersion() { - this.api.get('/server/version').then((res) => { - this.version = res.version; - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); + mounted() { + if (this.$route.query.code && this.$route.query.state) { + this.oauth2_exchange({ + code: this.$route.query.code, + state: this.$route.query.state + }) + } else { + this.oauth2_url() + } + }, + + watch: { + authError(newValue, oldValue) { + console.log(newValue) + this.notify('error', newValue); + }, + + clientError(newValue, oldValue) { + console.log(newValue) + this.notify('error', newValue); + }, + + serverError(newValue, oldValue) { + console.log(newValue) + this.notify('error', newValue); + }, + + isAuthenticated(newValue, oldValue) { + console.log(`Updating isAuthenticated from ${oldValue} to ${newValue}`); + if (newValue === true) { + this.$router.push('/clients') + } + }, + + authStatus(newValue, oldValue) { + console.log(`Updating authStatus from ${oldValue} to ${newValue}`); + if (newValue === 'redirect') { + window.location.replace(this.authRedirectUrl) + } }, + }, + + methods: { + ...mapActions('auth', { + oauth2_exchange: 'oauth2_exchange', + oauth2_url: 'oauth2_url', + }), + notify(color, msg) { this.notification.show = true; this.notification.color = color; diff --git a/ui/src/components/Clients.vue b/ui/src/components/Clients.vue index e2c5786e41446302f36084ce977dd2d7742e27b1..57cd215e5e2ebce0f763fa3b564ff8fb2740027f 100644 --- a/ui/src/components/Clients.vue +++ b/ui/src/components/Clients.vue @@ -9,7 +9,7 @@ </v-list-item-content> <v-btn color="success" - @click="startAddClient" + @click="startCreate" > Add new client <v-icon right dark>mdi-account-multiple-plus-outline</v-icon> @@ -31,15 +31,15 @@ <v-list-item-content> <v-list-item-title class="headline">{{ client.name }}</v-list-item-title> <v-list-item-subtitle>{{ client.email }}</v-list-item-subtitle> - <v-list-item-subtitle>Created: {{ client.created | formatDate }}</v-list-item-subtitle> - <v-list-item-subtitle>Updated: {{ client.updated | formatDate }}</v-list-item-subtitle> + <v-list-item-subtitle>Created: {{ client.created | formatDate }} by {{ client.createdBy }}</v-list-item-subtitle> + <v-list-item-subtitle>Updated: {{ client.updated | formatDate }} by {{ client.updatedBy }}</v-list-item-subtitle> </v-list-item-content> <v-list-item-avatar tile size="150" > - <v-img :src="`${apiBaseUrl}/client/${client.id}/config?qrcode=true`"/> + <v-img :src="'data:image/png;base64, ' + getClientQrcode(client.id)"/> </v-list-item-avatar> </v-list-item> @@ -55,61 +55,61 @@ </v-chip> </v-card-text> <v-card-actions> - <v-tooltip bottom> - <template v-slot:activator="{ on }"> - <v-btn - text - :href="`${apiBaseUrl}/client/${client.id}/config?qrcode=false`" - v-on="on" - > - <span class="d-none d-lg-flex">Download</span> - <v-icon right dark>mdi-cloud-download-outline</v-icon> - </v-btn> - </template> - <span>Download</span> - </v-tooltip> + <v-tooltip bottom> + <template v-slot:activator="{ on }"> + <v-btn + text + v-on:click="forceFileDownload(client)" + v-on="on" + > + <span class="d-none d-lg-flex">Download</span> + <v-icon right dark>mdi-cloud-download-outline</v-icon> + </v-btn> + </template> + <span>Download</span> + </v-tooltip> - <v-tooltip bottom> - <template v-slot:activator="{ on }"> - <v-btn - text - @click.stop="startUpdateClient(client)" - v-on="on" - > - <span class="d-none d-lg-flex">Edit</span> - <v-icon right dark>mdi-square-edit-outline</v-icon> - </v-btn> - </template> - <span>Edit</span> - </v-tooltip> + <v-tooltip bottom> + <template v-slot:activator="{ on }"> + <v-btn + text + @click.stop="startUpdate(client)" + v-on="on" + > + <span class="d-none d-lg-flex">Edit</span> + <v-icon right dark>mdi-square-edit-outline</v-icon> + </v-btn> + </template> + <span>Edit</span> + </v-tooltip> - <v-tooltip bottom> - <template v-slot:activator="{ on }"> - <v-btn - text - @click="deleteClient(client)" - v-on="on" - > - <span class="d-none d-lg-flex">Delete</span> - <v-icon right dark>mdi-trash-can-outline</v-icon> - </v-btn> - </template> - <span>Delete</span> - </v-tooltip> + <v-tooltip bottom> + <template v-slot:activator="{ on }"> + <v-btn + text + @click="remove(client)" + v-on="on" + > + <span class="d-none d-lg-flex">Delete</span> + <v-icon right dark>mdi-trash-can-outline</v-icon> + </v-btn> + </template> + <span>Delete</span> + </v-tooltip> - <v-tooltip bottom> - <template v-slot:activator="{ on }"> - <v-btn - text - @click="sendEmailClient(client.id)" - v-on="on" - > - <span class="d-none d-lg-flex">Send Email</span> - <v-icon right dark>mdi-email-send-outline</v-icon> - </v-btn> - </template> - <span>Send Email</span> - </v-tooltip> + <v-tooltip bottom> + <template v-slot:activator="{ on }"> + <v-btn + text + @click="email(client)" + v-on="on" + > + <span class="d-none d-lg-flex">Send Email</span> + <v-icon right dark>mdi-email-send-outline</v-icon> + </v-btn> + </template> + <span>Send Email</span> + </v-tooltip> <v-spacer/> <v-tooltip right> <template v-slot:activator="{ on }"> @@ -118,7 +118,7 @@ v-on="on" color="success" v-model="client.enable" - v-on:change="updateClient(client)" + v-on:change="update(client)" /> </template> <span> {{client.enable ? 'Disable' : 'Enable'}} this client</span> @@ -133,7 +133,7 @@ </v-row> <v-dialog v-if="client" - v-model="dialogAddClient" + v-model="dialogCreate" max-width="550" > <v-card> @@ -210,14 +210,14 @@ <v-btn :disabled="!valid" color="success" - @click="addClient(client)" + @click="create(client)" > Submit <v-icon right dark>mdi-check-outline</v-icon> </v-btn> <v-btn color="primary" - @click="dialogAddClient = false" + @click="dialogCreate = false" > Cancel <v-icon right dark>mdi-close-circle-outline</v-icon> @@ -227,7 +227,7 @@ </v-dialog> <v-dialog v-if="client" - v-model="dialogEditClient" + v-model="dialogUpdate" max-width="550" > <v-card> @@ -308,14 +308,14 @@ <v-btn :disabled="!valid" color="success" - @click="updateClient(client)" + @click="update(client)" > Submit <v-icon right dark>mdi-check-outline</v-icon> </v-btn> <v-btn color="primary" - @click="dialogEditClient = false" + @click="dialogUpdate = false" > Cancel <v-icon right dark>mdi-close-circle-outline</v-icon> @@ -323,61 +323,50 @@ </v-card-actions> </v-card> </v-dialog> - <Notification v-bind:notification="notification"/> </v-container> </template> <script> - import {ApiService, API_BASE_URL} from '../services/ApiService' - import Notification from '../components/Notification' + import { mapActions, mapGetters } from 'vuex' export default { name: 'Clients', - components: { - Notification - }, - data: () => ({ - api: null, - apiBaseUrl: API_BASE_URL, - clients: [], - notification: { - show: false, - color: '', - text: '', - }, - dialogAddClient: false, - dialogEditClient: false, + dialogCreate: false, + dialogUpdate: false, client: null, - server: null, valid: false, }), + computed:{ + ...mapGetters({ + getClientQrcode: 'client/getClientQrcode', + getClientConfig: 'client/getClientConfig', + server: 'server/server', + clients: 'client/clients', + clientQrcodes: 'client/clientQrcodes', + }), + }, + mounted () { - this.api = new ApiService(); - this.getClients(); - this.getServer() + this.readAllClients() + this.readServer() }, methods: { - getClients() { - this.api.get('/client').then((res) => { - this.clients = res - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); - }, - - getServer() { - this.api.get('/server').then((res) => { - this.server = res; - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); - }, + ...mapActions('client', { + errorClient: 'error', + readAllClients: 'readAll', + creatClient: 'create', + updateClient: 'update', + deleteClient: 'delete', + emailClient: 'email', + }), + ...mapActions('server', { + readServer: 'read', + }), - startAddClient() { - this.dialogAddClient = true; + startCreate() { this.client = { name: "", email: "", @@ -385,91 +374,87 @@ allowedIPs: this.server.allowedips, address: this.server.address, } + this.dialogCreate = true; }, - addClient(client) { + + create(client) { if (client.allowedIPs.length < 1) { - this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs'); + this.errorClient('Please provide at least one valid CIDR address for client allowed IPs') return; } for (let i = 0; i < client.allowedIPs.length; i++){ if (this.$isCidr(client.allowedIPs[i]) === 0) { - this.notify('error', 'Invalid CIDR detected, please correct before submitting'); + this.errorClient('Invalid CIDR detected, please correct before submitting') return } } - this.dialogAddClient = false; - - this.api.post('/client', client).then((res) => { - this.notify('success', `Client ${res.name} successfully added`); - this.getClients() - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); + this.dialogCreate = false; + this.creatClient(client) }, - deleteClient(client) { + remove(client) { if(confirm(`Do you really want to delete ${client.name} ?`)){ - this.api.delete(`/client/${client.id}`).then((res) => { - this.notify('success', "Client successfully deleted"); - this.getClients() - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); + this.deleteClient(client) } }, - sendEmailClient(id) { - this.api.get(`/client/${id}/email`).then((res) => { - this.notify('success', "Email successfully sent"); - this.getClients() - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); + email(client) { + if (!client.email){ + this.errorClient('Client email is not defined') + return + } + + if(confirm(`Do you really want to send email to ${client.email} with all configurations ?`)){ + this.emailClient(client) + } }, - startUpdateClient(client) { + startUpdate(client) { this.client = client; - this.dialogEditClient = true; + this.dialogUpdate = true; }, - updateClient(client) { + + update(client) { // check allowed IPs if (client.allowedIPs.length < 1) { - this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs'); + this.errorClient('Please provide at least one valid CIDR address for client allowed IPs'); return; } for (let i = 0; i < client.allowedIPs.length; i++){ if (this.$isCidr(client.allowedIPs[i]) === 0) { - this.notify('error', 'Invalid CIDR detected, please correct before submitting'); + this.errorClient('Invalid CIDR detected, please correct before submitting'); return } } // check address if (client.address.length < 1) { - this.notify('error', 'Please provide at least one valid CIDR address for client'); + this.errorClient('Please provide at least one valid CIDR address for client'); return; } for (let i = 0; i < client.address.length; i++){ if (this.$isCidr(client.address[i]) === 0) { - this.notify('error', 'Invalid CIDR detected, please correct before submitting'); + this.errorClient('Invalid CIDR detected, please correct before submitting'); return } } // all good, submit - this.dialogEditClient = false; - - this.api.patch(`/client/${client.id}`, client).then((res) => { - this.notify('success', `Client ${res.name} successfully updated`); - this.getClients() - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); + this.dialogUpdate = false; + this.updateClient(client) }, - notify(color, msg) { - this.notification.show = true; - this.notification.color = color; - this.notification.text = msg; - } + forceFileDownload(client){ + let config = this.getClientConfig(client.id) + if (!config) { + this.errorClient('Failed to download client config'); + return + } + const url = window.URL.createObjectURL(new Blob([config])) + const link = document.createElement('a') + link.href = url + link.setAttribute('download', 'wg0.conf') //or any other extension + document.body.appendChild(link) + link.click() + }, } }; </script> diff --git a/ui/src/components/Footer.vue b/ui/src/components/Footer.vue new file mode 100644 index 0000000000000000000000000000000000000000..336f8a8d2cba89129a2c6ca45a7affcf4453dbce --- /dev/null +++ b/ui/src/components/Footer.vue @@ -0,0 +1,51 @@ +<template> + <v-container> + <v-footer app> + <v-row justify="start" no-gutters> + <v-col cols="12" lg="6" md="12" sm="12"> + <div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'"> + <small>Copyright © {{ new Date().getFullYear() }}, Wg Gen Web. </small> + <small>This work is licensed under <a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">WTFPL License.</a></small> + </div> + </v-col> + </v-row> + <v-row justify="end" no-gutters> + <v-col cols="12" lg="6" md="12" sm="12"> + <div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'"> + <small>Created with</small> + <v-icon class="pr-1 pl-1">mdi-heart</v-icon><span>by</span><a class="pr-2 pl-1" href="mailto:vx3r@127-0-0-1.fr">vx3r</a> + <a :href="'https://github.com/vx3r/wg-gen-web/commit/' + version"><kbd>Version: {{ version.substring(0,7) }}</kbd></a> + </div> + </v-col> + </v-row> + </v-footer> + </v-container> +</template> + +<script> + import {mapActions, mapGetters} from "vuex"; + + export default { + name: 'Footer', + + data: () => ({ + + }), + + computed:{ + ...mapGetters({ + version: 'server/version', + }), + }, + + mounted() { + this.versionServer() + }, + + methods: { + ...mapActions('server', { + versionServer: 'version', + }), + } + } +</script> diff --git a/ui/src/components/Header.vue b/ui/src/components/Header.vue new file mode 100644 index 0000000000000000000000000000000000000000..58ca18fa712add7492071ab0d89928f1f6a2cf1a --- /dev/null +++ b/ui/src/components/Header.vue @@ -0,0 +1,77 @@ +<template> + <v-container> + <v-app-bar app> + <img class="mr-3" :src="require('../assets/logo.png')" height="50" alt="Wg Gen Web"/> + <v-toolbar-title to="/">Wg Gen Web</v-toolbar-title> + + <v-spacer /> + + <v-toolbar-items> + <v-btn to="/clients"> + Clients + <v-icon right dark>mdi-account-network-outline</v-icon> + </v-btn> + <v-btn to="/server"> + Server + <v-icon right dark>mdi-vpn</v-icon> + </v-btn> + </v-toolbar-items> + + <v-menu + left + bottom + > + <template v-slot:activator="{ on }"> + <v-btn icon v-on="on"> + <v-icon>mdi-account-circle</v-icon> + </v-btn> + </template> + + <v-card + class="mx-auto" + max-width="344" + outlined + > + <v-list-item three-line> + <v-list-item-content> + <div class="overline mb-4">connected as</div> + <v-list-item-title class="headline mb-1">{{user.name}}</v-list-item-title> + <v-list-item-subtitle>Email: {{user.email}}</v-list-item-subtitle> + <v-list-item-subtitle>Issuer: {{user.issuer}}</v-list-item-subtitle> + <v-list-item-subtitle>Issued at: {{ user.issuedAt | formatDate }}</v-list-item-subtitle> + </v-list-item-content> + </v-list-item> + <v-card-actions> + <v-btn small + v-on:click="logout()" + > + logout + <v-icon small right dark>mdi-logout</v-icon> + </v-btn> + </v-card-actions> + </v-card> + </v-menu> + + </v-app-bar> + </v-container> +</template> + +<script> + import {mapActions, mapGetters} from "vuex"; + + export default { + name: 'Header', + + computed:{ + ...mapGetters({ + user: 'auth/user', + }), + }, + + methods: { + ...mapActions('auth', { + logout: 'logout', + }), + } + } +</script> diff --git a/ui/src/components/Server.vue b/ui/src/components/Server.vue index c8fdffcff90b00f4bc4ca008a348fa7cd10c02af..5a70e94a9105248b0bca8d90f925c3e14ff96237 100644 --- a/ui/src/components/Server.vue +++ b/ui/src/components/Server.vue @@ -158,7 +158,7 @@ <v-btn class="ma-2" color="success" - :href="`${apiBaseUrl}/server/config`" + v-on:click="forceFileDownload()" > Download server configuration <v-icon right dark>mdi-cloud-download-outline</v-icon> @@ -167,52 +167,44 @@ <v-btn class="ma-2" color="warning" - @click="updateServer" + @click="update" > Update server configuration <v-icon right dark>mdi-update</v-icon> </v-btn> <v-divider dark/> </v-row> - <Notification v-bind:notification="notification"/> </v-container> </template> <script> - import {API_BASE_URL, ApiService} from "../services/ApiService"; - import Notification from '../components/Notification' + import {mapActions, mapGetters} from "vuex"; export default { name: 'Server', - components: { - Notification - }, - data: () => ({ - api: null, - server: null, - apiBaseUrl: API_BASE_URL, - notification: { - show: false, - color: '', - text: '', - }, + }), + computed:{ + ...mapGetters({ + server: 'server/server', + config: 'server/config', + }), + }, + mounted () { - this.api = new ApiService(); - this.getServer() + this.readServer() }, methods: { - getServer() { - this.api.get('/server').then((res) => { - this.server = res; - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); - }, - updateServer () { + ...mapActions('server', { + errorServer: 'error', + readServer: 'read', + updateServer: 'update', + }), + + update() { // convert int values this.server.listenPort = parseInt(this.server.listenPort, 10); this.server.persistentKeepalive = parseInt(this.server.persistentKeepalive, 10); @@ -220,12 +212,12 @@ // check server addresses if (this.server.address.length < 1) { - this.notify('error', 'Please provide at least one valid CIDR address for server interface'); + this.errorServer('Please provide at least one valid CIDR address for server interface'); return; } for (let i = 0; i < this.server.address.length; i++){ if (this.$isCidr(this.server.address[i]) === 0) { - this.notify('error', `Invalid CIDR detected, please correct ${this.server.address[i]} before submitting`); + this.errorServer(`Invalid CIDR detected, please correct ${this.server.address[i]} before submitting`); return } } @@ -233,35 +225,34 @@ // check DNS correct for (let i = 0; i < this.server.dns.length; i++){ if (this.$isCidr(this.server.dns[i] + '/32') === 0) { - this.notify('error', `Invalid IP detected, please correct ${this.server.dns[i]} before submitting`); + this.errorServer(`Invalid IP detected, please correct ${this.server.dns[i]} before submitting`); return } } // check client AllowedIPs if (this.server.allowedips.length < 1) { - this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs'); + this.errorServer('Please provide at least one valid CIDR address for client allowed IPs'); return; } for (let i = 0; i < this.server.allowedips.length; i++){ if (this.$isCidr(this.server.allowedips[i]) === 0) { - this.notify('error', 'Invalid CIDR detected, please correct before submitting'); + this.errorServer('Invalid CIDR detected, please correct before submitting'); return } } - this.api.patch('/server', this.server).then((res) => { - this.notify('success', "Server successfully updated"); - this.server = res; - }).catch((e) => { - this.notify('error', e.response.status + ' ' + e.response.statusText); - }); + this.updateServer(this.server) + }, + + forceFileDownload(){ + const url = window.URL.createObjectURL(new Blob([this.config])) + const link = document.createElement('a') + link.href = url + link.setAttribute('download', 'wg0.conf') //or any other extension + document.body.appendChild(link) + link.click() }, - notify(color, msg) { - this.notification.show = true; - this.notification.color = color; - this.notification.text = msg; - } } }; </script> diff --git a/ui/src/main.js b/ui/src/main.js index bfb2c507e3d5233979b13bdaeb0b7341de2b7990..fea553f62d81535f07cd77c7b3aeccca88014676 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -1,14 +1,18 @@ import Vue from 'vue' import App from './App.vue' import router from './router' +import store from './store' import vuetify from './plugins/vuetify'; import './plugins/moment'; import './plugins/cidr' +import './plugins/axios' -Vue.config.productionTip = false +// Don't warn about using the dev version of Vue in development. +Vue.config.productionTip = process.env.NODE_ENV === 'production' new Vue({ router, + store, vuetify, render: function (h) { return h(App) } }).$mount('#app') diff --git a/ui/src/plugins/axios.js b/ui/src/plugins/axios.js new file mode 100644 index 0000000000000000000000000000000000000000..444ef90787f9bc91a3126388a5c54d927449df4e --- /dev/null +++ b/ui/src/plugins/axios.js @@ -0,0 +1,26 @@ +import Vue from 'vue' +import axios from "axios"; +import VueAxios from "vue-axios"; +import TokenService from "../services/token.service"; + +Vue.use(VueAxios, axios); + +let baseUrl = "/api/v1.0"; +if (process.env.NODE_ENV === "development"){ + baseUrl = process.env.VUE_APP_API_BASE_URL; +} + +Vue.axios.defaults.baseURL = baseUrl; + +Vue.axios.interceptors.response.use(function (response) { + return response; +}, function (error) { + if (401 === error.response.status) { + TokenService.destroyToken(); + TokenService.destroyClientId(); + window.location = '/'; + } else { + return Promise.reject(error); + } +}); + diff --git a/ui/src/router/index.js b/ui/src/router/index.js index bbd46bb8b6567b11ed662cb764ad1547f51fd867..ee29359639b949ce6a018767c37c9931482b6c85 100644 --- a/ui/src/router/index.js +++ b/ui/src/router/index.js @@ -1,22 +1,19 @@ import Vue from 'vue' import VueRouter from 'vue-router' +import store from "../store"; Vue.use(VueRouter); const routes = [ - { - path: '/', - name: 'index', - component: function () { - return import(/* webpackChunkName: "Index" */ '../views/Index.vue') - }, - }, { path: '/clients', name: 'clients', component: function () { return import(/* webpackChunkName: "Clients" */ '../views/Clients.vue') }, + meta: { + requiresAuth: true + } }, { path: '/server', @@ -24,6 +21,9 @@ const routes = [ component: function () { return import(/* webpackChunkName: "Server" */ '../views/Server.vue') }, + meta: { + requiresAuth: true + } } ]; @@ -33,4 +33,16 @@ const router = new VueRouter({ routes }); +router.beforeEach((to, from, next) => { + if(to.matched.some(record => record.meta.requiresAuth)) { + if (store.getters["auth/isAuthenticated"]) { + next() + return + } + next('/') + } else { + next() + } +}) + export default router diff --git a/ui/src/services/ApiService.js b/ui/src/services/ApiService.js deleted file mode 100644 index dc55572b974b1acb4e294a1ccfca6d20af70a6e9..0000000000000000000000000000000000000000 --- a/ui/src/services/ApiService.js +++ /dev/null @@ -1,40 +0,0 @@ -import axios from 'axios' - -let baseUrl = "/api/v1.0"; -if (process.env.NODE_ENV === "development"){ - baseUrl = process.env.VUE_APP_API_BASE_URL -} - -export const API_BASE_URL = baseUrl; - -export class ApiService { - get(resource) { - return axios - .get(`${API_BASE_URL}${resource}`) - .then(response => response.data) - }; - - post(resource, data) { - return axios - .post(`${API_BASE_URL}${resource}`, data) - .then(response => response.data) - }; - - put(resource, data) { - return axios - .put(`${API_BASE_URL}${resource}`, data) - .then(response => response.data) - }; - - patch(resource, data) { - return axios - .patch(`${API_BASE_URL}${resource}`, data) - .then(response => response.data) - }; - - delete(resource) { - return axios - .delete(`${API_BASE_URL}${resource}`) - .then(response => response.data) - }; -} diff --git a/ui/src/services/api.service.js b/ui/src/services/api.service.js new file mode 100644 index 0000000000000000000000000000000000000000..97b0093c0a1e2b444f5aaee3effb9100a98dcb47 --- /dev/null +++ b/ui/src/services/api.service.js @@ -0,0 +1,59 @@ +import Vue from "vue"; +import TokenService from "./token.service"; + +const ApiService = { + + setHeader() { + Vue.axios.defaults.headers.common.Authorization = `${TokenService.getToken()}`; + }, + + get(resource) { + return Vue.axios.get(resource) + .then(response => response.data) + .catch(error => { + throw new Error(`ApiService: ${error}`) + }); + }, + + post(resource, params) { + return Vue.axios.post(resource, params) + .then(response => response.data) + .catch(error => { + throw new Error(`ApiService: ${error}`) + }); + }, + + put(resource, params) { + return Vue.axios.put(resource, params) + .then(response => response.data) + .catch(error => { + throw new Error(`ApiService: ${error}`) + }); + }, + + patch(resource, params) { + return Vue.axios.patch(resource, params) + .then(response => response.data) + .catch(error => { + throw new Error(`ApiService: ${error}`) + }); + }, + + delete(resource) { + return Vue.axios.delete(resource) + .then(response => response.data) + .catch(error => { + throw new Error(`ApiService: ${error}`) + }); + }, + + getWithConfig(resource, config) { + return Vue.axios.get(resource, config) + .then(response => response.data) + .catch(error => { + throw new Error(`ApiService: ${error}`) + }); + }, +}; + +export default ApiService; diff --git a/ui/src/services/token.service.js b/ui/src/services/token.service.js new file mode 100644 index 0000000000000000000000000000000000000000..cbbb12de36ed2039c0dffef73a69b7fc67608d03 --- /dev/null +++ b/ui/src/services/token.service.js @@ -0,0 +1,35 @@ +const TOKEN_KEY = "token"; +const CLIENT_ID_KEY = "client_id"; + +export const getToken = () => { + return window.localStorage.getItem(TOKEN_KEY); +}; + +export const saveToken = token => { + window.localStorage.setItem(TOKEN_KEY, token); +}; + +export const destroyToken = () => { + window.localStorage.removeItem(TOKEN_KEY); +}; + +export const getClientId = () => { + return window.localStorage.getItem(CLIENT_ID_KEY); +}; + +export const saveClientId = token => { + window.localStorage.setItem(CLIENT_ID_KEY, token); +}; + +export const destroyClientId = () => { + window.localStorage.removeItem(CLIENT_ID_KEY); +}; + +export default { + getToken, + saveToken, + destroyToken, + getClientId, + saveClientId, + destroyClientId +}; diff --git a/ui/src/store/index.js b/ui/src/store/index.js new file mode 100644 index 0000000000000000000000000000000000000000..97b5b767ebbfaf23cac1b4503bc1589ca0c627dc --- /dev/null +++ b/ui/src/store/index.js @@ -0,0 +1,19 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import auth from "./modules/auth"; +import client from "./modules/client"; +import server from "./modules/server"; + +Vue.use(Vuex) + +export default new Vuex.Store({ + state: {}, + getters : {}, + mutations: {}, + actions:{}, + modules: { + auth, + client, + server + } +}) diff --git a/ui/src/store/modules/auth.js b/ui/src/store/modules/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..90b7e0769b64044cca4f981c4e846c0bbd8e55ec --- /dev/null +++ b/ui/src/store/modules/auth.js @@ -0,0 +1,126 @@ +import ApiService from "../../services/api.service"; +import TokenService from "../../services/token.service"; + +const state = { + error: null, + user: null, + authStatus: '', + authRedirectUrl: '', +}; + +const getters = { + error(state) { + return state.error; + }, + user(state) { + return state.user; + }, + isAuthenticated(state) { + return state.user !== null; + }, + authRedirectUrl(state) { + return state.authRedirectUrl + }, + authStatus(state) { + return state.authStatus + }, +}; + +const actions = { + user({ commit }){ + ApiService.get("/auth/user") + .then( resp => { + commit('user', resp) + }) + .catch(err => { + commit('error', err); + commit('logout') + }); + }, + + oauth2_url({ commit, dispatch }){ + if (TokenService.getToken()) { + ApiService.setHeader(); + dispatch('user'); + return + } + ApiService.get("/auth/oauth2_url") + .then(resp => { + if (resp.codeUrl === '_magic_string_fake_auth_no_redirect_'){ + console.log("server report oauth2 is disabled, fake exchange") + commit('authStatus', 'disabled') + TokenService.saveClientId(resp.clientId) + dispatch('oauth2_exchange', {code: "", state: resp.state}) + } else { + commit('authStatus', 'redirect') + commit('authRedirectUrl', resp) + } + }) + .catch(err => { + commit('authStatus', 'error') + commit('error', err); + commit('logout') + }) + }, + + oauth2_exchange({ commit, dispatch }, data){ + data.clientId = TokenService.getClientId() + ApiService.post("/auth/oauth2_exchange", data) + .then(resp => { + commit('authStatus', 'success') + commit('token', resp) + dispatch('user'); + }) + .catch(err => { + commit('authStatus', 'error') + commit('error', err); + commit('logout') + }) + }, + + logout({ commit }){ + ApiService.get("/auth/logout") + .then(resp => { + commit('logout') + }) + .catch(err => { + commit('authStatus', '') + commit('error', err); + commit('logout') + }) + }, +} + +const mutations = { + error(state, error) { + state.error = error; + }, + authStatus(state, authStatus) { + state.authStatus = authStatus; + }, + authRedirectUrl(state, resp) { + state.authRedirectUrl = resp.codeUrl; + TokenService.saveClientId(resp.clientId); + }, + token(state, token) { + TokenService.saveToken(token); + ApiService.setHeader(); + TokenService.destroyClientId(); + }, + user(state, user) { + state.user = user; + }, + logout(state) { + state.user = null; + TokenService.destroyToken(); + TokenService.destroyClientId(); + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/ui/src/store/modules/client.js b/ui/src/store/modules/client.js new file mode 100644 index 0000000000000000000000000000000000000000..8453b351be918938a310506005e063bfc6a66980 --- /dev/null +++ b/ui/src/store/modules/client.js @@ -0,0 +1,177 @@ +import ApiService from "../../services/api.service"; + +const state = { + error: null, + clients: [], + clientQrcodes: [], + clientConfigs: [] +} + +const getters = { + error(state) { + return state.error; + }, + clients(state) { + return state.clients; + }, + getClientQrcode: (state) => (id) => { + let item = state.clientQrcodes.find(item => item.id === id) + // initial load fails, must wait promise and stuff... + return item ? item.qrcode : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" + }, + getClientConfig: (state) => (id) => { + let item = state.clientConfigs.find(item => item.id === id) + return item ? item.config : null + } +} + +const actions = { + error({ commit }, error){ + commit('error', error) + }, + + readAll({ commit, dispatch }){ + ApiService.get("/client") + .then(resp => { + commit('clients', resp) + dispatch('readQrcodes') + dispatch('readConfigs') + }) + .catch(err => { + commit('error', err) + }) + }, + + create({ commit, dispatch }, client){ + ApiService.post("/client", client) + .then(resp => { + dispatch('readQrcode', resp) + dispatch('readConfig', resp) + commit('create', resp) + }) + .catch(err => { + commit('error', err) + }) + }, + + update({ commit, dispatch }, client){ + ApiService.patch(`/client/${client.id}`, client) + .then(resp => { + dispatch('readQrcode', resp) + dispatch('readConfig', resp) + commit('update', resp) + }) + .catch(err => { + commit('error', err) + }) + }, + + delete({ commit }, client){ + ApiService.delete(`/client/${client.id}`) + .then(() => { + commit('delete', client) + }) + .catch(err => { + commit('error', err) + }) + }, + + email({ commit }, client){ + ApiService.get(`/client/${client.id}/email`) + .then(() => { + }) + .catch(err => { + commit('error', err) + }) + }, + + readQrcode({ state, commit }, client){ + ApiService.getWithConfig(`/client/${client.id}/config?qrcode=true`, {responseType: 'arraybuffer'}) + .then(resp => { + let image = Buffer.from(resp, 'binary').toString('base64') + commit('clientQrcodes', { client, image }) + }) + .catch(err => { + commit('error', err) + }) + }, + + readConfig({ state, commit }, client){ + ApiService.getWithConfig(`/client/${client.id}/config?qrcode=false`, {responseType: 'arraybuffer'}) + .then(resp => { + commit('clientConfigs', { client: client, config: resp }) + }) + .catch(err => { + commit('error', err) + }) + }, + + readQrcodes({ state, dispatch }){ + state.clients.forEach(client => { + dispatch('readQrcode', client) + }) + }, + + readConfigs({ state, dispatch }){ + state.clients.forEach(client => { + dispatch('readConfig', client) + }) + }, +} + +const mutations = { + error(state, error) { + state.error = error; + }, + clients(state, clients){ + state.clients = clients + }, + create(state, client){ + state.clients.push(client) + }, + update(state, client){ + let index = state.clients.findIndex(x => x.id === client.id); + if (index !== -1) { + state.clients.splice(index, 1); + state.clients.push(client); + } else { + state.error = "update client failed, not in list" + } + }, + delete(state, client){ + let index = state.clients.findIndex(x => x.id === client.id); + if (index !== -1) { + state.clients.splice(index, 1); + } else { + state.error = "delete client failed, not in list" + } + }, + clientQrcodes(state, { client, image }){ + let index = state.clientQrcodes.findIndex(x => x.id === client.id); + if (index !== -1) { + state.clientQrcodes.splice(index, 1); + } + state.clientQrcodes.push({ + id: client.id, + qrcode: image + }) + }, + clientConfigs(state, { client, config }){ + let index = state.clientConfigs.findIndex(x => x.id === client.id); + if (index !== -1) { + state.clientConfigs.splice(index, 1); + } + state.clientConfigs.push({ + id: client.id, + config: config + }) + }, +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/ui/src/store/modules/server.js b/ui/src/store/modules/server.js new file mode 100644 index 0000000000000000000000000000000000000000..63ba16968c332d47365b40f28cbcad9638a31348 --- /dev/null +++ b/ui/src/store/modules/server.js @@ -0,0 +1,100 @@ +import ApiService from "../../services/api.service"; + +const state = { + error: null, + server: null, + config: '', + version: '_ci_build_not_run_properly_', +} + +const getters = { + error(state) { + return state.error; + }, + + server(state) { + return state.server; + }, + + version(state) { + return state.version; + }, + + config(state) { + return state.config; + }, +} + +const actions = { + error({ commit }, error){ + commit('error', error) + }, + + read({ commit, dispatch }){ + ApiService.get("/server") + .then(resp => { + commit('server', resp) + dispatch('config') + }) + .catch(err => { + commit('error', err) + }) + }, + + update({ commit }, server){ + ApiService.patch(`/server`, server) + .then(resp => { + commit('server', resp) + }) + .catch(err => { + commit('error', err) + }) + }, + + config({ commit }){ + ApiService.getWithConfig("/server/config", {responseType: 'arraybuffer'}) + .then(resp => { + commit('config', resp) + }) + .catch(err => { + commit('error', err) + }) + }, + + version({ commit }){ + ApiService.get("/server/version") + .then(resp => { + commit('version', resp.version) + }) + .catch(err => { + commit('error', err) + }) + }, + +} + +const mutations = { + error(state, error) { + state.error = error; + }, + + server(state, server){ + state.server = server + }, + + config(state, config){ + state.config = config + }, + + version(state, version){ + state.version = version + }, +} + +export default { + namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/ui/src/views/Index.vue b/ui/src/views/Index.vue deleted file mode 100644 index 46ed1f1861dfdd31540f4eca4b23b25ba4e21f55..0000000000000000000000000000000000000000 --- a/ui/src/views/Index.vue +++ /dev/null @@ -1,10 +0,0 @@ -<template> -</template> - -<script> - export default { - created () { - this.$router.replace({ name: 'clients' }) - } - } -</script> diff --git a/util/util.go b/util/util.go index 5d7e323c57e616b6c0404eb23351df0e22674ebe..3d3f88c73adcfe99564589000b82831df49ece19 100644 --- a/util/util.go +++ b/util/util.go @@ -1,6 +1,8 @@ package util import ( + "crypto/rand" + "encoding/base64" "errors" "io/ioutil" "net" @@ -9,6 +11,7 @@ import ( ) var ( + AuthTokenHeaderName = "Authorization" // RegexpEmail check valid email RegexpEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") ) @@ -51,7 +54,7 @@ func DirectoryExists(name string) bool { return info.IsDir() } -// GetAvailableIp search for an available in cidr against a list of reserved ips +// GetAvailableIp search for an available ip in cidr against a list of reserved ips func GetAvailableIp(cidr string, reserved []string) (string, error) { ip, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -132,3 +135,28 @@ func BroadcastAddr(n *net.IPNet) net.IP { } return broadcast } + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, nil +} + +// GenerateRandomString returns a URL-safe, base64 encoded +// securely generated random string. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func GenerateRandomString(s int) (string, error) { + b, err := GenerateRandomBytes(s) + return base64.URLEncoding.EncodeToString(b), err +}